ViewModelStore, register repositories, improve view model relations

This commit is contained in:
FinnStutzenstein 2019-02-01 13:56:08 +01:00
parent 812168b00c
commit d0991936d6
150 changed files with 1943 additions and 1519 deletions

View File

@ -1,20 +1,23 @@
import { Type } from '@angular/core';
import { ModelConstructor, BaseModel } from '../shared/models/base/base-model'; import { ModelConstructor, BaseModel } from '../shared/models/base/base-model';
import { MainMenuEntry } from './core-services/main-menu.service'; import { MainMenuEntry } from './core-services/main-menu.service';
import { Searchable } from '../shared/models/base/searchable'; import { Searchable } from '../site/base/searchable';
import { Type } from '@angular/core';
import { BaseRepository } from './repositories/base-repository'; import { BaseRepository } from './repositories/base-repository';
import { BaseViewModel, ViewModelConstructor } from 'app/site/base/base-view-model';
interface BaseModelEntry { interface BaseModelEntry {
collectionString: string; collectionString: string;
repository: Type<BaseRepository<any, any>>; repository: Type<BaseRepository<any, any>>;
}
export interface ModelEntry extends BaseModelEntry {
model: ModelConstructor<BaseModel>; model: ModelConstructor<BaseModel>;
} }
export interface ModelEntry extends BaseModelEntry {
viewModel: ViewModelConstructor<BaseViewModel>;
}
export interface SearchableModelEntry extends BaseModelEntry { export interface SearchableModelEntry extends BaseModelEntry {
model: new (...args: any[]) => BaseModel & Searchable; viewModel: new (...args: any[]) => BaseViewModel & Searchable;
searchOrder: number; searchOrder: number;
} }

View File

@ -14,9 +14,11 @@ import { TagAppConfig } from '../../site/tags/tag.config';
import { MainMenuService } from './main-menu.service'; import { MainMenuService } from './main-menu.service';
import { HistoryAppConfig } from 'app/site/history/history.config'; import { HistoryAppConfig } from 'app/site/history/history.config';
import { SearchService } from '../ui-services/search.service'; import { SearchService } from '../ui-services/search.service';
import { isSearchable } from '../../shared/models/base/searchable'; import { isSearchable } from '../../site/base/searchable';
import { ProjectorAppConfig } from 'app/site/projector/projector.config'; import { ProjectorAppConfig } from 'app/site/projector/projector.config';
import { BaseRepository } from 'app/core/repositories/base-repository'; import { BaseRepository } from 'app/core/repositories/base-repository';
import { OnAfterAppsLoaded } from '../onAfterAppsLoaded';
import { ServicesToLoadOnAppsLoaded } from '../core.module';
/** /**
* A list of all app configurations of all delivered apps. * A list of all app configurations of all delivered apps.
@ -63,16 +65,21 @@ export class AppLoadService {
const plugin = await import('../../../../../plugins/' + pluginName + '/' + pluginName); const plugin = await import('../../../../../plugins/' + pluginName + '/' + pluginName);
plugin.main(); plugin.main();
}*/ }*/
const repositories: OnAfterAppsLoaded[] = [];
appConfigs.forEach((config: AppConfig) => { appConfigs.forEach((config: AppConfig) => {
if (config.models) { if (config.models) {
config.models.forEach(entry => { config.models.forEach(entry => {
let repository: BaseRepository<any, any> = null; let repository: BaseRepository<any, any> = null;
if (entry.repository) {
repository = this.injector.get(entry.repository); repository = this.injector.get(entry.repository);
} repositories.push(repository);
this.modelMapper.registerCollectionElement(entry.collectionString, entry.model, repository); this.modelMapper.registerCollectionElement(
entry.collectionString,
entry.model,
entry.viewModel,
repository
);
if (this.isSearchableModelEntry(entry)) { if (this.isSearchableModelEntry(entry)) {
this.searchService.registerModel(entry.collectionString, entry.model, entry.searchOrder); this.searchService.registerModel(entry.collectionString, entry.viewModel, entry.searchOrder);
} }
}); });
} }
@ -80,6 +87,16 @@ export class AppLoadService {
this.mainMenuService.registerEntries(config.mainMenuEntries); this.mainMenuService.registerEntries(config.mainMenuEntries);
} }
}); });
// Collect all services to notify for the OnAfterAppsLoadedHook
const onAfterAppsLoadedItems = ServicesToLoadOnAppsLoaded.map(service => {
return this.injector.get(service);
}).concat(repositories);
// Notify them.
onAfterAppsLoadedItems.forEach(repo => {
repo.onAfterAppsLoaded();
});
} }
private isSearchableModelEntry(entry: ModelEntry | SearchableModelEntry): entry is SearchableModelEntry { private isSearchableModelEntry(entry: ModelEntry | SearchableModelEntry): entry is SearchableModelEntry {
@ -87,7 +104,7 @@ export class AppLoadService {
// We need to double check, because Typescipt cannot check contructors. If typescript could differentiate // We need to double check, because Typescipt cannot check contructors. If typescript could differentiate
// between (ModelConstructor<BaseModel>) and (new (...args: any[]) => (BaseModel & Searchable)), we would not have // between (ModelConstructor<BaseModel>) and (new (...args: any[]) => (BaseModel & Searchable)), we would not have
// to check if the result of the contructor (the model instance) is really a searchable. // to check if the result of the contructor (the model instance) is really a searchable.
if (!isSearchable(new entry.model())) { if (!isSearchable(new entry.viewModel())) {
throw Error( throw Error(
`Wrong configuration for ${ `Wrong configuration for ${
entry.collectionString entry.collectionString

View File

@ -120,7 +120,6 @@ export class AutoupdateService extends OpenSlidesComponent {
await this.DS.add(this.mapObjectsToBaseModels(collection, autoupdate.changed[collection])); await this.DS.add(this.mapObjectsToBaseModels(collection, autoupdate.changed[collection]));
} }
console.log('new max change id', autoupdate.to_change_id);
await this.DS.flushToStorage(autoupdate.to_change_id); await this.DS.flushToStorage(autoupdate.to_change_id);
} else { } else {
// autoupdate fully in the future. we are missing something! // autoupdate fully in the future. we are missing something!
@ -135,7 +134,7 @@ export class AutoupdateService extends OpenSlidesComponent {
* @returns A list of basemodels constructed from the given models. * @returns A list of basemodels constructed from the given models.
*/ */
private mapObjectsToBaseModels(collection: string, models: object[]): BaseModel[] { private mapObjectsToBaseModels(collection: string, models: object[]): BaseModel[] {
const targetClass = this.modelMapper.getModelConstructor(collection); const targetClass = this.modelMapper.getModelConstructorFromCollectionString(collection);
if (!targetClass) { if (!targetClass) {
throw new Error(`Unregistered resource ${collection}`); throw new Error(`Unregistered resource ${collection}`);
} }

View File

@ -2,64 +2,134 @@ import { Injectable } from '@angular/core';
import { ModelConstructor, BaseModel } from '../../shared/models/base/base-model'; import { ModelConstructor, BaseModel } from '../../shared/models/base/base-model';
import { BaseRepository } from 'app/core/repositories/base-repository'; import { BaseRepository } from 'app/core/repositories/base-repository';
import { ViewModelConstructor, BaseViewModel } from 'app/site/base/base-view-model';
/** /**
* Registeres the mapping of collection strings <--> actual types. Every Model should register itself here. * Holds a mapping entry with the matching collection string,
* model constructor, view model constructor and the repository
*/
type MappingEntry = [
string,
ModelConstructor<BaseModel>,
ViewModelConstructor<BaseViewModel>,
BaseRepository<BaseViewModel, BaseModel>
];
/**
* Registeres the mapping between collection strings, models constructors, view
* model constructors and repositories.
* All models ned to be registered!
*/ */
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class CollectionStringMapperService { export class CollectionStringMapperService {
/** /**
* Mapps collection strings to model constructors. Accessed by {@method registerCollectionElement} and * Maps collection strings to mapping entries
* {@method getCollectionStringType}.
*/ */
private collectionStringsTypeMapping: { private collectionStringMapping: {
[collectionString: string]: [ModelConstructor<BaseModel>, BaseRepository<any, any>]; [collectionString: string]: MappingEntry;
} = {}; } = {};
/** /**
* Constructor to create the NotifyService. Registers itself to the WebsocketService. * Maps models to mapping entries
* @param websocketService
*/ */
private modelMapping: {
[modelName: string]: MappingEntry;
} = {};
/**
* Maps view models to mapping entries
*/
private viewModelMapping: {
[viewModelname: string]: MappingEntry;
} = {};
/**
* Maps repositories to mapping entries
*/
private repositoryMapping: {
[repositoryName: string]: MappingEntry;
} = {};
public constructor() {} public constructor() {}
/** /**
* Registers the type to the collection string * Registers the combination of a collection string, model, view model and repository
* @param collectionString * @param collectionString
* @param model * @param model
*/ */
public registerCollectionElement( public registerCollectionElement<V extends BaseViewModel, M extends BaseModel>(
collectionString: string, collectionString: string,
model: ModelConstructor<BaseModel>, model: ModelConstructor<M>,
repository: BaseRepository<any, any> viewModel: ViewModelConstructor<V>,
repository: BaseRepository<V, M>
): void { ): void {
this.collectionStringsTypeMapping[collectionString] = [model, repository]; const entry: MappingEntry = [collectionString, model, viewModel, repository];
this.collectionStringMapping[collectionString] = entry;
this.modelMapping[model.name] = entry;
this.viewModelMapping[viewModel.name] = entry;
this.repositoryMapping[repository.name] = entry;
} }
/** // The following accessors are for giving one of EntryType by given a different object
* Returns the constructor of the requested collection or undefined, if it is not registered. // of EntryType.
* @param collectionString the requested collection
*/ public getCollectionStringFromModelConstructor<M extends BaseModel>(ctor: ModelConstructor<M>): string {
public getModelConstructor(collectionString: string): ModelConstructor<BaseModel> { return this.modelMapping[ctor.name][0];
return this.collectionStringsTypeMapping[collectionString][0]; }
public getCollectionStringFromViewModelConstructor<V extends BaseViewModel>(ctor: ViewModelConstructor<V>): string {
return this.viewModelMapping[ctor.name][0];
}
public getCollectionStringFromRepository<M extends BaseModel, V extends BaseViewModel>(
repository: BaseRepository<V, M>
): string {
return this.repositoryMapping[repository.name][0];
} }
/** public getModelConstructorFromCollectionString<M extends BaseModel>(collectionString: string): ModelConstructor<M> {
* Returns the repository of the requested collection or undefined, if it is not registered. return this.collectionStringMapping[collectionString][1] as ModelConstructor<M>;
* @param collectionString the requested collection }
*/ public getModelConstructorFromViewModelConstructor<V extends BaseViewModel, M extends BaseModel>(
public getRepository(collectionString: string): BaseRepository<any, any> { ctor: ViewModelConstructor<V>
return this.collectionStringsTypeMapping[collectionString][1]; ): ModelConstructor<M> {
return this.viewModelMapping[ctor.name][1] as ModelConstructor<M>;
}
public getModelConstructorFromRepository<V extends BaseViewModel, M extends BaseModel>(
repository: BaseRepository<V, M>
): ModelConstructor<M> {
return this.repositoryMapping[repository.name][1] as ModelConstructor<M>;
} }
/** public getViewModelConstructorFromCollectionString<M extends BaseViewModel>(
* Returns the collection string of a given ModelConstructor or undefined, if it is not registered. collectionString: string
* @param ctor ): ViewModelConstructor<M> {
*/ return this.collectionStringMapping[collectionString][2] as ViewModelConstructor<M>;
public getCollectionString(ctor: ModelConstructor<BaseModel>): string { }
return Object.keys(this.collectionStringsTypeMapping).find((collectionString: string) => { public getViewModelConstructorFromModelConstructor<V extends BaseViewModel, M extends BaseModel>(
return ctor === this.collectionStringsTypeMapping[collectionString][0]; ctor: ModelConstructor<M>
}); ): ViewModelConstructor<V> {
return this.modelMapping[ctor.name][2] as ViewModelConstructor<V>;
}
public getViewModelConstructorFromRepository<V extends BaseViewModel, M extends BaseModel>(
repository: BaseRepository<V, M>
): ViewModelConstructor<V> {
return this.repositoryMapping[repository.name][2] as ViewModelConstructor<V>;
}
public getRepositoryFromCollectionString<V extends BaseViewModel, M extends BaseModel>(
collectionString: string
): BaseRepository<V, M> {
return this.collectionStringMapping[collectionString][3] as BaseRepository<V, M>;
}
public getRepositoryFromModelConstructor<V extends BaseViewModel, M extends BaseModel>(
ctor: ModelConstructor<M>
): BaseRepository<V, M> {
return this.modelMapping[ctor.name][3] as BaseRepository<V, M>;
}
public getRepositoryFromViewModelConstructor<V extends BaseViewModel, M extends BaseModel>(
ctor: ViewModelConstructor<V>
): BaseRepository<V, M> {
return this.viewModelMapping[ctor.name][3] as BaseRepository<V, M>;
} }
} }

View File

@ -72,6 +72,18 @@ export class DataStoreService {
*/ */
private readonly changedSubject: Subject<BaseModel> = new Subject<BaseModel>(); private readonly changedSubject: Subject<BaseModel> = new Subject<BaseModel>();
/**
* This is subject notify all subscribers _before_ the `secondaryModelChangeSubject`.
* It's the same subject as the changedSubject.
*/
public readonly primaryModelChangeSubject = new Subject<BaseModel>();
/**
* This is subject notify all subscribers _after_ the `primaryModelChangeSubject`.
* It's the same subject as the changedSubject.
*/
public readonly secondaryModelChangeSubject = new Subject<BaseModel>();
/** /**
* Observe the datastore for changes. * Observe the datastore for changes.
* *
@ -127,7 +139,12 @@ export class DataStoreService {
* @param storageService use StorageService to preserve the DataStore. * @param storageService use StorageService to preserve the DataStore.
* @param modelMapper * @param modelMapper
*/ */
public constructor(private storageService: StorageService, private modelMapper: CollectionStringMapperService) {} public constructor(private storageService: StorageService, private modelMapper: CollectionStringMapperService) {
this.changeObservable.subscribe(model => {
this.primaryModelChangeSubject.next(model);
this.secondaryModelChangeSubject.next(model);
});
}
/** /**
* Gets the DataStore from cache and instantiate all models out of the serialized version. * Gets the DataStore from cache and instantiate all models out of the serialized version.
@ -170,7 +187,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 = this.modelMapper.getModelConstructor(collectionString); const target = this.modelMapper.getModelConstructorFromCollectionString(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]);
@ -201,7 +218,7 @@ export class DataStoreService {
if (typeof collectionType === 'string') { if (typeof collectionType === 'string') {
return collectionType; return collectionType;
} else { } else {
return this.modelMapper.getCollectionString(collectionType); return this.modelMapper.getCollectionStringFromModelConstructor(collectionType);
} }
} }

View File

@ -9,6 +9,10 @@ import { User } from '../../shared/models/users/user';
import { environment } from 'environments/environment'; import { environment } from 'environments/environment';
import { DataStoreService } from './data-store.service'; import { DataStoreService } from './data-store.service';
import { OfflineService } from './offline.service'; import { OfflineService } from './offline.service';
import { ViewUser } from 'app/site/users/models/view-user';
import { CollectionStringMapperService } from './collectionStringMapper.service';
import { OnAfterAppsLoaded } from '../onAfterAppsLoaded';
import { UserRepositoryService } from '../repositories/users/user-repository.service';
/** /**
* Permissions on the client are just strings. This makes clear, that * Permissions on the client are just strings. This makes clear, that
@ -36,12 +40,19 @@ export interface WhoAmIResponse {
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class OperatorService extends OpenSlidesComponent { export class OperatorService extends OpenSlidesComponent implements OnAfterAppsLoaded {
/** /**
* The operator. * The operator.
*/ */
private _user: User; private _user: User;
/**
* The operator as a view user. We need a separation here, because
* we need to acces the operators permissions, before we get data
* from the server to build the view user.
*/
private _viewUser: ViewUser;
/** /**
* Get the user that corresponds to operator. * Get the user that corresponds to operator.
*/ */
@ -49,14 +60,20 @@ export class OperatorService extends OpenSlidesComponent {
return this._user; return this._user;
} }
/**
* Get the user that corresponds to operator.
*/
public get viewUser(): ViewUser {
return this._viewUser;
}
/** /**
* Sets the current operator. * Sets the current operator.
* *
* The permissions are updated and the new user published. * The permissions are updated and the new user published.
*/ */
public set user(user: User) { public set user(user: User) {
this._user = user; this.updateUser(user);
this.updatePermissions();
} }
public get isAnonymous(): boolean { public get isAnonymous(): boolean {
@ -78,6 +95,16 @@ export class OperatorService extends OpenSlidesComponent {
*/ */
private operatorSubject: BehaviorSubject<User> = new BehaviorSubject<User>(null); private operatorSubject: BehaviorSubject<User> = new BehaviorSubject<User>(null);
/**
* Subject for the operator as a view user.
*/
private viewOperatorSubject: BehaviorSubject<ViewUser> = new BehaviorSubject<ViewUser>(null);
/**
* The user repository. Will be filled by the `onAfterAppsLoaded`.
*/
private userRepository: UserRepositoryService;
/** /**
* Sets up an observer for watching changes in the DS. If the operator user or groups are changed, * Sets up an observer for watching changes in the DS. If the operator user or groups are changed,
* the operator's permissions are updated. * the operator's permissions are updated.
@ -86,7 +113,12 @@ export class OperatorService extends OpenSlidesComponent {
* @param DS * @param DS
* @param offlineService * @param offlineService
*/ */
public constructor(private http: HttpClient, private DS: DataStoreService, private offlineService: OfflineService) { public constructor(
private http: HttpClient,
private DS: DataStoreService,
private offlineService: OfflineService,
private collectionStringMapperService: CollectionStringMapperService
) {
super(); super();
this.DS.changeObservable.subscribe(newModel => { this.DS.changeObservable.subscribe(newModel => {
@ -96,8 +128,7 @@ export class OperatorService extends OpenSlidesComponent {
} }
if (newModel instanceof User && this._user.id === newModel.id) { if (newModel instanceof User && this._user.id === newModel.id) {
this._user = newModel; this.updateUser(newModel);
this.updatePermissions();
} }
} else if (newModel instanceof Group && newModel.id === 1) { } else if (newModel instanceof Group && newModel.id === 1) {
// Group 1 (default) for anonymous changed // Group 1 (default) for anonymous changed
@ -106,6 +137,33 @@ export class OperatorService extends OpenSlidesComponent {
}); });
} }
/**
* Load the repo to get a view user.
*/
public onAfterAppsLoaded(): void {
this.userRepository = this.collectionStringMapperService.getRepositoryFromModelConstructor(
User
) as UserRepositoryService;
if (this.user) {
this._viewUser = this.userRepository.getViewModel(this.user.id);
}
}
/**
* Updates the user and update the permissions.
*
* @param user The user to set.
*/
private updateUser(user: User | null): void {
this._user = user;
if (user && this.userRepository) {
this._viewUser = this.userRepository.getViewModel(user.id);
} else {
this._viewUser = null;
}
this.updatePermissions();
}
/** /**
* Calls `/apps/users/whoami` to find out the real operator. * Calls `/apps/users/whoami` to find out the real operator.
* @returns The response of the WhoAmI request. * @returns The response of the WhoAmI request.
@ -131,10 +189,14 @@ export class OperatorService extends OpenSlidesComponent {
* Services an components can use it to get informed when something changes in * Services an components can use it to get informed when something changes in
* the operator * the operator
*/ */
public getObservable(): Observable<User> { public getUserObservable(): Observable<User> {
return this.operatorSubject.asObservable(); return this.operatorSubject.asObservable();
} }
public getViewUserObservable(): Observable<ViewUser> {
return this.viewOperatorSubject.asObservable();
}
/** /**
* Checks, if the operator has at least one of the given permissions. * Checks, if the operator has at least one of the given permissions.
* @param checkPerms The permissions to check, if at least one matches. * @param checkPerms The permissions to check, if at least one matches.
@ -193,5 +255,6 @@ export class OperatorService extends OpenSlidesComponent {
} }
// publish changes in the operator. // publish changes in the operator.
this.operatorSubject.next(this.user); this.operatorSubject.next(this.user);
this.viewOperatorSubject.next(this.viewUser);
} }
} }

View File

@ -17,6 +17,8 @@ import {
import { HttpService } from './http.service'; import { HttpService } from './http.service';
import { SlideManager } from 'app/slides/services/slide-manager.service'; import { SlideManager } from 'app/slides/services/slide-manager.service';
import { BaseModel } from 'app/shared/models/base/base-model'; import { BaseModel } from 'app/shared/models/base/base-model';
import { BaseViewModel } from 'app/site/base/base-view-model';
import { ViewModelStoreService } from './view-model-store.service';
/** /**
* This service cares about Projectables being projected and manage all projection-related * This service cares about Projectables being projected and manage all projection-related
@ -34,7 +36,12 @@ export class ProjectorService extends OpenSlidesComponent {
* @param DS * @param DS
* @param dataSend * @param dataSend
*/ */
public constructor(private DS: DataStoreService, private http: HttpService, private slideManager: SlideManager) { public constructor(
private DS: DataStoreService,
private http: HttpService,
private slideManager: SlideManager,
private viewModelStore: ViewModelStoreService
) {
super(); super();
} }
@ -222,13 +229,12 @@ export class ProjectorService extends OpenSlidesComponent {
} }
/** /**
* Returns a model associated with the identifiable projector element. Throws an error, * Asserts, that the given element is mappable to a model or view model.
* if the element is not mappable. * Throws an error, if this assertion fails.
* *
* @param element The projector element * @param element The element to check
* @returns the model from the projector element
*/ */
public getModelFromProjectorElement<T extends BaseModel>(element: IdentifiableProjectorElement): T { private assertElementIsMappable(element: IdentifiableProjectorElement): void {
if (!this.slideManager.canSlideBeMappedToModel(element.name)) { if (!this.slideManager.canSlideBeMappedToModel(element.name)) {
throw new Error('This projector element cannot be mapped to a model'); throw new Error('This projector element cannot be mapped to a model');
} }
@ -236,9 +242,32 @@ export class ProjectorService extends OpenSlidesComponent {
if (!identifiers.includes('name') || !identifiers.includes('id')) { if (!identifiers.includes('name') || !identifiers.includes('id')) {
throw new Error('To map this element to a model, a name and id is needed.'); throw new Error('To map this element to a model, a name and id is needed.');
} }
}
/**
* Returns a model associated with the identifiable projector element. Throws an error,
* if the element is not mappable.
*
* @param element The projector element
* @returns the model from the projector element
*/
public getModelFromProjectorElement<T extends BaseModel>(element: IdentifiableProjectorElement): T {
this.assertElementIsMappable(element);
return this.DS.get<T>(element.name, element.id); return this.DS.get<T>(element.name, element.id);
} }
/**
* Returns a view model associated with the identifiable projector element. Throws an error,
* if the element is not mappable.
*
* @param element The projector element
* @returns the view model from the projector element
*/
public getViewModelFromProjectorElement<T extends BaseViewModel>(element: IdentifiableProjectorElement): T {
this.assertElementIsMappable(element);
return this.viewModelStore.get<T>(element.name, element.id);
}
/** /**
* Projects the next slide in the queue. Moves all currently projected * Projects the next slide in the queue. Moves all currently projected
* non-stable slides to the history. * non-stable slides to the history.

View File

@ -68,7 +68,7 @@ export class TimeTravelService {
[collectionString, id] = historyObject.element_id.split(':'); [collectionString, id] = historyObject.element_id.split(':');
if (historyObject.full_data) { if (historyObject.full_data) {
const targetClass = this.modelMapperService.getModelConstructor(collectionString); const targetClass = this.modelMapperService.getModelConstructorFromCollectionString(collectionString);
await this.DS.add([new targetClass(historyObject.full_data)]); await this.DS.add([new targetClass(historyObject.full_data)]);
} else { } else {
await this.DS.remove(collectionString, [+id]); await this.DS.remove(collectionString, [+id]);

View File

@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { ViewModelStoreService } from './view-model-store.service';
import { E2EImportsModule } from '../../../e2e-imports.module';
describe('ViewModelStoreService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule],
providers: [ViewModelStoreService]
});
});
it('should be created', inject([ViewModelStoreService], (service: ViewModelStoreService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,90 @@
import { Injectable } from '@angular/core';
import { CollectionStringMapperService } from './collectionStringMapper.service';
import { BaseViewModel, ViewModelConstructor } from 'app/site/base/base-view-model';
import { BaseRepository } from '../repositories/base-repository';
/**
* This service takes care of handling view models.
*/
@Injectable({
providedIn: 'root'
})
export class ViewModelStoreService {
/**
* @param mapperService
*/
public constructor(private mapperService: CollectionStringMapperService) {}
/**
* gets the repository from a collection string or a view model constructor.
*
* @param collectionType The collection string or constructor.
*/
private getRepository<T extends BaseViewModel>(
collectionType: ViewModelConstructor<T> | string
): BaseRepository<T, any> {
if (typeof collectionType === 'string') {
return this.mapperService.getRepositoryFromCollectionString(collectionType) as BaseRepository<T, any>;
} else {
return this.mapperService.getRepositoryFromViewModelConstructor(collectionType as ViewModelConstructor<T>);
}
}
/**
* Returns the view model identified by the collectionString and id
*
* @param collectionString The collection of the view model
* @param id The id of the view model
*/
public get<T extends BaseViewModel>(collectionType: ViewModelConstructor<T> | string, id: number): T {
return this.getRepository(collectionType).getViewModel(id);
}
/**
* Returns all view models for the given ids.
*
* @param collectionType The collection of the view model
* @param ids All ids to match
*/
public getMany<T extends BaseViewModel>(collectionType: ViewModelConstructor<T> | string, ids: number[]): T[] {
const repository = this.getRepository<T>(collectionType);
return ids
.map(id => {
return repository.getViewModel(id);
})
.filter(model => !!model); // remove non valid models.
}
/**
* Gets all view models from a collection
*
* @param collectionString The collection
* @returns all models from the collection
*/
public getAll<T extends BaseViewModel>(collectionType: ViewModelConstructor<T> | string): T[] {
return this.getRepository(collectionType).getViewModelList();
}
/**
* Get all view modles from a collection, that satisfy the callback
*
* @param collectionString The collection
* @param callback The function to check
* @returns all matched view models of the collection
*/
public filter<T extends BaseViewModel>(collectionString: string, callback: (model: T) => boolean): T[] {
return this.getAll<T>(collectionString).filter(callback);
}
/**
* Finds one view model from the collection, that satifies the callback
*
* @param collectionString The collection
* @param callback THe callback to satisfy
* @returns a found view model or null, if nothing was found.
*/
public find<T extends BaseViewModel>(collectionString: string, callback: (model: T) => boolean): T {
return this.getAll<T>(collectionString).find(callback);
}
}

View File

@ -1,4 +1,4 @@
import { NgModule, Optional, SkipSelf } from '@angular/core'; import { NgModule, Optional, SkipSelf, Type } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
@ -6,6 +6,10 @@ import { Title } from '@angular/platform-browser';
import { PromptDialogComponent } from '../shared/components/prompt-dialog/prompt-dialog.component'; import { PromptDialogComponent } from '../shared/components/prompt-dialog/prompt-dialog.component';
import { ChoiceDialogComponent } from '../shared/components/choice-dialog/choice-dialog.component'; import { ChoiceDialogComponent } from '../shared/components/choice-dialog/choice-dialog.component';
import { ProjectionDialogComponent } from 'app/shared/components/projection-dialog/projection-dialog.component'; import { ProjectionDialogComponent } from 'app/shared/components/projection-dialog/projection-dialog.component';
import { OperatorService } from './core-services/operator.service';
import { OnAfterAppsLoaded } from './onAfterAppsLoaded';
export const ServicesToLoadOnAppsLoaded: Type<OnAfterAppsLoaded>[] = [OperatorService];
/** /**
* Global Core Module. * Global Core Module.

View File

@ -0,0 +1,9 @@
/**
* A lifecyclehook to be called, after all apps are loaded.
*/
export interface OnAfterAppsLoaded {
/**
* The hook to call
*/
onAfterAppsLoaded(): void;
}

View File

@ -1,17 +1,17 @@
import { TestBed, inject } from '@angular/core/testing'; import { TestBed, inject } from '@angular/core/testing';
import { AgendaRepositoryService } from './agenda-repository.service'; import { ItemRepositoryService } from './item-repository.service';
import { E2EImportsModule } from 'e2e-imports.module'; import { E2EImportsModule } from 'e2e-imports.module';
describe('AgendaRepositoryService', () => { describe('ItemRepositoryService', () => {
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [E2EImportsModule], imports: [E2EImportsModule],
providers: [AgendaRepositoryService] providers: [ItemRepositoryService]
}); });
}); });
it('should be created', inject([AgendaRepositoryService], (service: AgendaRepositoryService) => { it('should be created', inject([ItemRepositoryService], (service: ItemRepositoryService) => {
expect(service).toBeTruthy(); expect(service).toBeTruthy();
})); }));
}); });

View File

@ -3,8 +3,6 @@ import { tap, map } from 'rxjs/operators';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
import { AgendaBaseModel } from 'app/shared/models/base/agenda-base-model';
import { BaseModel } from 'app/shared/models/base/base-model';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service';
import { ConfigService } from 'app/core/ui-services/config.service'; import { ConfigService } from 'app/core/ui-services/config.service';
import { DataSendService } from 'app/core/core-services/data-send.service'; import { DataSendService } from 'app/core/core-services/data-send.service';
@ -14,10 +12,13 @@ import { Identifiable } from 'app/shared/models/base/identifiable';
import { Item } from 'app/shared/models/agenda/item'; import { Item } from 'app/shared/models/agenda/item';
import { OSTreeSortEvent } from 'app/shared/components/sorting-tree/sorting-tree.component'; import { OSTreeSortEvent } from 'app/shared/components/sorting-tree/sorting-tree.component';
import { Speaker } from 'app/shared/models/agenda/speaker'; import { Speaker } from 'app/shared/models/agenda/speaker';
import { User } from 'app/shared/models/users/user';
import { ViewItem } from 'app/site/agenda/models/view-item'; import { ViewItem } from 'app/site/agenda/models/view-item';
import { ViewSpeaker } from 'app/site/agenda/models/view-speaker'; import { ViewSpeaker } from 'app/site/agenda/models/view-speaker';
import { TreeService } from 'app/core/ui-services/tree.service'; import { TreeService } from 'app/core/ui-services/tree.service';
import { BaseAgendaViewModel } from 'app/site/base/base-agenda-view-model';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { BaseViewModel } from 'app/site/base/base-view-model';
import { ViewUser } from 'app/site/users/models/view-user';
/** /**
* Repository service for users * Repository service for users
@ -27,7 +28,7 @@ import { TreeService } from 'app/core/ui-services/tree.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class AgendaRepositoryService extends BaseRepository<ViewItem, Item> { export class ItemRepositoryService extends BaseRepository<ViewItem, Item> {
/** /**
* Contructor for agenda repository. * Contructor for agenda repository.
* *
@ -39,38 +40,39 @@ export class AgendaRepositoryService extends BaseRepository<ViewItem, Item> {
* @param treeService sort the data according to weight and parents * @param treeService sort the data according to weight and parents
*/ */
public constructor( public constructor(
protected DS: DataStoreService, DS: DataStoreService,
private httpService: HttpService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService,
private httpService: HttpService,
private config: ConfigService, private config: ConfigService,
private dataSend: DataSendService, private dataSend: DataSendService,
private treeService: TreeService private treeService: TreeService
) { ) {
super(DS, mapperService, Item); super(DS, mapperService, viewModelStoreService, Item);
} }
/** /**
* Returns the corresponding content object to a given {@link Item} as an {@link AgendaBaseModel} * Returns the corresponding content object to a given {@link Item} as an {@link AgendaBaseViewModel}
* Used dynamically because of heavy race conditions * Used dynamically because of heavy race conditions
* *
* @param agendaItem the target agenda Item * @param agendaItem the target agenda Item
* @returns the content object of the given item. Might be null if it was not found. * @returns the content object of the given item. Might be null if it was not found.
*/ */
public getContentObject(agendaItem: Item): AgendaBaseModel { public getContentObject(agendaItem: Item): BaseAgendaViewModel {
const contentObject = this.DS.get<BaseModel>( const contentObject = this.viewModelStoreService.get<BaseViewModel>(
agendaItem.content_object.collection, agendaItem.content_object.collection,
agendaItem.content_object.id agendaItem.content_object.id
); );
if (!contentObject) { if (!contentObject) {
return null; return null;
} }
if (contentObject instanceof AgendaBaseModel) { if (contentObject instanceof BaseAgendaViewModel) {
return contentObject as AgendaBaseModel; return contentObject as BaseAgendaViewModel;
} else { } else {
throw new Error( throw new Error(
`The content object (${agendaItem.content_object.collection}, ${ `The content object (${agendaItem.content_object.collection}, ${
agendaItem.content_object.id agendaItem.content_object.id
}) of item ${agendaItem.id} is not a AgendaBaseModel.` }) of item ${agendaItem.id} is not a AgendaBaseViewModel.`
); );
} }
} }
@ -86,7 +88,7 @@ export class AgendaRepositoryService extends BaseRepository<ViewItem, Item> {
const speakers = item.speakers; const speakers = item.speakers;
if (speakers && speakers.length > 0) { if (speakers && speakers.length > 0) {
speakers.forEach((speaker: Speaker) => { speakers.forEach((speaker: Speaker) => {
const user = this.DS.get(User, speaker.user_id); const user = this.viewModelStoreService.get(ViewUser, speaker.user_id);
viewSpeakers.push(new ViewSpeaker(speaker, user)); viewSpeakers.push(new ViewSpeaker(speaker, user));
}); });
} }

View File

@ -10,6 +10,9 @@ import { ViewTopic } from 'app/site/agenda/models/view-topic';
import { Identifiable } from 'app/shared/models/base/identifiable'; import { Identifiable } from 'app/shared/models/base/identifiable';
import { CollectionStringMapperService } from 'app/core/core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from 'app/core/core-services/collectionStringMapper.service';
import { CreateTopic } from 'app/site/agenda/models/create-topic'; import { CreateTopic } from 'app/site/agenda/models/create-topic';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
import { ViewItem } from 'app/site/agenda/models/view-item';
/** /**
* Repository for topics * Repository for topics
@ -28,9 +31,10 @@ export class TopicRepositoryService extends BaseRepository<ViewTopic, Topic> {
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService,
private dataSend: DataSendService private dataSend: DataSendService
) { ) {
super(DS, mapperService, Topic, [Mediafile, Item]); super(DS, mapperService, viewModelStoreService, Topic, [Mediafile, Item]);
} }
/** /**
@ -40,22 +44,11 @@ export class TopicRepositoryService extends BaseRepository<ViewTopic, Topic> {
* @returns a new view topic * @returns a new view topic
*/ */
public createViewModel(topic: Topic): ViewTopic { public createViewModel(topic: Topic): ViewTopic {
const attachments = this.DS.getMany(Mediafile, topic.attachments_id); const attachments = this.viewModelStoreService.getMany(ViewMediafile, topic.attachments_id);
const item = this.getAgendaItem(topic); const item = this.viewModelStoreService.get(ViewItem, topic.agenda_item_id);
return new ViewTopic(topic, attachments, item); return new ViewTopic(topic, attachments, item);
} }
/**
* Gets the corresponding agendaItem to the topic.
* Used to deal with race conditions
*
* @param topic the topic for the agenda item
* @returns an agenda item that fits for the topic
*/
public getAgendaItem(topic: Topic): Item {
return this.DS.get(Item, topic.agenda_item_id);
}
/** /**
* Save a new topic * Save a new topic
* *

View File

@ -8,6 +8,10 @@ import { BaseRepository } from '../base-repository';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';
import { Identifiable } from 'app/shared/models/base/identifiable'; import { Identifiable } from 'app/shared/models/base/identifiable';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { ViewItem } from 'app/site/agenda/models/view-item';
import { ViewUser } from 'app/site/users/models/view-user';
import { ViewTag } from 'app/site/tags/models/view-tag';
/** /**
* Repository Service for Assignments. * Repository Service for Assignments.
@ -24,8 +28,12 @@ export class AssignmentRepositoryService extends BaseRepository<ViewAssignment,
* @param DS The DataStore * @param DS The DataStore
* @param mapperService Maps collection strings to classes * @param mapperService Maps collection strings to classes
*/ */
public constructor(DS: DataStoreService, mapperService: CollectionStringMapperService) { public constructor(
super(DS, mapperService, Assignment, [User, Item, Tag]); DS: DataStoreService,
mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService
) {
super(DS, mapperService, viewModelStoreService, Assignment, [User, Item, Tag]);
} }
public async update(assignment: Partial<Assignment>, viewAssignment: ViewAssignment): Promise<void> { public async update(assignment: Partial<Assignment>, viewAssignment: ViewAssignment): Promise<void> {
@ -41,9 +49,9 @@ export class AssignmentRepositoryService extends BaseRepository<ViewAssignment,
} }
public createViewModel(assignment: Assignment): ViewAssignment { public createViewModel(assignment: Assignment): ViewAssignment {
const relatedUser = this.DS.getMany(User, assignment.candidateIds); const relatedUser = this.viewModelStoreService.getMany(ViewUser, assignment.candidateIds);
const agendaItem = this.DS.get(Item, assignment.agenda_item_id); const agendaItem = this.viewModelStoreService.get(ViewItem, assignment.agenda_item_id);
const tags = this.DS.getMany(Tag, assignment.tags_id); const tags = this.viewModelStoreService.getMany(ViewTag, assignment.tags_id);
return new ViewAssignment(assignment, relatedUser, agendaItem, tags); return new ViewAssignment(assignment, relatedUser, agendaItem, tags);
} }

View File

@ -7,8 +7,11 @@ import { CollectionStringMapperService } from '../core-services/collectionString
import { DataStoreService } from '../core-services/data-store.service'; import { DataStoreService } from '../core-services/data-store.service';
import { Identifiable } from '../../shared/models/base/identifiable'; import { Identifiable } from '../../shared/models/base/identifiable';
import { auditTime } from 'rxjs/operators'; import { auditTime } from 'rxjs/operators';
import { ViewModelStoreService } from '../core-services/view-model-store.service';
import { OnAfterAppsLoaded } from '../onAfterAppsLoaded';
export abstract class BaseRepository<V extends BaseViewModel, M extends BaseModel> extends OpenSlidesComponent { export abstract class BaseRepository<V extends BaseViewModel, M extends BaseModel> extends OpenSlidesComponent
implements OnAfterAppsLoaded {
/** /**
* Stores all the viewModel in an object * Stores all the viewModel in an object
*/ */
@ -29,26 +32,33 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
*/ */
protected readonly generalViewModelSubject: Subject<V> = new Subject<V>(); protected readonly generalViewModelSubject: Subject<V> = new Subject<V>();
private _name: string;
public get name(): string {
return this._name;
}
/** /**
* Construction routine for the base repository * Construction routine for the base repository
* *
* @param DS: The DataStore * @param DS: The DataStore
* @param collectionStringModelMapperService Mapping strings to their corresponding classes * @param collectionStringMapperService Mapping strings to their corresponding classes
* @param baseModelCtor The model constructor of which this repository is about. * @param baseModelCtor The model constructor of which this repository is about.
* @param depsModelCtors A list of constructors that are used in the view model. * @param depsModelCtors A list of constructors that are used in the view model.
* If one of those changes, the view models will be updated. * If one of those changes, the view models will be updated.
*/ */
public constructor( public constructor(
protected DS: DataStoreService, protected DS: DataStoreService,
protected collectionStringModelMapperService: CollectionStringMapperService, protected collectionStringMapperService: CollectionStringMapperService,
protected viewModelStoreService: ViewModelStoreService,
protected baseModelCtor: ModelConstructor<M>, protected baseModelCtor: ModelConstructor<M>,
protected depsModelCtors?: ModelConstructor<BaseModel>[] protected depsModelCtors?: ModelConstructor<BaseModel>[]
) { ) {
super(); super();
this.setup(); this._name = baseModelCtor.name;
} }
protected setup(): void { public onAfterAppsLoaded(): void {
// Populate the local viewModelStore with ViewModel Objects. // Populate the local viewModelStore with ViewModel Objects.
this.DS.getAll(this.baseModelCtor).forEach((model: M) => { this.DS.getAll(this.baseModelCtor).forEach((model: M) => {
this.viewModelStore[model.id] = this.createViewModel(model); this.viewModelStore[model.id] = this.createViewModel(model);
@ -60,28 +70,40 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
}); });
// Could be raise in error if the root injector is not known // Could be raise in error if the root injector is not known
this.DS.changeObservable.subscribe(model => { this.DS.primaryModelChangeSubject.subscribe(model => {
if (model instanceof this.baseModelCtor) { if (model instanceof this.baseModelCtor) {
// Add new and updated motions to the viewModelStore // Add new and updated motions to the viewModelStore
this.viewModelStore[model.id] = this.createViewModel(model as M); this.viewModelStore[model.id] = this.createViewModel(model as M);
this.updateAllObservables(model.id); this.updateAllObservables(model.id);
} else if (this.depsModelCtors) { }
});
if (this.depsModelCtors) {
this.DS.secondaryModelChangeSubject.subscribe(model => {
const dependencyChanged: boolean = this.depsModelCtors.some(ctor => { const dependencyChanged: boolean = this.depsModelCtors.some(ctor => {
return model instanceof ctor; return model instanceof ctor;
}); });
if (dependencyChanged) { if (dependencyChanged) {
const viewModel = this.viewModelStoreService.get(model.collectionString, model.id);
// if an domain object we need was added or changed, update viewModelStore // if an domain object we need was added or changed, update viewModelStore
this.getViewModelList().forEach(viewModel => { this.getViewModelList().forEach(ownViewModel => {
viewModel.updateValues(model); ownViewModel.updateDependencies(viewModel);
}); });
this.updateAllObservables(model.id); this.updateAllObservables(model.id);
} }
}
}); });
}
// Watch the Observables for deleting // Watch the Observables for deleting
// TODO: What happens, if some related object was deleted?
// My quess: This must trigger an autoupdate also for this model, because some IDs changed, so the
// affected models will be newly created by the primaryModelChangeSubject.
this.DS.deletedObservable.subscribe(model => { this.DS.deletedObservable.subscribe(model => {
if (model.collection === this.collectionStringModelMapperService.getCollectionString(this.baseModelCtor)) { if (
model.collection ===
this.collectionStringMapperService.getCollectionStringFromModelConstructor(this.baseModelCtor)
) {
delete this.viewModelStore[model.id]; delete this.viewModelStore[model.id];
this.updateAllObservables(model.id); this.updateAllObservables(model.id);
} }
@ -167,9 +189,9 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
protected updateViewModelObservable(id: number): void { protected updateViewModelObservable(id: number): void {
if (this.viewModelSubjects[id]) { if (this.viewModelSubjects[id]) {
this.viewModelSubjects[id].next(this.viewModelStore[id]); this.viewModelSubjects[id].next(this.viewModelStore[id]);
}
this.generalViewModelSubject.next(this.viewModelStore[id]); this.generalViewModelSubject.next(this.viewModelStore[id]);
} }
}
/** /**
* update the observable of the list * update the observable of the list

View File

@ -5,13 +5,18 @@ import { Identifiable } from 'app/shared/models/base/identifiable';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service';
import { ChatMessage } from 'app/shared/models/core/chat-message'; import { ChatMessage } from 'app/shared/models/core/chat-message';
import { ViewChatMessage } from 'app/site/common/models/view-chatmessage'; import { ViewChatMessage } from 'app/site/common/models/view-chatmessage';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class ChatMessageRepositoryService extends BaseRepository<ViewChatMessage, ChatMessage> { export class ChatMessageRepositoryService extends BaseRepository<ViewChatMessage, ChatMessage> {
public constructor(DS: DataStoreService, mapperService: CollectionStringMapperService) { public constructor(
super(DS, mapperService, ChatMessage); DS: DataStoreService,
mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService
) {
super(DS, mapperService, viewModelStoreService, ChatMessage);
} }
protected createViewModel(message: ChatMessage): ViewChatMessage { protected createViewModel(message: ChatMessage): ViewChatMessage {

View File

@ -1,14 +1,16 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { BaseRepository } from 'app/core/repositories/base-repository';
import { ViewConfig } from '../models/view-config';
import { Config } from 'app/shared/models/core/config';
import { Observable, BehaviorSubject } from 'rxjs'; import { Observable, BehaviorSubject } from 'rxjs';
import { BaseRepository } from 'app/core/repositories/base-repository';
import { Config } from 'app/shared/models/core/config';
import { DataStoreService } from 'app/core/core-services/data-store.service'; import { DataStoreService } from 'app/core/core-services/data-store.service';
import { ConstantsService } from 'app/core/ui-services/constants.service'; import { ConstantsService } from 'app/core/ui-services/constants.service';
import { HttpService } from 'app/core/core-services/http.service'; import { HttpService } from 'app/core/core-services/http.service';
import { Identifiable } from 'app/shared/models/base/identifiable'; import { Identifiable } from 'app/shared/models/base/identifiable';
import { CollectionStringMapperService } from 'app/core/core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from 'app/core/core-services/collectionStringMapper.service';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { ViewConfig } from 'app/site/config/models/view-config';
/** /**
* Holds a single config item. * Holds a single config item.
@ -95,10 +97,11 @@ export class ConfigRepositoryService extends BaseRepository<ViewConfig, Config>
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService,
private constantsService: ConstantsService, private constantsService: ConstantsService,
private http: HttpService private http: HttpService
) { ) {
super(DS, mapperService, Config); super(DS, mapperService, viewModelStoreService, Config);
this.constantsService.get('OpenSlidesConfigVariables').subscribe(constant => { this.constantsService.get('OpenSlidesConfigVariables').subscribe(constant => {
this.createConfigStructure(constant); this.createConfigStructure(constant);
@ -111,7 +114,7 @@ export class ConfigRepositoryService extends BaseRepository<ViewConfig, Config>
* Overwritten setup. Does only care about the custom list observable and inserts changed configs into the * Overwritten setup. Does only care about the custom list observable and inserts changed configs into the
* config group structure. * config group structure.
*/ */
protected setup(): void { public onAfterAppsLoaded(): void {
if (!this.configListSubject) { if (!this.configListSubject) {
this.configListSubject = new BehaviorSubject<ConfigGroup[]>(null); this.configListSubject = new BehaviorSubject<ConfigGroup[]>(null);
} }

View File

@ -9,7 +9,8 @@ import { Identifiable } from 'app/shared/models/base/identifiable';
import { HttpService } from 'app/core/core-services/http.service'; import { HttpService } from 'app/core/core-services/http.service';
import { ViewHistory } from 'app/site/history/models/view-history'; import { ViewHistory } from 'app/site/history/models/view-history';
import { TimeTravelService } from 'app/core/core-services/time-travel.service'; import { TimeTravelService } from 'app/core/core-services/time-travel.service';
import { BaseModel } from 'app/shared/models/base/base-model'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { ViewUser } from 'app/site/users/models/view-user';
/** /**
* Repository for the history. * Repository for the history.
@ -31,10 +32,11 @@ export class HistoryRepositoryService extends BaseRepository<ViewHistory, Histor
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService,
private httpService: HttpService, private httpService: HttpService,
private timeTravel: TimeTravelService private timeTravel: TimeTravelService
) { ) {
super(DS, mapperService, History, [User]); super(DS, mapperService, viewModelStoreService, History, [User]);
} }
/** /**
@ -70,12 +72,11 @@ export class HistoryRepositoryService extends BaseRepository<ViewHistory, Histor
* @returns the ListTitle or null if the model was deleted already * @returns the ListTitle or null if the model was deleted already
*/ */
public getOldModelInfo(collectionString: string, id: number): string { public getOldModelInfo(collectionString: string, id: number): string {
const oldModel: BaseModel = this.DS.get(collectionString, id); const model = this.viewModelStoreService.get(collectionString, id);
if (oldModel) { if (model) {
return oldModel.getListTitle(); return model.getListTitle();
} else {
return null;
} }
return null;
} }
/** /**
@ -85,7 +86,7 @@ export class HistoryRepositoryService extends BaseRepository<ViewHistory, Histor
* @return a new ViewHistory object * @return a new ViewHistory object
*/ */
public createViewModel(history: History): ViewHistory { public createViewModel(history: History): ViewHistory {
const user = this.DS.get(User, history.user_id); const user = this.viewModelStoreService.get(ViewUser, history.user_id);
return new ViewHistory(history, user); return new ViewHistory(history, user);
} }

View File

@ -10,6 +10,8 @@ import { CollectionStringMapperService } from '../../core-services/collectionStr
import { DataSendService } from 'app/core/core-services/data-send.service'; import { DataSendService } from 'app/core/core-services/data-send.service';
import { HttpService } from 'app/core/core-services/http.service'; import { HttpService } from 'app/core/core-services/http.service';
import { HttpHeaders } from '@angular/common/http'; import { HttpHeaders } from '@angular/common/http';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { ViewUser } from 'app/site/users/models/view-user';
/** /**
* Repository for MediaFiles * Repository for MediaFiles
@ -28,10 +30,11 @@ export class MediafileRepositoryService extends BaseRepository<ViewMediafile, Me
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService,
private dataSend: DataSendService, private dataSend: DataSendService,
private httpService: HttpService private httpService: HttpService
) { ) {
super(DS, mapperService, Mediafile, [User]); super(DS, mapperService, viewModelStoreService, Mediafile, [User]);
} }
/** /**
@ -88,7 +91,7 @@ export class MediafileRepositoryService extends BaseRepository<ViewMediafile, Me
* @returns a new mediafile ViewModel * @returns a new mediafile ViewModel
*/ */
public createViewModel(file: Mediafile): ViewMediafile { public createViewModel(file: Mediafile): ViewMediafile {
const uploader = this.DS.get(User, file.uploader_id); const uploader = this.viewModelStoreService.get(ViewUser, file.uploader_id);
return new ViewMediafile(file, uploader); return new ViewMediafile(file, uploader);
} }
} }

View File

@ -9,6 +9,7 @@ import { Motion } from 'app/shared/models/motions/motion';
import { HttpService } from '../../core-services/http.service'; import { HttpService } from '../../core-services/http.service';
import { Identifiable } from 'app/shared/models/base/identifiable'; import { Identifiable } from 'app/shared/models/base/identifiable';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
/** /**
* Repository Services for Categories * Repository Services for Categories
@ -37,10 +38,11 @@ export class CategoryRepositoryService extends BaseRepository<ViewCategory, Cate
public constructor( public constructor(
protected DS: DataStoreService, protected DS: DataStoreService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService,
private dataSend: DataSendService, private dataSend: DataSendService,
private httpService: HttpService private httpService: HttpService
) { ) {
super(DS, mapperService, Category); super(DS, mapperService, viewModelStoreService, Category);
} }
protected createViewModel(category: Category): ViewCategory { protected createViewModel(category: Category): ViewCategory {

View File

@ -3,16 +3,17 @@ import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { DataSendService } from '../../core-services/data-send.service'; import { DataSendService } from 'app/core/core-services/data-send.service';
import { User } from 'app/shared/models/users/user'; import { User } from 'app/shared/models/users/user';
import { Category } from 'app/shared/models/motions/category'; import { Category } from 'app/shared/models/motions/category';
import { Workflow } from 'app/shared/models/motions/workflow'; import { Workflow } from 'app/shared/models/motions/workflow';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from 'app/core/core-services/data-store.service';
import { MotionChangeReco } from 'app/shared/models/motions/motion-change-reco'; import { MotionChangeRecommendation } from 'app/shared/models/motions/motion-change-reco';
import { ViewChangeReco } from 'app/site/motions/models/view-change-reco'; import { ViewMotionChangeRecommendation } from 'app/site/motions/models/view-change-recommendation';
import { Identifiable } from 'app/shared/models/base/identifiable'; import { Identifiable } from 'app/shared/models/base/identifiable';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from 'app/core/core-services/collectionStringMapper.service';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
/** /**
* Repository Services for change recommendations * Repository Services for change recommendations
@ -27,7 +28,10 @@ import { CollectionStringMapperService } from '../../core-services/collectionStr
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class ChangeRecommendationRepositoryService extends BaseRepository<ViewChangeReco, MotionChangeReco> { export class ChangeRecommendationRepositoryService extends BaseRepository<
ViewMotionChangeRecommendation,
MotionChangeRecommendation
> {
/** /**
* Creates a MotionRepository * Creates a MotionRepository
* *
@ -41,18 +45,19 @@ export class ChangeRecommendationRepositoryService extends BaseRepository<ViewCh
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService,
private dataSend: DataSendService private dataSend: DataSendService
) { ) {
super(DS, mapperService, MotionChangeReco, [Category, User, Workflow]); super(DS, mapperService, viewModelStoreService, MotionChangeRecommendation, [Category, User, Workflow]);
} }
/** /**
* Creates a change recommendation * Creates a change recommendation
* Creates a (real) change recommendation and delegates it to the {@link DataSendService} * Creates a (real) change recommendation and delegates it to the {@link DataSendService}
* *
* @param {MotionChangeReco} changeReco * @param {MotionChangeRecommendation} changeReco
*/ */
public async create(changeReco: MotionChangeReco): Promise<Identifiable> { public async create(changeReco: MotionChangeRecommendation): Promise<Identifiable> {
return await this.dataSend.createModel(changeReco); return await this.dataSend.createModel(changeReco);
} }
@ -61,17 +66,17 @@ export class ChangeRecommendationRepositoryService extends BaseRepository<ViewCh
* @param view * @param view
* @returns The id of the created change recommendation * @returns The id of the created change recommendation
*/ */
public async createByViewModel(view: ViewChangeReco): Promise<Identifiable> { public async createByViewModel(view: ViewMotionChangeRecommendation): Promise<Identifiable> {
return await this.dataSend.createModel(view.changeRecommendation); return await this.dataSend.createModel(view.changeRecommendation);
} }
/** /**
* Creates this view wrapper based on an actual Change Recommendation model * Creates this view wrapper based on an actual Change Recommendation model
* *
* @param {MotionChangeReco} model * @param {MotionChangeRecommendation} model
*/ */
protected createViewModel(model: MotionChangeReco): ViewChangeReco { protected createViewModel(model: MotionChangeRecommendation): ViewMotionChangeRecommendation {
return new ViewChangeReco(model); return new ViewMotionChangeRecommendation(model);
} }
/** /**
@ -79,9 +84,9 @@ export class ChangeRecommendationRepositoryService extends BaseRepository<ViewCh
* *
* Extract the change recommendation out of the viewModel and delegate * Extract the change recommendation out of the viewModel and delegate
* to {@link DataSendService} * to {@link DataSendService}
* @param {ViewChangeReco} viewModel * @param {ViewMotionChangeRecommendation} viewModel
*/ */
public async delete(viewModel: ViewChangeReco): Promise<void> { public async delete(viewModel: ViewMotionChangeRecommendation): Promise<void> {
await this.dataSend.deleteModel(viewModel.changeRecommendation); await this.dataSend.deleteModel(viewModel.changeRecommendation);
} }
@ -91,10 +96,13 @@ export class ChangeRecommendationRepositoryService extends BaseRepository<ViewCh
* Updates a (real) change recommendation with patched data and delegate it * Updates a (real) change recommendation with patched data and delegate it
* to the {@link DataSendService} * to the {@link DataSendService}
* *
* @param {Partial<MotionChangeReco>} update the form data containing the update values * @param {Partial<MotionChangeRecommendation>} update the form data containing the update values
* @param {ViewChangeReco} viewModel The View Change Recommendation. If not present, a new motion will be created * @param {ViewMotionChangeRecommendation} viewModel The View Change Recommendation. If not present, a new motion will be created
*/ */
public async update(update: Partial<MotionChangeReco>, viewModel: ViewChangeReco): Promise<void> { public async update(
update: Partial<MotionChangeRecommendation>,
viewModel: ViewMotionChangeRecommendation
): Promise<void> {
const changeReco = viewModel.changeRecommendation; const changeReco = viewModel.changeRecommendation;
changeReco.patchValues(update); changeReco.patchValues(update);
await this.dataSend.partialUpdateModel(changeReco); await this.dataSend.partialUpdateModel(changeReco);
@ -103,9 +111,9 @@ export class ChangeRecommendationRepositoryService extends BaseRepository<ViewCh
/** /**
* return the Observable of all change recommendations belonging to the given motion * return the Observable of all change recommendations belonging to the given motion
*/ */
public getChangeRecosOfMotionObservable(motion_id: number): Observable<ViewChangeReco[]> { public getChangeRecosOfMotionObservable(motion_id: number): Observable<ViewMotionChangeRecommendation[]> {
return this.viewModelListSubject.asObservable().pipe( return this.viewModelListSubject.asObservable().pipe(
map((recos: ViewChangeReco[]) => { map((recos: ViewMotionChangeRecommendation[]) => {
return recos.filter(reco => reco.motion_id === motion_id); return recos.filter(reco => reco.motion_id === motion_id);
}) })
); );
@ -117,16 +125,16 @@ export class ChangeRecommendationRepositoryService extends BaseRepository<ViewCh
* @param motionId the id of the target motion * @param motionId the id of the target motion
* @returns the array of change recommendations to the motions. * @returns the array of change recommendations to the motions.
*/ */
public getChangeRecoOfMotion(motion_id: number): ViewChangeReco[] { public getChangeRecoOfMotion(motion_id: number): ViewMotionChangeRecommendation[] {
return this.getViewModelList().filter(reco => reco.motion_id === motion_id); return this.getViewModelList().filter(reco => reco.motion_id === motion_id);
} }
/** /**
* Sets a change recommendation to accepted. * Sets a change recommendation to accepted.
* *
* @param {ViewChangeReco} change * @param {ViewMotionChangeRecommendation} change
*/ */
public async setAccepted(change: ViewChangeReco): Promise<void> { public async setAccepted(change: ViewMotionChangeRecommendation): Promise<void> {
const changeReco = change.changeRecommendation; const changeReco = change.changeRecommendation;
changeReco.patchValues({ changeReco.patchValues({
rejected: false rejected: false
@ -137,9 +145,9 @@ export class ChangeRecommendationRepositoryService extends BaseRepository<ViewCh
/** /**
* Sets a change recommendation to rejected. * Sets a change recommendation to rejected.
* *
* @param {ViewChangeReco} change * @param {ViewMotionChangeRecommendation} change
*/ */
public async setRejected(change: ViewChangeReco): Promise<void> { public async setRejected(change: ViewMotionChangeRecommendation): Promise<void> {
const changeReco = change.changeRecommendation; const changeReco = change.changeRecommendation;
changeReco.patchValues({ changeReco.patchValues({
rejected: true rejected: true
@ -150,10 +158,10 @@ export class ChangeRecommendationRepositoryService extends BaseRepository<ViewCh
/** /**
* Sets if a change recommendation is internal (for the administrators) or not. * Sets if a change recommendation is internal (for the administrators) or not.
* *
* @param {ViewChangeReco} change * @param {ViewMotionChangeRecommendation} change
* @param {boolean} internal * @param {boolean} internal
*/ */
public async setInternal(change: ViewChangeReco, internal: boolean): Promise<void> { public async setInternal(change: ViewMotionChangeRecommendation, internal: boolean): Promise<void> {
const changeReco = change.changeRecommendation; const changeReco = change.changeRecommendation;
changeReco.patchValues({ changeReco.patchValues({
internal: internal internal: internal

View File

@ -14,6 +14,9 @@ import { MotionBlock } from 'app/shared/models/motions/motion-block';
import { MotionRepositoryService } from './motion-repository.service'; import { MotionRepositoryService } from './motion-repository.service';
import { ViewMotion } from 'app/site/motions/models/view-motion'; import { ViewMotion } from 'app/site/motions/models/view-motion';
import { ViewMotionBlock } from 'app/site/motions/models/view-motion-block'; import { ViewMotionBlock } from 'app/site/motions/models/view-motion-block';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { Item } from 'app/shared/models/agenda/item';
import { ViewItem } from 'app/site/agenda/models/view-item';
/** /**
* Repository service for motion blocks * Repository service for motion blocks
@ -34,11 +37,12 @@ export class MotionBlockRepositoryService extends BaseRepository<ViewMotionBlock
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService,
private dataSend: DataSendService, private dataSend: DataSendService,
private motionRepo: MotionRepositoryService, private motionRepo: MotionRepositoryService,
private httpService: HttpService private httpService: HttpService
) { ) {
super(DS, mapperService, MotionBlock); super(DS, mapperService, viewModelStoreService, MotionBlock, [Item]);
} }
/** /**
@ -80,7 +84,8 @@ export class MotionBlockRepositoryService extends BaseRepository<ViewMotionBlock
* @returns a new ViewMotionBlock * @returns a new ViewMotionBlock
*/ */
protected createViewModel(block: MotionBlock): ViewMotionBlock { protected createViewModel(block: MotionBlock): ViewMotionBlock {
return new ViewMotionBlock(block); const item = this.viewModelStoreService.get(ViewItem, block.agenda_item_id);
return new ViewMotionBlock(block, item);
} }
/** /**

View File

@ -9,6 +9,8 @@ import { Group } from 'app/shared/models/users/group';
import { Identifiable } from 'app/shared/models/base/identifiable'; import { Identifiable } from 'app/shared/models/base/identifiable';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service';
import { HttpService } from 'app/core/core-services/http.service'; import { HttpService } from 'app/core/core-services/http.service';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { ViewGroup } from 'app/site/users/models/view-group';
/** /**
* Repository Services for Categories * Repository Services for Categories
@ -38,12 +40,13 @@ export class MotionCommentSectionRepositoryService extends BaseRepository<
* @param http Service to handle direct http-communication * @param http Service to handle direct http-communication
*/ */
public constructor( public constructor(
DS: DataStoreService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
protected DS: DataStoreService, viewModelStoreService: ViewModelStoreService,
private dataSend: DataSendService, private dataSend: DataSendService,
private http: HttpService private http: HttpService
) { ) {
super(DS, mapperService, MotionCommentSection, [Group]); super(DS, mapperService, viewModelStoreService, MotionCommentSection, [Group]);
} }
/** /**
@ -53,9 +56,9 @@ export class MotionCommentSectionRepositoryService extends BaseRepository<
* @returns the View Model representation of the MotionCommentSection * @returns the View Model representation of the MotionCommentSection
*/ */
protected createViewModel(section: MotionCommentSection): ViewMotionCommentSection { protected createViewModel(section: MotionCommentSection): ViewMotionCommentSection {
const read_groups = this.DS.getMany(Group, section.read_groups_id); const readGroups = this.viewModelStoreService.getMany(ViewGroup, section.read_groups_id);
const write_groups = this.DS.getMany(Group, section.write_groups_id); const writeGroups = this.viewModelStoreService.getMany(ViewGroup, section.write_groups_id);
return new ViewMotionCommentSection(section, read_groups, write_groups); return new ViewMotionCommentSection(section, readGroups, writeGroups);
} }
/** /**

View File

@ -19,19 +19,27 @@ import { LinenumberingService } from '../../ui-services/linenumbering.service';
import { Mediafile } from 'app/shared/models/mediafiles/mediafile'; import { Mediafile } from 'app/shared/models/mediafiles/mediafile';
import { Motion } from 'app/shared/models/motions/motion'; import { Motion } from 'app/shared/models/motions/motion';
import { MotionBlock } from 'app/shared/models/motions/motion-block'; import { MotionBlock } from 'app/shared/models/motions/motion-block';
import { MotionChangeReco } from 'app/shared/models/motions/motion-change-reco'; import { MotionChangeRecommendation } from 'app/shared/models/motions/motion-change-reco';
import { MotionPoll } from 'app/shared/models/motions/motion-poll'; import { MotionPoll } from 'app/shared/models/motions/motion-poll';
import { OSTreeSortEvent } from 'app/shared/components/sorting-tree/sorting-tree.component'; import { OSTreeSortEvent } from 'app/shared/components/sorting-tree/sorting-tree.component';
import { PersonalNoteService } from '../../ui-services/personal-note.service'; import { PersonalNoteService } from '../../ui-services/personal-note.service';
import { TreeService } from 'app/core/ui-services/tree.service'; import { TreeService } from 'app/core/ui-services/tree.service';
import { User } from '../../../shared/models/users/user'; import { User } from 'app/shared/models/users/user';
import { ViewChangeReco } from '../../../site/motions/models/view-change-reco'; import { ViewMotionChangeRecommendation } from 'app/site/motions/models/view-change-recommendation';
import { ViewMotionAmendedParagraph } from '../../../site/motions/models/view-motion-amended-paragraph'; import { ViewMotionAmendedParagraph } from 'app/site/motions/models/view-motion-amended-paragraph';
import { ViewUnifiedChange } from '../../../site/motions/models/view-unified-change'; import { ViewUnifiedChange } from 'app/site/motions/models/view-unified-change';
import { ViewStatuteParagraph } from '../../../site/motions/models/view-statute-paragraph'; import { ViewStatuteParagraph } from 'app/site/motions/models/view-statute-paragraph';
import { Workflow } from '../../../shared/models/motions/workflow'; import { Workflow } from 'app/shared/models/motions/workflow';
import { WorkflowState } from '../../../shared/models/motions/workflow-state'; import { WorkflowState } from 'app/shared/models/motions/workflow-state';
import { Tag } from 'app/shared/models/core/tag'; import { Tag } from 'app/shared/models/core/tag';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { ViewCategory } from 'app/site/motions/models/view-category';
import { ViewUser } from 'app/site/users/models/view-user';
import { ViewWorkflow } from 'app/site/motions/models/view-workflow';
import { ViewItem } from 'app/site/agenda/models/view-item';
import { ViewMotionBlock } from 'app/site/motions/models/view-motion-block';
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
import { ViewTag } from 'app/site/tags/models/view-tag';
/** /**
* Repository Services for motions (and potentially categories) * Repository Services for motions (and potentially categories)
@ -64,6 +72,7 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService,
private dataSend: DataSendService, private dataSend: DataSendService,
private httpService: HttpService, private httpService: HttpService,
private readonly lineNumbering: LinenumberingService, private readonly lineNumbering: LinenumberingService,
@ -72,7 +81,15 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
private personalNoteService: PersonalNoteService, private personalNoteService: PersonalNoteService,
private translate: TranslateService private translate: TranslateService
) { ) {
super(DS, mapperService, Motion, [Category, User, Workflow, Item, MotionBlock, Mediafile, Tag]); super(DS, mapperService, viewModelStoreService, Motion, [
Category,
User,
Workflow,
Item,
MotionBlock,
Mediafile,
Tag
]);
} }
/** /**
@ -84,15 +101,15 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
* @param motion blank motion domain object * @param motion blank motion domain object
*/ */
protected createViewModel(motion: Motion): ViewMotion { protected createViewModel(motion: Motion): ViewMotion {
const category = this.DS.get(Category, motion.category_id); const category = this.viewModelStoreService.get(ViewCategory, motion.category_id);
const submitters = this.DS.getMany(User, motion.submitterIds); const submitters = this.viewModelStoreService.getMany(ViewUser, motion.submitterIds);
const supporters = this.DS.getMany(User, motion.supporters_id); const supporters = this.viewModelStoreService.getMany(ViewUser, motion.supporters_id);
const workflow = this.DS.get(Workflow, motion.workflow_id); const workflow = this.viewModelStoreService.get(ViewWorkflow, motion.workflow_id);
const item = this.DS.get(Item, motion.agenda_item_id); const item = this.viewModelStoreService.get(ViewItem, motion.agenda_item_id);
const block = this.DS.get(MotionBlock, motion.motion_block_id); const block = this.viewModelStoreService.get(ViewMotionBlock, motion.motion_block_id);
const attachments = this.DS.getMany(Mediafile, motion.attachments_id); const attachments = this.viewModelStoreService.getMany(ViewMediafile, motion.attachments_id);
const tags = this.DS.getMany(Tag, motion.tags_id); const tags = this.viewModelStoreService.getMany(ViewTag, motion.tags_id);
const parent = this.DS.get(Motion, motion.parent_id); const parent = this.viewModelStoreService.get(ViewMotion, motion.parent_id);
let state: WorkflowState = null; let state: WorkflowState = null;
if (workflow) { if (workflow) {
state = workflow.getStateById(motion.state_id); state = workflow.getStateById(motion.state_id);
@ -260,7 +277,7 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
* @param viewMotion The motion to change the submitters from * @param viewMotion The motion to change the submitters from
* @param submitters The submitters to set * @param submitters The submitters to set
*/ */
public async setSubmitters(viewMotion: ViewMotion, submitters: User[]): Promise<void> { public async setSubmitters(viewMotion: ViewMotion, submitters: ViewUser[]): Promise<void> {
const requestData = { const requestData = {
motions: [ motions: [
{ {
@ -525,8 +542,8 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
motionId: number, motionId: number,
lineRange: LineRange, lineRange: LineRange,
lineLength: number lineLength: number
): ViewChangeReco { ): ViewMotionChangeRecommendation {
const changeReco = new MotionChangeReco(); const changeReco = new MotionChangeRecommendation();
changeReco.line_from = lineRange.from; changeReco.line_from = lineRange.from;
changeReco.line_to = lineRange.to; changeReco.line_to = lineRange.to;
changeReco.type = ModificationType.TYPE_REPLACEMENT; changeReco.type = ModificationType.TYPE_REPLACEMENT;
@ -534,7 +551,7 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
changeReco.rejected = false; changeReco.rejected = false;
changeReco.motion_id = motionId; changeReco.motion_id = motionId;
return new ViewChangeReco(changeReco); return new ViewMotionChangeRecommendation(changeReco);
} }
/** /**

View File

@ -7,6 +7,7 @@ import { ViewStatuteParagraph } from 'app/site/motions/models/view-statute-parag
import { StatuteParagraph } from 'app/shared/models/motions/statute-paragraph'; import { StatuteParagraph } from 'app/shared/models/motions/statute-paragraph';
import { Identifiable } from 'app/shared/models/base/identifiable'; import { Identifiable } from 'app/shared/models/base/identifiable';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
/** /**
* Repository Services for statute paragraphs * Repository Services for statute paragraphs
@ -31,9 +32,10 @@ export class StatuteParagraphRepositoryService extends BaseRepository<ViewStatut
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService,
private dataSend: DataSendService private dataSend: DataSendService
) { ) {
super(DS, mapperService, StatuteParagraph); super(DS, mapperService, viewModelStoreService, StatuteParagraph);
} }
protected createViewModel(statuteParagraph: StatuteParagraph): ViewStatuteParagraph { protected createViewModel(statuteParagraph: StatuteParagraph): ViewStatuteParagraph {

View File

@ -10,6 +10,7 @@ import { CollectionStringMapperService } from '../../core-services/collectionStr
import { WorkflowState } from 'app/shared/models/motions/workflow-state'; import { WorkflowState } from 'app/shared/models/motions/workflow-state';
import { ViewMotion } from 'app/site/motions/models/view-motion'; import { ViewMotion } from 'app/site/motions/models/view-motion';
import { HttpService } from 'app/core/core-services/http.service'; import { HttpService } from 'app/core/core-services/http.service';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
/** /**
* Repository Services for Categories * Repository Services for Categories
@ -40,12 +41,13 @@ export class WorkflowRepositoryService extends BaseRepository<ViewWorkflow, Work
* @param httpService HttpService * @param httpService HttpService
*/ */
public constructor( public constructor(
protected DS: DataStoreService, DS: DataStoreService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
private dataSend: DataSendService, private httpService: HttpService,
private httpService: HttpService viewModelStoreService: ViewModelStoreService,
private dataSend: DataSendService
) { ) {
super(DS, mapperService, Workflow); super(DS, mapperService, viewModelStoreService, Workflow);
} }
/** /**

View File

@ -6,6 +6,7 @@ import { Identifiable } from 'app/shared/models/base/identifiable';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service';
import { ViewCountdown } from 'app/site/projector/models/view-countdown'; import { ViewCountdown } from 'app/site/projector/models/view-countdown';
import { Countdown } from 'app/shared/models/core/countdown'; import { Countdown } from 'app/shared/models/core/countdown';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -14,9 +15,10 @@ export class CountdownRepositoryService extends BaseRepository<ViewCountdown, Co
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService,
private dataSend: DataSendService private dataSend: DataSendService
) { ) {
super(DS, mapperService, Countdown); super(DS, mapperService, viewModelStoreService, Countdown);
} }
protected createViewModel(countdown: Countdown): ViewCountdown { protected createViewModel(countdown: Countdown): ViewCountdown {

View File

@ -8,6 +8,7 @@ import { Identifiable } from 'app/shared/models/base/identifiable';
import { ViewProjector } from 'app/site/projector/models/view-projector'; import { ViewProjector } from 'app/site/projector/models/view-projector';
import { Projector } from 'app/shared/models/core/projector'; import { Projector } from 'app/shared/models/core/projector';
import { HttpService } from 'app/core/core-services/http.service'; import { HttpService } from 'app/core/core-services/http.service';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
/** /**
* Directions for scale and scroll requests. * Directions for scale and scroll requests.
@ -36,10 +37,11 @@ export class ProjectorRepositoryService extends BaseRepository<ViewProjector, Pr
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService,
private dataSend: DataSendService, private dataSend: DataSendService,
private http: HttpService private http: HttpService
) { ) {
super(DS, mapperService, Projector); super(DS, mapperService, viewModelStoreService, Projector);
} }
/** /**

View File

@ -5,13 +5,18 @@ import { Identifiable } from 'app/shared/models/base/identifiable';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service';
import { ProjectorMessage } from 'app/shared/models/core/projector-message'; import { ProjectorMessage } from 'app/shared/models/core/projector-message';
import { ViewProjectorMessage } from 'app/site/projector/models/view-projectormessage'; import { ViewProjectorMessage } from 'app/site/projector/models/view-projectormessage';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class ProjectorMessageRepositoryService extends BaseRepository<ViewProjectorMessage, ProjectorMessage> { export class ProjectorMessageRepositoryService extends BaseRepository<ViewProjectorMessage, ProjectorMessage> {
public constructor(DS: DataStoreService, mapperService: CollectionStringMapperService) { public constructor(
super(DS, mapperService, ProjectorMessage); DS: DataStoreService,
mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService
) {
super(DS, mapperService, viewModelStoreService, ProjectorMessage);
} }
protected createViewModel(message: ProjectorMessage): ViewProjectorMessage { protected createViewModel(message: ProjectorMessage): ViewProjectorMessage {

View File

@ -7,6 +7,7 @@ import { DataStoreService } from '../../core-services/data-store.service';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
import { Identifiable } from 'app/shared/models/base/identifiable'; import { Identifiable } from 'app/shared/models/base/identifiable';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
/** /**
* Repository Services for Tags * Repository Services for Tags
@ -34,9 +35,10 @@ export class TagRepositoryService extends BaseRepository<ViewTag, Tag> {
public constructor( public constructor(
protected DS: DataStoreService, protected DS: DataStoreService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService,
private dataSend: DataSendService private dataSend: DataSendService
) { ) {
super(DS, mapperService, Tag); super(DS, mapperService, viewModelStoreService, Tag);
} }
protected createViewModel(tag: Tag): ViewTag { protected createViewModel(tag: Tag): ViewTag {

View File

@ -8,6 +8,7 @@ import { DataStoreService } from '../../core-services/data-store.service';
import { Group } from 'app/shared/models/users/group'; import { Group } from 'app/shared/models/users/group';
import { Identifiable } from 'app/shared/models/base/identifiable'; import { Identifiable } from 'app/shared/models/base/identifiable';
import { ViewGroup } from 'app/site/users/models/view-group'; import { ViewGroup } from 'app/site/users/models/view-group';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
/** /**
* Shape of a permission * Shape of a permission
@ -49,10 +50,11 @@ export class GroupRepositoryService extends BaseRepository<ViewGroup, Group> {
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService,
private dataSend: DataSendService, private dataSend: DataSendService,
private constants: ConstantsService private constants: ConstantsService
) { ) {
super(DS, mapperService, Group); super(DS, mapperService, viewModelStoreService, Group);
this.sortPermsPerApp(); this.sortPermsPerApp();
} }

View File

@ -5,34 +5,40 @@ import { BaseRepository } from '../base-repository';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service';
import { PersonalNote } from 'app/shared/models/users/personal-note'; import { PersonalNote } from 'app/shared/models/users/personal-note';
import { Identifiable } from 'app/shared/models/base/identifiable'; import { Identifiable } from 'app/shared/models/base/identifiable';
import { ViewPersonalNote } from 'app/site/users/models/view-personal-note';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
/** /**
*/ */
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class PersonalNoteRepositoryService extends BaseRepository<any, PersonalNote> { export class PersonalNoteRepositoryService extends BaseRepository<ViewPersonalNote, PersonalNote> {
/** /**
* @param DS The DataStore * @param DS The DataStore
* @param mapperService Maps collection strings to classes * @param mapperService Maps collection strings to classes
*/ */
public constructor(protected DS: DataStoreService, mapperService: CollectionStringMapperService) { public constructor(
super(DS, mapperService, PersonalNote); DS: DataStoreService,
mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService
) {
super(DS, mapperService, viewModelStoreService, PersonalNote);
} }
protected createViewModel(personalNote: PersonalNote): any { protected createViewModel(personalNote: PersonalNote): ViewPersonalNote {
return {}; return new ViewPersonalNote();
} }
public async create(personalNote: PersonalNote): Promise<Identifiable> { public async create(personalNote: PersonalNote): Promise<Identifiable> {
throw new Error('TODO'); throw new Error('TODO');
} }
public async update(personalNote: Partial<PersonalNote>, viewPersonalNote: any): Promise<void> { public async update(personalNote: Partial<PersonalNote>, viewPersonalNote: ViewPersonalNote): Promise<void> {
throw new Error('TODO'); throw new Error('TODO');
} }
public async delete(viewPersonalNote: any): Promise<void> { public async delete(viewPersonalNote: ViewPersonalNote): Promise<void> {
throw new Error('TODO'); throw new Error('TODO');
} }
} }

View File

@ -12,6 +12,8 @@ import { ConfigService } from 'app/core/ui-services/config.service';
import { HttpService } from 'app/core/core-services/http.service'; import { HttpService } from 'app/core/core-services/http.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { environment } from '../../../../environments/environment'; import { environment } from '../../../../environments/environment';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { ViewGroup } from 'app/site/users/models/view-group';
/** /**
* type for determining the user name from a string during import. * type for determining the user name from a string during import.
@ -38,12 +40,13 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService,
private dataSend: DataSendService, private dataSend: DataSendService,
private translate: TranslateService, private translate: TranslateService,
private httpService: HttpService, private httpService: HttpService,
private configService: ConfigService private configService: ConfigService
) { ) {
super(DS, mapperService, User, [Group]); super(DS, mapperService, viewModelStoreService, User, [Group]);
} }
/** /**
@ -104,7 +107,7 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {
} }
public createViewModel(user: User): ViewUser { public createViewModel(user: User): ViewUser {
const groups = this.DS.getMany(Group, user.groups_id); const groups = this.viewModelStoreService.getMany(ViewGroup, user.groups_id);
return new ViewUser(user, groups); return new ViewUser(user, groups);
} }
@ -218,20 +221,9 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {
* @returns all users matching that name * @returns all users matching that name
*/ */
public getUsersByName(name: string): ViewUser[] { public getUsersByName(name: string): ViewUser[] {
const results: ViewUser[] = []; return this.getViewModelList().filter(user => {
const users = this.DS.getAll(User).filter(user => { return user.full_name === name || user.short_name === name || user.number === name;
if (user.full_name === name || user.short_name === name) {
return true;
}
if (user.number === name) {
return true;
}
return false;
}); });
users.forEach(user => {
results.push(this.createViewModel(user));
});
return results;
} }
/** /**
@ -241,7 +233,7 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {
* @returns all users matching that number * @returns all users matching that number
*/ */
public getUsersByNumber(number: string): ViewUser[] { public getUsersByNumber(number: string): ViewUser[] {
return this.getViewModelList().filter(user => user.participant_number === number); return this.getViewModelList().filter(user => user.number === number);
} }
/** /**

View File

@ -61,7 +61,7 @@ export class CountUsersService extends OpenSlidesComponent {
}); });
// Look for the current user. // Look for the current user.
operator.getObservable().subscribe(user => (this.currentUserId = user ? user.id : null)); operator.getUserObservable().subscribe(user => (this.currentUserId = user ? user.id : null));
} }
/** /**

View File

@ -41,7 +41,7 @@ export class PersonalNoteService {
* Watches for changes in the personal note model. * Watches for changes in the personal note model.
*/ */
public constructor(private operator: OperatorService, private DS: DataStoreService, private http: HttpService) { public constructor(private operator: OperatorService, private DS: DataStoreService, private http: HttpService) {
operator.getObservable().subscribe(() => this.updatePersonalNoteObject()); operator.getUserObservable().subscribe(() => this.updatePersonalNoteObject());
this.DS.changeObservable.subscribe(model => { this.DS.changeObservable.subscribe(model => {
if (model instanceof PersonalNote) { if (model instanceof PersonalNote) {
this.updatePersonalNoteObject(); this.updatePersonalNoteObject();

View File

@ -1,7 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { BaseModel } from '../../shared/models/base/base-model'; import { Searchable } from '../../site/base/searchable';
import { DataStoreService } from '../core-services/data-store.service'; import { BaseViewModel } from 'app/site/base/base-view-model';
import { Searchable } from '../../shared/models/base/searchable';
/** /**
* The representation every searchable model should use to represent their data. * The representation every searchable model should use to represent their data.
@ -46,7 +45,7 @@ export interface SearchResult {
/** /**
* All matched models. * All matched models.
*/ */
models: (BaseModel & Searchable)[]; models: (BaseViewModel & Searchable)[];
} }
/** /**
@ -61,7 +60,6 @@ export class SearchService {
*/ */
private searchModels: { private searchModels: {
collectionString: string; collectionString: string;
ctor: new (...args: any[]) => Searchable & BaseModel;
verboseNameSingular: string; verboseNameSingular: string;
verboseNamePlural: string; verboseNamePlural: string;
displayOrder: number; displayOrder: number;
@ -70,7 +68,7 @@ export class SearchService {
/** /**
* @param DS The DataStore to search in. * @param DS The DataStore to search in.
*/ */
public constructor(private DS: DataStoreService) {} public constructor() {}
/** /**
* Registers a model by the given attributes. * Registers a model by the given attributes.
@ -81,13 +79,12 @@ export class SearchService {
*/ */
public registerModel( public registerModel(
collectionString: string, collectionString: string,
ctor: new (...args: any[]) => Searchable & BaseModel, ctor: new (...args: any[]) => Searchable & BaseViewModel,
displayOrder: number displayOrder: number
): void { ): void {
const instance = new ctor(); const instance = new ctor();
this.searchModels.push({ this.searchModels.push({
collectionString: collectionString, collectionString: collectionString,
ctor: ctor,
verboseNameSingular: instance.getVerboseName(), verboseNameSingular: instance.getVerboseName(),
verboseNamePlural: instance.getVerboseName(true), verboseNamePlural: instance.getVerboseName(true),
displayOrder: displayOrder displayOrder: displayOrder
@ -115,10 +112,10 @@ export class SearchService {
*/ */
public search(query: string, inCollectionStrings: string[]): SearchResult[] { public search(query: string, inCollectionStrings: string[]): SearchResult[] {
query = query.toLowerCase(); query = query.toLowerCase();
return this.searchModels /*return this.searchModels
.filter(s => inCollectionStrings.includes(s.collectionString)) .filter(s => inCollectionStrings.includes(s.collectionString))
.map(searchModel => { .map(searchModel => {
const results = this.DS.filter(searchModel.ctor, model => const results = this.viewModelStore.filter(searchModel.collectionString, model =>
model.formatForSearch().some(text => text.toLowerCase().includes(query)) model.formatForSearch().some(text => text.toLowerCase().includes(query))
); );
return { return {
@ -126,6 +123,8 @@ export class SearchService {
verboseName: results.length === 1 ? searchModel.verboseNameSingular : searchModel.verboseNamePlural, verboseName: results.length === 1 ? searchModel.verboseNameSingular : searchModel.verboseNamePlural,
models: results models: results
}; };
}); });*/
throw new Error('Todo');
return [];
} }
} }

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { OpenSlidesComponent } from 'app/openslides.component'; import { OpenSlidesComponent } from 'app/openslides.component';
import { Displayable } from 'app/shared/models/base/displayable'; import { Displayable } from 'app/site/base/displayable';
import { Identifiable } from 'app/shared/models/base/identifiable'; import { Identifiable } from 'app/shared/models/base/identifiable';
/** /**

View File

@ -88,7 +88,7 @@ export class FullscreenProjectorComponent implements OnInit {
this.isLoading = false; this.isLoading = false;
}); });
this.operator.getObservable().subscribe(() => { this.operator.getUserObservable().subscribe(() => {
this.canSeeProjector = this.operator.hasPerms('projector.can_see'); this.canSeeProjector = this.operator.hasPerms('projector.can_see');
}); });
} }

View File

@ -1,7 +1,7 @@
import { Component, Inject } from '@angular/core'; import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
import { Displayable } from 'app/shared/models/base/displayable'; import { Displayable } from 'app/site/base/displayable';
import { Identifiable } from 'app/shared/models/base/identifiable'; import { Identifiable } from 'app/shared/models/base/identifiable';
/** /**

View File

@ -373,7 +373,7 @@ export class C4DialogComponent implements OnInit, OnDestroy {
* Returns the operators name. * Returns the operators name.
*/ */
public getPlayerName(): string { public getPlayerName(): string {
return this.op.user.short_name; return this.op.viewUser.short_name;
} }
/** /**

View File

@ -221,9 +221,9 @@ export class ProjectorComponent extends BaseComponent implements OnDestroy {
this.projectorDataService.projectorClosed(from); this.projectorDataService.projectorClosed(from);
} }
this.dataSubscription = this.projectorDataService this.dataSubscription = this.projectorDataService.getProjectorObservable(to).subscribe(data => {
.getProjectorObservable(to) this.slides = data || [];
.subscribe(data => (this.slides = data || [])); });
this.projectorSubscription = this.projectorRepository.getViewModelObservable(to).subscribe(projector => { this.projectorSubscription = this.projectorRepository.getViewModelObservable(to).subscribe(projector => {
if (projector) { if (projector) {
this.scroll = projector.scroll || 0; this.scroll = projector.scroll || 0;

View File

@ -1,4 +1,4 @@
import { Displayable } from '../models/base/displayable'; import { Displayable } from '../../site/base/displayable';
import { Identifiable } from '../models/base/identifiable'; import { Identifiable } from '../models/base/identifiable';
/** /**

View File

@ -2,7 +2,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { E2EImportsModule } from '../../../../e2e-imports.module'; import { E2EImportsModule } from '../../../../e2e-imports.module';
import { SortingTreeComponent } from './sorting-tree.component'; import { SortingTreeComponent } from './sorting-tree.component';
import { Component, ViewChild } from '@angular/core'; import { Component, ViewChild } from '@angular/core';
import { Displayable } from 'app/shared/models/base/displayable'; import { Displayable } from 'app/site/base/displayable';
import { Identifiable } from 'app/shared/models/base/identifiable'; import { Identifiable } from 'app/shared/models/base/identifiable';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';

View File

@ -6,7 +6,7 @@ import { auditTime } from 'rxjs/operators';
import { Subscription, Observable } from 'rxjs'; import { Subscription, Observable } from 'rxjs';
import { Identifiable } from 'app/shared/models/base/identifiable'; import { Identifiable } from 'app/shared/models/base/identifiable';
import { Displayable } from 'app/shared/models/base/displayable'; import { Displayable } from 'app/site/base/displayable';
import { OSTreeNode, TreeService } from 'app/core/ui-services/tree.service'; import { OSTreeNode, TreeService } from 'app/core/ui-services/tree.service';
/** /**

View File

@ -68,7 +68,7 @@ export class PermsDirective extends OpenSlidesComponent implements OnInit, OnDes
public ngOnInit(): void { public ngOnInit(): void {
// observe groups of operator, so the directive can actively react to changes // observe groups of operator, so the directive can actively react to changes
this.operatorSubscription = this.operator.getObservable().subscribe(() => { this.operatorSubscription = this.operator.getUserObservable().subscribe(() => {
this.updateView(); this.updateView();
}); });
} }

View File

@ -41,7 +41,7 @@ export class Item extends BaseModel<Item> {
public parent_id: number; public parent_id: number;
public constructor(input?: any) { public constructor(input?: any) {
super('agenda/item', 'Item', input); super('agenda/item', input);
} }
public deserialize(input: any): void { public deserialize(input: any): void {
@ -84,16 +84,4 @@ export class Item extends BaseModel<Item> {
const type = itemVisibilityChoices.find(choice => choice.key === this.type); const type = itemVisibilityChoices.find(choice => choice.key === this.type);
return type ? type.csvName : ''; return type ? type.csvName : '';
} }
public getTitle(): string {
return this.title;
}
public getListTitle(): string {
return this.title_with_type;
}
public getProjectorTitle(): string {
return this.getListTitle();
}
} }

View File

@ -59,12 +59,4 @@ export class Speaker extends Deserializer {
return SpeakerState.FINISHED; return SpeakerState.FINISHED;
} }
} }
/**
* Getting the title of a speaker does not make much sense.
* Usually it would refer to the title of a user.
*/
public getTitle(): string {
return '';
}
} }

View File

@ -1,7 +1,6 @@
import { AssignmentUser } from './assignment-user'; import { AssignmentUser } from './assignment-user';
import { Poll } from './poll'; import { Poll } from './poll';
import { AgendaBaseModel } from '../base/agenda-base-model'; import { BaseModel } from '../base/base-model';
import { SearchRepresentation } from 'app/core/ui-services/search.service';
export const assignmentPhase = [ export const assignmentPhase = [
{ key: 0, name: 'Searching for candidates' }, { key: 0, name: 'Searching for candidates' },
@ -13,7 +12,7 @@ export const assignmentPhase = [
* Representation of an assignment. * Representation of an assignment.
* @ignore * @ignore
*/ */
export class Assignment extends AgendaBaseModel { export class Assignment extends BaseModel<Assignment> {
public id: number; public id: number;
public title: string; public title: string;
public description: string; public description: string;
@ -26,7 +25,7 @@ export class Assignment extends AgendaBaseModel {
public tags_id: number[]; public tags_id: number[];
public constructor(input?: any) { public constructor(input?: any) {
super('assignments/assignment', 'Election', input); super('assignments/assignment', input);
} }
public get candidateIds(): number[] { public get candidateIds(): number[] {
@ -54,16 +53,4 @@ export class Assignment extends AgendaBaseModel {
}); });
} }
} }
public getTitle(): string {
return this.title;
}
public formatForSearch(): SearchRepresentation {
return [this.title, this.description];
}
public getDetailStateURL(): string {
return 'TODO';
}
} }

View File

@ -1,7 +1,7 @@
import { OpenSlidesComponent } from 'app/openslides.component'; import { OpenSlidesComponent } from 'app/openslides.component';
import { Deserializable } from './deserializable'; import { Deserializable } from './deserializable';
import { Displayable } from './displayable';
import { Identifiable } from './identifiable'; import { Identifiable } from './identifiable';
import { Collection } from './collection';
export type ModelConstructor<T extends BaseModel<T>> = new (...args: any[]) => T; export type ModelConstructor<T extends BaseModel<T>> = new (...args: any[]) => T;
@ -10,7 +10,7 @@ export type ModelConstructor<T extends BaseModel<T>> = new (...args: any[]) => T
* When inherit from this class, give the subclass as the type. E.g. `class Motion extends BaseModel<Motion>` * When inherit from this class, give the subclass as the type. E.g. `class Motion extends BaseModel<Motion>`
*/ */
export abstract class BaseModel<T = object> extends OpenSlidesComponent export abstract class BaseModel<T = object> extends OpenSlidesComponent
implements Deserializable, Displayable, Identifiable { implements Deserializable, Identifiable, Collection {
/** /**
* force children of BaseModel to have a collectionString. * force children of BaseModel to have a collectionString.
* *
@ -27,11 +27,6 @@ export abstract class BaseModel<T = object> extends OpenSlidesComponent
return this._collectionString; return this._collectionString;
} }
/**
* Children should also have a verbose name for generic display purposes
*/
protected _verboseName: string;
/** /**
* force children of BaseModel to have an id * force children of BaseModel to have an id
*/ */
@ -44,10 +39,9 @@ export abstract class BaseModel<T = object> extends OpenSlidesComponent
* @param verboseName * @param verboseName
* @param input * @param input
*/ */
protected constructor(collectionString: string, verboseName: string, input?: any) { protected constructor(collectionString: string, input?: any) {
super(); super();
this._collectionString = collectionString; this._collectionString = collectionString;
this._verboseName = verboseName;
if (input) { if (input) {
this.changeNullValuesToUndef(input); this.changeNullValuesToUndef(input);
@ -74,32 +68,6 @@ export abstract class BaseModel<T = object> extends OpenSlidesComponent
Object.assign(this, update); Object.assign(this, update);
} }
public abstract getTitle(): string;
public getListTitle(): string {
return this.getTitle();
}
public toString(): string {
return this.getTitle();
}
/**
* Returns the verbose name. Makes it plural by adding a 's'.
*
* @param plural If the name should be plural
* @returns the verbose name of the model
*/
public getVerboseName(plural: boolean = false): string {
if (plural) {
return this._verboseName + 's'; // I love english. This works for all our models (participantS, electionS,
// topicS, motionS, (media)fileS, motion blockS, commentS, personal noteS, projectorS, messageS, countdownS, ...)
// Just categorIES need to overwrite this...
} else {
return this._verboseName;
}
}
/** /**
* Most simple and most commonly used deserialize function. * Most simple and most commonly used deserialize function.
* Inherited to children, can be overwritten for special use cases * Inherited to children, can be overwritten for special use cases

View File

@ -0,0 +1,6 @@
/**
* Every implementing object should have a collection string.
*/
export interface Collection {
readonly collectionString: string;
}

View File

@ -5,16 +5,13 @@ import { BaseModel } from '../base/base-model';
* @ignore * @ignore
*/ */
export class ChatMessage extends BaseModel<ChatMessage> { export class ChatMessage extends BaseModel<ChatMessage> {
public static COLLECTIONSTRING = 'core/chat-message';
public id: number; public id: number;
public message: string; public message: string;
public timestamp: string; // TODO: Type for timestamp public timestamp: string; // TODO: Type for timestamp
public user_id: number; public user_id: number;
public constructor(input?: any) { public constructor(input?: any) {
super('core/chat-message', 'Chatmessage', input); super(ChatMessage.COLLECTIONSTRING, input);
}
public getTitle(): string {
return 'Chatmessage';
} }
} }

View File

@ -5,15 +5,12 @@ import { BaseModel } from '../base/base-model';
* @ignore * @ignore
*/ */
export class Config extends BaseModel { export class Config extends BaseModel {
public static COLLECTIONSTRING = 'core/config';
public id: number; public id: number;
public key: string; public key: string;
public value: Object; public value: Object;
public constructor(input?: any) { public constructor(input?: any) {
super('core/config', 'Config', input); super(Config.COLLECTIONSTRING, input);
}
public getTitle(): string {
return this.key;
} }
} }

View File

@ -14,10 +14,6 @@ export class Countdown extends BaseModel<Countdown> {
public running: boolean; public running: boolean;
public constructor(input?: any) { public constructor(input?: any) {
super(Countdown.COLLECTIONSTRING, 'Countdown', input); super(Countdown.COLLECTIONSTRING, input);
}
public getTitle(): string {
return this.description;
} }
} }

View File

@ -6,6 +6,7 @@ import { BaseModel } from '../base/base-model';
* @ignore * @ignore
*/ */
export class History extends BaseModel { export class History extends BaseModel {
public static COLLECTIONSTRING = 'core/history';
public id: number; public id: number;
public element_id: string; public element_id: string;
public now: string; public now: string;
@ -29,10 +30,6 @@ export class History extends BaseModel {
} }
public constructor(input?: any) { public constructor(input?: any) {
super('core/history', 'History', input); super(History.COLLECTIONSTRING, input);
}
public getTitle(): string {
return this.element_id;
} }
} }

View File

@ -11,10 +11,6 @@ export class ProjectorMessage extends BaseModel<ProjectorMessage> {
public message: string; public message: string;
public constructor(input?: any) { public constructor(input?: any) {
super(ProjectorMessage.COLLECTIONSTRING, 'Message', input); super(ProjectorMessage.COLLECTIONSTRING, input);
}
public getTitle(): string {
return 'Projectormessage';
} }
} }

View File

@ -44,9 +44,14 @@ export interface ProjectionDefault {
/** /**
* Representation of a projector. Has the nested property "projectiondefaults" * Representation of a projector. Has the nested property "projectiondefaults"
*
* TODO: Move all function to the viewprojector.
*
* @ignore * @ignore
*/ */
export class Projector extends BaseModel<Projector> { export class Projector extends BaseModel<Projector> {
public static COLLECTIONSTRING = 'core/projector';
public id: number; public id: number;
public elements: ProjectorElements; public elements: ProjectorElements;
public elements_preview: ProjectorElements; public elements_preview: ProjectorElements;
@ -60,7 +65,7 @@ export class Projector extends BaseModel<Projector> {
public projectiondefaults: ProjectionDefault[]; public projectiondefaults: ProjectionDefault[];
public constructor(input?: any) { public constructor(input?: any) {
super('core/projector', 'Projector', input); super(Projector.COLLECTIONSTRING, input);
} }
/** /**
@ -139,8 +144,4 @@ export class Projector extends BaseModel<Projector> {
[[], []] as [T[], T[]] [[], []] as [T[], T[]]
); );
} }
public getTitle(): string {
return this.name;
}
} }

View File

@ -1,27 +1,16 @@
import { BaseModel } from '../base/base-model'; import { BaseModel } from '../base/base-model';
import { Searchable } from '../base/searchable';
/** /**
* Representation of a tag. * Representation of a tag.
* @ignore * @ignore
*/ */
export class Tag extends BaseModel<Tag> implements Searchable { export class Tag extends BaseModel<Tag> {
public static COLLECTIONSTRING = 'core/tag';
public id: number; public id: number;
public name: string; public name: string;
public constructor(input?: any) { public constructor(input?: any) {
super('core/tag', 'Tag', input); super(Tag.COLLECTIONSTRING, input);
}
public getTitle(): string {
return this.name;
}
public formatForSearch(): string[] {
return [this.name];
}
public getDetailStateURL(): string {
return '/tags';
} }
} }

View File

@ -1,12 +1,11 @@
import { File } from './file'; import { File } from './file';
import { Searchable } from '../base/searchable';
import { BaseModel } from '../base/base-model'; import { BaseModel } from '../base/base-model';
/** /**
* Representation of MediaFile. Has the nested property "File" * Representation of MediaFile. Has the nested property "File"
* @ignore * @ignore
*/ */
export class Mediafile extends BaseModel<Mediafile> implements Searchable { export class Mediafile extends BaseModel<Mediafile> {
public id: number; public id: number;
public title: string; public title: string;
public mediafile: File; public mediafile: File;
@ -17,7 +16,7 @@ export class Mediafile extends BaseModel<Mediafile> implements Searchable {
public timestamp: string; public timestamp: string;
public constructor(input?: any) { public constructor(input?: any) {
super('mediafiles/mediafile', 'Mediafile', input); super('mediafiles/mediafile', input);
} }
public deserialize(input: any): void { public deserialize(input: any): void {
@ -30,19 +29,7 @@ export class Mediafile extends BaseModel<Mediafile> implements Searchable {
* *
* @returns the download URL for the specific file as string * @returns the download URL for the specific file as string
*/ */
public getDownloadUrl(): string { public get downloadUrl(): string {
return `${this.media_url_prefix}${this.mediafile.name}`; return `${this.media_url_prefix}${this.mediafile.name}`;
} }
public getTitle(): string {
return this.title;
}
public formatForSearch(): string[] {
return [this.title, this.mediafile.name];
}
public getDetailStateURL(): string {
return this.getDownloadUrl();
}
} }

View File

@ -1,52 +1,17 @@
import { BaseModel } from '../base/base-model'; import { BaseModel } from '../base/base-model';
import { Searchable } from '../base/searchable';
import { SearchRepresentation } from 'app/core/ui-services/search.service';
/** /**
* Representation of a motion category. Has the nested property "File" * Representation of a motion category. Has the nested property "File"
* @ignore * @ignore
*/ */
export class Category extends BaseModel<Category> implements Searchable { export class Category extends BaseModel<Category> {
public static COLLECTIONSTRING = 'motions/category';
public id: number; public id: number;
public name: string; public name: string;
public prefix: string; public prefix: string;
public constructor(input?: any) { public constructor(input?: any) {
super('motions/category', 'Category', input); super(Category.COLLECTIONSTRING, input);
}
public getTitle(): string {
return this.prefix ? this.prefix + ' - ' + this.name : this.name;
}
/**
* Returns the verbose name of this model.
*
* @override
* @param plural If the name should be plural
* @param The verbose name
*/
public getVerboseName(plural: boolean = false): string {
if (plural) {
return 'Categories';
} else {
return this._verboseName;
}
}
/**
* Formats the category for search
*
* @override
*/
public formatForSearch(): SearchRepresentation {
return [this.getTitle()];
}
/**
* TODO: add an id as url parameter, so the category auto-opens.
*/
public getDetailStateURL(): string {
return '/motions/category';
} }
} }

View File

@ -1,38 +1,17 @@
import { AgendaBaseModel } from '../base/agenda-base-model'; import { BaseModel } from '../base/base-model';
import { SearchRepresentation } from 'app/core/ui-services/search.service';
/** /**
* Representation of a motion block. * Representation of a motion block.
* @ignore * @ignore
*/ */
export class MotionBlock extends AgendaBaseModel { export class MotionBlock extends BaseModel {
public static COLLECTIONSTRING = 'motions/motion-block';
public id: number; public id: number;
public title: string; public title: string;
public agenda_item_id: number; public agenda_item_id: number;
public constructor(input?: any) { public constructor(input?: any) {
super('motions/motion-block', 'Motion block', input); super(MotionBlock.COLLECTIONSTRING, input);
}
public getTitle(): string {
return this.title;
}
/**
* Formats the category for search
*
* @override
*/
public formatForSearch(): SearchRepresentation {
return [this.title];
}
/**
* Get the URL to the motion block
*
* @returns the URL as string
*/
public getDetailStateURL(): string {
return `/motions/blocks/${this.id}`;
} }
} }

View File

@ -4,7 +4,9 @@ import { BaseModel } from '../base/base-model';
* Representation of a motion change recommendation. * Representation of a motion change recommendation.
* @ignore * @ignore
*/ */
export class MotionChangeReco extends BaseModel<MotionChangeReco> { export class MotionChangeRecommendation extends BaseModel<MotionChangeRecommendation> {
public static COLLECTIONSTRING = 'motions/motion-change-recommendation';
public id: number; public id: number;
public motion_id: number; public motion_id: number;
public rejected: boolean; public rejected: boolean;
@ -17,10 +19,6 @@ export class MotionChangeReco extends BaseModel<MotionChangeReco> {
public creation_time: string; public creation_time: string;
public constructor(input?: any) { public constructor(input?: any) {
super('motions/motion-change-recommendation', 'Change recommendation', input); super(MotionChangeRecommendation.COLLECTIONSTRING, input);
}
public getTitle(): string {
return 'Changerecommendation';
} }
} }

View File

@ -1,20 +1,18 @@
import { BaseModel } from '../base/base-model'; import { BaseModel } from '../base/base-model';
/** /**
* Representation of a motion category. Has the nested property "File" * Representation of a comment section.
* @ignore * @ignore
*/ */
export class MotionCommentSection extends BaseModel<MotionCommentSection> { export class MotionCommentSection extends BaseModel<MotionCommentSection> {
public static COLLECTIONSTRING = 'motions/motion-comment-section';
public id: number; public id: number;
public name: string; public name: string;
public read_groups_id: number[]; public read_groups_id: number[];
public write_groups_id: number[]; public write_groups_id: number[];
public constructor(input?: any) { public constructor(input?: any) {
super('motions/motion-comment-section', 'Comment section', input); super(MotionCommentSection.COLLECTIONSTRING, input);
}
public getTitle(): string {
return this.name;
} }
} }

View File

@ -1,8 +1,7 @@
import { MotionSubmitter } from './motion-submitter'; import { MotionSubmitter } from './motion-submitter';
import { MotionComment } from './motion-comment'; import { MotionComment } from './motion-comment';
import { AgendaBaseModel } from '../base/agenda-base-model';
import { SearchRepresentation } from 'app/core/ui-services/search.service';
import { MotionPoll } from './motion-poll'; import { MotionPoll } from './motion-poll';
import { BaseModel } from '../base/base-model';
/** /**
* Representation of Motion. * Representation of Motion.
@ -11,7 +10,7 @@ import { MotionPoll } from './motion-poll';
* *
* @ignore * @ignore
*/ */
export class Motion extends AgendaBaseModel { export class Motion extends BaseModel {
public static COLLECTIONSTRING = 'motions/motion'; public static COLLECTIONSTRING = 'motions/motion';
public id: number; public id: number;
@ -45,7 +44,7 @@ export class Motion extends AgendaBaseModel {
public last_modified: string; public last_modified: string;
public constructor(input?: any) { public constructor(input?: any) {
super(Motion.COLLECTIONSTRING, 'Motion', input); super(Motion.COLLECTIONSTRING, input);
} }
/** /**
@ -59,49 +58,6 @@ export class Motion extends AgendaBaseModel {
.map((submitter: MotionSubmitter) => submitter.user_id); .map((submitter: MotionSubmitter) => submitter.user_id);
} }
public getTitle(): string {
if (this.identifier) {
return this.identifier + ': ' + this.title;
} else {
return this.title;
}
}
public getAgendaTitle(): string {
// if the identifier is set, the title will be 'Motion <identifier>'.
if (this.identifier) {
return 'Motion ' + this.identifier;
} else {
return this.getTitle();
}
}
public getAgendaTitleWithType(): string {
// Append the verbose name only, if not the special format 'Motion <identifier>' is used.
if (this.identifier) {
return 'Motion ' + this.identifier;
} else {
return this.getTitle() + ' (' + this.getVerboseName() + ')';
}
}
/**
* Formats the category for search
*
* @override
*/
public formatForSearch(): SearchRepresentation {
let searchValues = [this.title, this.text, this.reason];
if (this.amendment_paragraphs) {
searchValues = searchValues.concat(this.amendment_paragraphs.filter(x => !!x));
}
return searchValues;
}
public getDetailStateURL(): string {
return `/motions/${this.id}`;
}
public deserialize(input: any): void { public deserialize(input: any): void {
Object.assign(this, input); Object.assign(this, input);

View File

@ -1,38 +1,18 @@
import { BaseModel } from '../base/base-model'; import { BaseModel } from '../base/base-model';
import { Searchable } from '../base/searchable';
import { SearchRepresentation } from 'app/core/ui-services/search.service';
/** /**
* Representation of a statute paragraph. * Representation of a statute paragraph.
* @ignore * @ignore
*/ */
export class StatuteParagraph extends BaseModel<StatuteParagraph> implements Searchable { export class StatuteParagraph extends BaseModel<StatuteParagraph> {
public static COLLECTIONSTRING = 'motions/statute-paragraph';
public id: number; public id: number;
public title: string; public title: string;
public text: string; public text: string;
public weight: number; public weight: number;
public constructor(input?: any) { public constructor(input?: any) {
super('motions/statute-paragraph', 'Statute paragraph', input); super(StatuteParagraph.COLLECTIONSTRING, input);
}
public getTitle(): string {
return this.title;
}
/**
* Formats the category for search
*
* @override
*/
public formatForSearch(): SearchRepresentation {
return [this.title, this.text];
}
/**
* TODO: add an id as url parameter, so the statute paragraph auto-opens.
*/
public getDetailStateURL(): string {
return '/motions/statute-paragraphs';
} }
} }

View File

@ -6,17 +6,15 @@ import { WorkflowState } from './workflow-state';
* @ignore * @ignore
*/ */
export class Workflow extends BaseModel<Workflow> { export class Workflow extends BaseModel<Workflow> {
public static COLLECTIONSTRING = 'motions/workflow';
public id: number; public id: number;
public name: string; public name: string;
public states: WorkflowState[]; public states: WorkflowState[];
public first_state_id: number; public first_state_id: number;
public get firstState(): WorkflowState {
return this.getStateById(this.first_state_id);
}
public constructor(input?: any) { public constructor(input?: any) {
super('motions/workflow', 'Workflow', input); super(Workflow.COLLECTIONSTRING, input);
} }
/** /**
@ -38,10 +36,6 @@ export class Workflow extends BaseModel<Workflow> {
}); });
} }
public getStateById(id: number): WorkflowState {
return this.states.find(state => state.id === id);
}
public deserialize(input: any): void { public deserialize(input: any): void {
Object.assign(this, input); Object.assign(this, input);
if (input.states instanceof Array) { if (input.states instanceof Array) {
@ -51,8 +45,4 @@ export class Workflow extends BaseModel<Workflow> {
}); });
} }
} }
public getTitle(): string {
return this.name;
}
} }

View File

@ -1,11 +1,12 @@
import { AgendaBaseModel } from '../base/agenda-base-model'; import { BaseModel } from '../base/base-model';
import { SearchRepresentation } from 'app/core/ui-services/search.service';
/** /**
* Representation of a topic. * Representation of a topic.
* @ignore * @ignore
*/ */
export class Topic extends AgendaBaseModel { export class Topic extends BaseModel<Topic> {
public static COLLECTIONSTRING = 'topics/topic';
public id: number; public id: number;
public title: string; public title: string;
public text: string; public text: string;
@ -13,36 +14,6 @@ export class Topic extends AgendaBaseModel {
public agenda_item_id: number; public agenda_item_id: number;
public constructor(input?: any) { public constructor(input?: any) {
super('topics/topic', 'Topic', input); super(Topic.COLLECTIONSTRING, input);
}
public getTitle(): string {
return this.title;
}
public getAgendaTitleWithType(): string {
// Do not append ' (Topic)' to the title.
return this.getAgendaTitle();
}
/**
* Formats the category for search
*
* @override
*/
public formatForSearch(): SearchRepresentation {
return [this.title, this.text];
}
public getDetailStateURL(): string {
return `/agenda/topics/${this.id}`;
}
/**
* Returns the text to be inserted in csv exports
* @override
*/
public getCSVExportText(): string {
return this.text;
} }
} }

View File

@ -5,12 +5,13 @@ import { BaseModel } from '../base/base-model';
* @ignore * @ignore
*/ */
export class Group extends BaseModel<Group> { export class Group extends BaseModel<Group> {
public static COLLECTIONSTRING = 'users/group';
public id: number; public id: number;
public name: string; public name: string;
public permissions: string[]; public permissions: string[];
public constructor(input?: any) { public constructor(input?: any) {
super('users/group', 'Group', input); super(Group.COLLECTIONSTRING, input);
if (!input) { if (!input) {
// permissions are required for new groups // permissions are required for new groups
this.permissions = []; this.permissions = [];

View File

@ -49,12 +49,14 @@ export interface PersonalNoteObject {
* @ignore * @ignore
*/ */
export class PersonalNote extends BaseModel<PersonalNote> implements PersonalNoteObject { export class PersonalNote extends BaseModel<PersonalNote> implements PersonalNoteObject {
public static COLLECTIONSTRING = 'users/personal-note';
public id: number; public id: number;
public user_id: number; public user_id: number;
public notes: PersonalNotesFormat; public notes: PersonalNotesFormat;
public constructor(input: any) { public constructor(input: any) {
super('users/personal-note', 'Personal note', input); super(PersonalNote.COLLECTIONSTRING, input);
} }
public getTitle(): string { public getTitle(): string {

View File

@ -1,7 +1,4 @@
import { Searchable } from '../base/searchable';
import { SearchRepresentation } from 'app/core/ui-services/search.service';
import { BaseModel } from '../base/base-model'; import { BaseModel } from '../base/base-model';
import { DetailNavigable } from '../base/detail-navigable';
/** /**
* Iterable pre selection of genders (sexes) * Iterable pre selection of genders (sexes)
@ -12,7 +9,7 @@ export const genders = ['Female', 'Male', 'Diverse'];
* Representation of a user in contrast to the operator. * Representation of a user in contrast to the operator.
* @ignore * @ignore
*/ */
export class User extends BaseModel<User> implements Searchable, DetailNavigable { export class User extends BaseModel<User> {
public static COLLECTIONSTRING = 'users/user'; public static COLLECTIONSTRING = 'users/user';
public id: number; public id: number;
@ -34,89 +31,10 @@ export class User extends BaseModel<User> implements Searchable, DetailNavigable
public default_password: string; public default_password: string;
public constructor(input?: any) { public constructor(input?: any) {
super(User.COLLECTIONSTRING, 'Participant', input); super(User.COLLECTIONSTRING, input);
}
public get full_name(): string {
let name = this.short_name;
const additions: string[] = [];
// addition: add number and structure level
const structure_level = this.structure_level ? this.structure_level.trim() : '';
if (structure_level) {
additions.push(structure_level);
}
const number = this.number ? this.number.trim() : null;
if (number) {
// TODO Translate
additions.push('No. ' + number);
}
if (additions.length > 0) {
name += ' (' + additions.join(' · ') + ')';
}
return name.trim();
} }
public containsGroupId(id: number): boolean { public containsGroupId(id: number): boolean {
return this.groups_id.some(groupId => groupId === id); return this.groups_id.some(groupId => groupId === id);
} }
// TODO read config values for "users_sort_by"
/**
* Getter for the short name (Title, given name, surname)
*
* @returns a non-empty string
*/
public get short_name(): string {
const title = this.title ? this.title.trim() : '';
const firstName = this.first_name ? this.first_name.trim() : '';
const lastName = this.last_name ? this.last_name.trim() : '';
// TODO need DS adjustment first first
// if (this.DS.getConfig('users_sort_by').value === 'last_name') {
// if (lastName && firstName) {
// shortName += `${lastName}, ${firstName}`;
// } else {
// shortName += lastName || firstName;
// }
// }
let shortName = `${firstName} ${lastName}`;
if (shortName.length <= 1) {
// We have at least one space from the concatination of
// first- and lastname.
shortName = this.username;
}
if (title) {
shortName = `${title} ${shortName}`;
}
return shortName;
}
public getTitle(): string {
return this.full_name;
}
public getListViewTitle(): string {
return this.short_name;
}
public getDetailStateURL(): string {
return `/users/${this.id}`;
}
/**
* Formats the category for search
*
* @override
*/
public formatForSearch(): SearchRepresentation {
return [this.title, this.first_name, this.last_name, this.structure_level, this.number];
}
} }

View File

@ -1,14 +1,22 @@
import { AppConfig } from '../../core/app-config'; import { AppConfig } from '../../core/app-config';
import { Item } from '../../shared/models/agenda/item'; import { Item } from '../../shared/models/agenda/item';
import { Topic } from '../../shared/models/topics/topic'; import { Topic } from '../../shared/models/topics/topic';
import { AgendaRepositoryService } from 'app/core/repositories/agenda/agenda-repository.service'; import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
import { TopicRepositoryService } from 'app/core/repositories/agenda/topic-repository.service'; import { TopicRepositoryService } from 'app/core/repositories/agenda/topic-repository.service';
import { ViewTopic } from './models/view-topic';
import { ViewItem } from './models/view-item';
export const AgendaAppConfig: AppConfig = { export const AgendaAppConfig: AppConfig = {
name: 'agenda', name: 'agenda',
models: [ models: [
{ collectionString: 'agenda/item', model: Item, repository: AgendaRepositoryService }, { collectionString: 'agenda/item', model: Item, viewModel: ViewItem, repository: ItemRepositoryService },
{ collectionString: 'topics/topic', model: Topic, searchOrder: 1, repository: TopicRepositoryService } {
collectionString: 'topics/topic',
model: Topic,
viewModel: ViewTopic,
searchOrder: 1,
repository: TopicRepositoryService
}
], ],
mainMenuEntries: [ mainMenuEntries: [
{ {

View File

@ -5,7 +5,7 @@ import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { AgendaFilterListService } from '../../services/agenda-filter-list.service'; import { AgendaFilterListService } from '../../services/agenda-filter-list.service';
import { AgendaRepositoryService } from 'app/core/repositories/agenda/agenda-repository.service'; import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
import { ListViewBaseComponent } from 'app/site/base/list-view-base'; import { ListViewBaseComponent } from 'app/site/base/list-view-base';
import { PromptService } from 'app/core/ui-services/prompt.service'; import { PromptService } from 'app/core/ui-services/prompt.service';
import { ViewItem } from '../../models/view-item'; import { ViewItem } from '../../models/view-item';
@ -63,7 +63,7 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem> impleme
matSnackBar: MatSnackBar, matSnackBar: MatSnackBar,
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router, private router: Router,
private repo: AgendaRepositoryService, private repo: ItemRepositoryService,
private promptService: PromptService, private promptService: PromptService,
private dialog: MatDialog, private dialog: MatDialog,
private config: ConfigService, private config: ConfigService,

View File

@ -5,7 +5,7 @@ import { MatSnackBar } from '@angular/material';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { AgendaRepositoryService } from 'app/core/repositories/agenda/agenda-repository.service'; import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
import { BaseViewComponent } from '../../../base/base-view'; import { BaseViewComponent } from '../../../base/base-view';
import { OSTreeSortEvent } from 'app/shared/components/sorting-tree/sorting-tree.component'; import { OSTreeSortEvent } from 'app/shared/components/sorting-tree/sorting-tree.component';
import { ViewItem } from '../../models/view-item'; import { ViewItem } from '../../models/view-item';
@ -39,7 +39,7 @@ export class AgendaSortComponent extends BaseViewComponent {
title: Title, title: Title,
translate: TranslateService, translate: TranslateService,
matSnackBar: MatSnackBar, matSnackBar: MatSnackBar,
private agendaRepo: AgendaRepositoryService private agendaRepo: ItemRepositoryService
) { ) {
super(title, translate, matSnackBar); super(title, translate, matSnackBar);
this.itemsObservable = this.agendaRepo.getViewModelListObservable(); this.itemsObservable = this.agendaRepo.getViewModelListObservable();

View File

@ -1,25 +1,25 @@
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { BehaviorSubject, Subscription } from 'rxjs';
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms'; import { FormGroup, FormControl } from '@angular/forms';
import { MatSnackBar, MatSelectChange } from '@angular/material'; import { MatSnackBar, MatSelectChange } from '@angular/material';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject, Subscription } from 'rxjs';
import { AgendaRepositoryService } from 'app/core/repositories/agenda/agenda-repository.service'; import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
import { BaseViewComponent } from 'app/site/base/base-view'; import { BaseViewComponent } from 'app/site/base/base-view';
import { CurrentListOfSpeakersSlideService } from 'app/site/projector/services/current-list-of-of-speakers-slide.service'; import { CurrentListOfSpeakersSlideService } from 'app/site/projector/services/current-list-of-of-speakers-slide.service';
import { DataStoreService } from 'app/core/core-services/data-store.service';
import { DurationService } from 'app/core/ui-services/duration.service';
import { OperatorService } from 'app/core/core-services/operator.service'; import { OperatorService } from 'app/core/core-services/operator.service';
import { ProjectorRepositoryService } from 'app/core/repositories/projector/projector-repository.service'; import { ProjectorRepositoryService } from 'app/core/repositories/projector/projector-repository.service';
import { PromptService } from 'app/core/ui-services/prompt.service'; import { PromptService } from 'app/core/ui-services/prompt.service';
import { SpeakerState } from 'app/shared/models/agenda/speaker'; import { SpeakerState } from 'app/shared/models/agenda/speaker';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
import { User } from 'app/shared/models/users/user';
import { ViewItem } from '../../models/view-item'; import { ViewItem } from '../../models/view-item';
import { ViewSpeaker } from '../../models/view-speaker'; import { ViewSpeaker } from '../../models/view-speaker';
import { ViewProjector } from 'app/site/projector/models/view-projector'; import { ViewProjector } from 'app/site/projector/models/view-projector';
import { ViewUser } from 'app/site/users/models/view-user';
import { UserRepositoryService } from 'app/core/repositories/users/user-repository.service';
import { DurationService } from 'app/core/ui-services/duration.service';
/** /**
* The list of speakers for agenda items. * The list of speakers for agenda items.
@ -68,7 +68,7 @@ export class SpeakerListComponent extends BaseViewComponent implements OnInit {
/** /**
* Hold the users * Hold the users
*/ */
public users: BehaviorSubject<User[]>; public users: BehaviorSubject<ViewUser[]>;
/** /**
* Required for the user search selector * Required for the user search selector
@ -113,12 +113,12 @@ export class SpeakerListComponent extends BaseViewComponent implements OnInit {
snackBar: MatSnackBar, snackBar: MatSnackBar,
projectorRepo: ProjectorRepositoryService, projectorRepo: ProjectorRepositoryService,
private route: ActivatedRoute, private route: ActivatedRoute,
private DS: DataStoreService, private itemRepo: ItemRepositoryService,
private itemRepo: AgendaRepositoryService,
private op: OperatorService, private op: OperatorService,
private promptService: PromptService, private promptService: PromptService,
private currentListOfSpeakersService: CurrentListOfSpeakersSlideService, private currentListOfSpeakersService: CurrentListOfSpeakersSlideService,
private durationService: DurationService private durationService: DurationService,
private userRepository: UserRepositoryService
) { ) {
super(title, translate, snackBar); super(title, translate, snackBar);
this.isCurrentListOfSpeakers(); this.isCurrentListOfSpeakers();
@ -143,12 +143,8 @@ export class SpeakerListComponent extends BaseViewComponent implements OnInit {
*/ */
public ngOnInit(): void { public ngOnInit(): void {
// load and observe users // load and observe users
this.users = new BehaviorSubject(this.DS.getAll(User)); this.users = new BehaviorSubject(this.userRepository.getViewModelList());
this.DS.changeObservable.subscribe(model => { this.userRepository.getViewModelListObservable().subscribe(users => this.users.next(users));
if (model instanceof User) {
this.users.next(this.DS.getAll(User));
}
});
// detect changes in the form // detect changes in the form
this.addSpeakerForm.valueChanges.subscribe(formResult => { this.addSpeakerForm.valueChanges.subscribe(formResult => {
@ -190,10 +186,10 @@ export class SpeakerListComponent extends BaseViewComponent implements OnInit {
} }
this.projectorSubscription = this.currentListOfSpeakersService this.projectorSubscription = this.currentListOfSpeakersService
.getAgendaItemIdObservable(projector) .getAgendaItemObservable(projector)
.subscribe(agendaId => { .subscribe(item => {
if (agendaId) { if (item) {
this.setSpeakerList(agendaId); this.setSpeakerList(item.id);
} }
}); });
} }

View File

@ -24,8 +24,7 @@
</div> </div>
</os-head-bar> </os-head-bar>
<mat-card *ngIf="topic || editTopic"
<mat-card *ngIf="topic || editTopic"
[ngClass]="editTopic ? 'os-form-card' : 'os-card'" [ngClass]="editTopic ? 'os-form-card' : 'os-card'"
class="on-transition-fade"> class="on-transition-fade">
<div *ngIf="!editTopic"> <div *ngIf="!editTopic">
@ -43,7 +42,7 @@
<span translate>Attachments</span>: <span translate>Attachments</span>:
<mat-list dense> <mat-list dense>
<mat-list-item *ngFor="let file of topic.attachments"> <mat-list-item *ngFor="let file of topic.attachments">
<a [routerLink]="file.getDownloadUrl()" target="_blank">{{ file.title }}</a> <a [routerLink]="file.downloadUrl" target="_blank">{{ file.title }}</a>
</mat-list-item> </mat-list-item>
</mat-list> </mat-list>
<!-- TODO: Mediafiles and attachments are not fully implemented --> <!-- TODO: Mediafiles and attachments are not fully implemented -->
@ -102,12 +101,12 @@
[multiple]="false" [multiple]="false"
[includeNone]="true" [includeNone]="true"
listname="{{ 'Parent item' | translate }}" listname="{{ 'Parent item' | translate }}"
[InputListValues]="agendaItemObserver" [InputListValues]="itemObserver"
></os-search-value-selector> ></os-search-value-selector>
</div> </div>
</div> </div>
</form> </form>
</mat-card> </mat-card>
<mat-menu #topicExtraMenu="matMenu"> <mat-menu #topicExtraMenu="matMenu">
<button mat-menu-item *ngIf="topic" [routerLink]="getSpeakerLink()"> <button mat-menu-item *ngIf="topic" [routerLink]="getSpeakerLink()">

View File

@ -12,10 +12,13 @@ import { TopicRepositoryService } from 'app/core/repositories/agenda/topic-repos
import { ViewTopic } from '../../models/view-topic'; import { ViewTopic } from '../../models/view-topic';
import { OperatorService } from 'app/core/core-services/operator.service'; import { OperatorService } from 'app/core/core-services/operator.service';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { DataStoreService } from 'app/core/core-services/data-store.service'; import { itemVisibilityChoices } from 'app/shared/models/agenda/item';
import { Mediafile } from 'app/shared/models/mediafiles/mediafile';
import { Item, itemVisibilityChoices } from 'app/shared/models/agenda/item';
import { CreateTopic } from '../../models/create-topic'; import { CreateTopic } from '../../models/create-topic';
import { Topic } from 'app/shared/models/topics/topic';
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
import { ViewItem } from '../../models/view-item';
import { MediafileRepositoryService } from 'app/core/repositories/mediafiles/mediafile-repository.service';
import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
/** /**
* Detail page for topics. * Detail page for topics.
@ -49,12 +52,12 @@ export class TopicDetailComponent extends BaseViewComponent {
/** /**
* Subject for mediafiles * Subject for mediafiles
*/ */
public mediafilesObserver: BehaviorSubject<Mediafile[]>; public mediafilesObserver: BehaviorSubject<ViewMediafile[]>;
/** /**
* Subject for agenda items * Subject for agenda items
*/ */
public agendaItemObserver: BehaviorSubject<Item[]>; public itemObserver: BehaviorSubject<ViewItem[]>;
/** /**
* Determine visibility states for the agenda that will be created implicitly * Determine visibility states for the agenda that will be created implicitly
@ -85,22 +88,20 @@ export class TopicDetailComponent extends BaseViewComponent {
private repo: TopicRepositoryService, private repo: TopicRepositoryService,
private promptService: PromptService, private promptService: PromptService,
private operator: OperatorService, private operator: OperatorService,
private DS: DataStoreService private mediafileRepo: MediafileRepositoryService,
private itemRepo: ItemRepositoryService
) { ) {
super(title, translate, matSnackBar); super(title, translate, matSnackBar);
this.getTopicByUrl(); this.getTopicByUrl();
this.createForm(); this.createForm();
this.mediafilesObserver = new BehaviorSubject(this.DS.getAll(Mediafile)); this.mediafilesObserver = new BehaviorSubject(this.mediafileRepo.getViewModelList());
this.agendaItemObserver = new BehaviorSubject(this.DS.getAll(Item)); this.itemObserver = new BehaviorSubject(this.itemRepo.getViewModelList());
this.DS.changeObservable.subscribe(newModel => { this.mediafileRepo
if (newModel instanceof Item) { .getViewModelListObservable()
this.agendaItemObserver.next(DS.getAll(Item)); .subscribe(mediafiles => this.mediafilesObserver.next(mediafiles));
} else if (newModel instanceof Mediafile) { this.itemRepo.getViewModelListObservable().subscribe(items => this.itemObserver.next(items));
this.mediafilesObserver.next(DS.getAll(Mediafile));
}
});
} }
/** /**
@ -170,7 +171,7 @@ export class TopicDetailComponent extends BaseViewComponent {
// creates a new topic // creates a new topic
this.newTopic = true; this.newTopic = true;
this.editTopic = true; this.editTopic = true;
this.topic = new ViewTopic(); this.topic = new ViewTopic(new Topic());
} else { } else {
// load existing topic // load existing topic
this.route.params.subscribe(params => { this.route.params.subscribe(params => {
@ -205,7 +206,7 @@ export class TopicDetailComponent extends BaseViewComponent {
*/ */
public getSpeakerLink(): string { public getSpeakerLink(): string {
if (!this.newTopic && this.topic) { if (!this.newTopic && this.topic) {
const item = this.repo.getAgendaItem(this.topic.topic); const item = this.topic.getAgendaItem();
if (item) { if (item) {
return `/agenda/${item.id}/speakers`; return `/agenda/${item.id}/speakers`;
} }

View File

@ -1,11 +1,11 @@
import { BaseViewModel } from '../../base/base-view-model'; import { BaseViewModel } from '../../base/base-view-model';
import { Item } from 'app/shared/models/agenda/item'; import { Item } from 'app/shared/models/agenda/item';
import { AgendaBaseModel } from 'app/shared/models/base/agenda-base-model';
import { Speaker } from 'app/shared/models/agenda/speaker'; import { Speaker } from 'app/shared/models/agenda/speaker';
import { BaseAgendaViewModel } from 'app/site/base/base-agenda-view-model';
export class ViewItem extends BaseViewModel { export class ViewItem extends BaseViewModel {
private _item: Item; private _item: Item;
private _contentObject: AgendaBaseModel; private _contentObject: BaseAgendaViewModel;
/** /**
* virtual weight defined by the order in the agenda tree, representing a shortcut to sorting by * virtual weight defined by the order in the agenda tree, representing a shortcut to sorting by
@ -24,7 +24,7 @@ export class ViewItem extends BaseViewModel {
return this._item; return this._item;
} }
public get contentObject(): AgendaBaseModel { public get contentObject(): BaseAgendaViewModel {
return this._contentObject; return this._contentObject;
} }
@ -92,8 +92,8 @@ export class ViewItem extends BaseViewModel {
return this.item ? this.item.parent_id : null; return this.item ? this.item.parent_id : null;
} }
public constructor(item: Item, contentObject: AgendaBaseModel) { public constructor(item: Item, contentObject: BaseAgendaViewModel) {
super(); super('Item');
this._item = item; this._item = item;
this._contentObject = contentObject; this._contentObject = contentObject;
} }
@ -113,7 +113,7 @@ export class ViewItem extends BaseViewModel {
* @returns the agenda list title as string * @returns the agenda list title as string
*/ */
public getListTitle(): string { public getListTitle(): string {
const contentObject: AgendaBaseModel = this.contentObject; const contentObject: BaseAgendaViewModel = this.contentObject;
const numberPrefix = this.itemNumber ? `${this.itemNumber} · ` : ''; const numberPrefix = this.itemNumber ? `${this.itemNumber} · ` : '';
if (contentObject) { if (contentObject) {
@ -123,9 +123,5 @@ export class ViewItem extends BaseViewModel {
} }
} }
public updateValues(update: Item): void { public updateDependencies(update: BaseViewModel): void {}
if (this.id === update.id) {
this._item = update;
}
}
} }

View File

@ -1,63 +1,62 @@
import { BaseViewModel } from 'app/site/base/base-view-model'; import { BaseViewModel } from 'app/site/base/base-view-model';
import { Speaker, SpeakerState } from 'app/shared/models/agenda/speaker'; import { Speaker, SpeakerState } from 'app/shared/models/agenda/speaker';
import { User } from 'app/shared/models/users/user'; import { ViewUser } from 'app/site/users/models/view-user';
import { Selectable } from 'app/shared/components/selectable';
/** /**
* Provides "safe" access to a speaker with all it's components * Provides "safe" access to a speaker with all it's components
*/ */
export class ViewSpeaker extends BaseViewModel implements Selectable { export class ViewSpeaker extends BaseViewModel {
private _speaker: Speaker; private _speaker: Speaker;
private _user: User; private _user: ViewUser | null;
public get speaker(): Speaker { public get speaker(): Speaker {
return this._speaker; return this._speaker;
} }
public get user(): User { public get user(): ViewUser {
return this._user; return this._user;
} }
public get id(): number { public get id(): number {
return this.speaker ? this.speaker.id : null; return this.speaker.id;
} }
public get weight(): number { public get weight(): number {
return this.speaker ? this.speaker.weight : null; return this.speaker.weight;
} }
public get marked(): boolean { public get marked(): boolean {
return this.speaker ? this.speaker.marked : null; return this.speaker.marked;
} }
/** /**
* @returns an ISO datetime string or null * @returns an ISO datetime string or null
*/ */
public get begin_time(): string { public get begin_time(): string {
return this.speaker ? this.speaker.begin_time : null; return this.speaker.begin_time;
} }
/** /**
* @returns an ISO datetime string or null * @returns an ISO datetime string or null
*/ */
public get end_time(): string { public get end_time(): string {
return this.speaker ? this.speaker.end_time : null; return this.speaker.end_time;
} }
public get state(): SpeakerState { public get state(): SpeakerState {
return this.speaker ? this.speaker.state : null; return this.speaker.state;
} }
public get name(): string { public get name(): string {
return this.user.full_name; return this.user ? this.user.full_name : '';
} }
public get gender(): string { public get gender(): string {
return this.user.gender || ''; return this.user ? this.user.gender : '';
} }
public constructor(speaker?: Speaker, user?: User) { public constructor(speaker: Speaker, user?: ViewUser) {
super(); super('Speaker');
this._speaker = speaker; this._speaker = speaker;
this._user = user; this._user = user;
} }
@ -70,9 +69,5 @@ export class ViewSpeaker extends BaseViewModel implements Selectable {
* Speaker is not a base model, * Speaker is not a base model,
* @param update the incoming update * @param update the incoming update
*/ */
public updateValues(update: Speaker): void { public updateDependencies(update: BaseViewModel): void {}
if (this.id === update.id) {
this._speaker = update;
}
}
} }

View File

@ -1,71 +1,112 @@
import { BaseViewModel } from '../../base/base-view-model';
import { Topic } from 'app/shared/models/topics/topic'; import { Topic } from 'app/shared/models/topics/topic';
import { Mediafile } from 'app/shared/models/mediafiles/mediafile'; import { BaseAgendaViewModel } from 'app/site/base/base-agenda-view-model';
import { Item } from 'app/shared/models/agenda/item'; import { SearchRepresentation } from 'app/core/ui-services/search.service';
import { BaseModel } from 'app/shared/models/base/base-model'; import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
import { ViewItem } from './view-item';
import { BaseViewModel } from 'app/site/base/base-view-model';
/** /**
* Provides "safe" access to topic with all it's components * Provides "safe" access to topic with all it's components
* @ignore * @ignore
*/ */
export class ViewTopic extends BaseViewModel { export class ViewTopic extends BaseAgendaViewModel {
protected _topic: Topic; protected _topic: Topic;
private _attachments: Mediafile[]; private _attachments: ViewMediafile[];
private _agenda_item: Item; private _agendaItem: ViewItem;
public get topic(): Topic { public get topic(): Topic {
return this._topic; return this._topic;
} }
public get attachments(): Mediafile[] { public get attachments(): ViewMediafile[] {
return this._attachments; return this._attachments;
} }
public get agenda_item(): Item { public get agendaItem(): ViewItem {
return this._agenda_item; return this._agendaItem;
} }
public get id(): number { public get id(): number {
return this.topic ? this.topic.id : null; return this.topic.id;
} }
public get agenda_item_id(): number { public get agenda_item_id(): number {
return this.topic ? this.topic.agenda_item_id : null; return this.topic.agenda_item_id;
} }
public get attachments_id(): number[] { public get attachments_id(): number[] {
return this.topic ? this.topic.attachments_id : null; return this.topic.attachments_id;
} }
public get title(): string { public get title(): string {
return this.topic ? this.topic.title : null; return this.topic.title;
} }
public get text(): string { public get text(): string {
return this.topic ? this.topic.text : null; return this.topic.text;
} }
public constructor(topic?: Topic, attachments?: Mediafile[], item?: Item) { public constructor(topic: Topic, attachments?: ViewMediafile[], item?: ViewItem) {
super(); super('Topic');
this._topic = topic; this._topic = topic;
this._attachments = attachments; this._attachments = attachments;
this._agenda_item = item; this._agendaItem = item;
} }
public getTitle(): string { public getTitle(): string {
return this.title; return this.title;
} }
public getAgendaItem(): ViewItem {
return this.agendaItem;
}
public getAgendaTitleWithType(): string {
// Do not append ' (Topic)' to the title.
return this.getAgendaTitle();
}
/**
* Formats the category for search
*
* @override
*/
public formatForSearch(): SearchRepresentation {
return [this.title, this.text];
}
public getDetailStateURL(): string {
return `/agenda/topics/${this.id}`;
}
/**
* Returns the text to be inserted in csv exports
* @override
*/
public getCSVExportText(): string {
return this.text;
}
public getSlide(): ProjectorElementBuildDeskriptor {
throw new Error('TODO');
}
public hasAttachments(): boolean { public hasAttachments(): boolean {
return this.attachments && this.attachments.length > 0; return this.attachments && this.attachments.length > 0;
} }
public updateValues(update: BaseModel): void { public updateDependencies(update: BaseViewModel): void {
if (update instanceof Mediafile) { if (update instanceof ViewMediafile && this.attachments_id.includes(update.id)) {
if (this.topic && this.attachments_id && this.attachments_id.includes(update.id)) {
const attachmentIndex = this.attachments.findIndex(mediafile => mediafile.id === update.id); const attachmentIndex = this.attachments.findIndex(mediafile => mediafile.id === update.id);
this.attachments[attachmentIndex] = update as Mediafile; if (attachmentIndex < 0) {
this.attachments.push(update);
} else {
this.attachments[attachmentIndex] = update;
} }
} }
if (update instanceof ViewItem && this.agenda_item_id === update.id) {
this._agendaItem = update;
}
} }
} }

View File

@ -4,7 +4,7 @@ import { BaseFilterListService, OsFilter, OsFilterOption } from 'app/core/ui-ser
import { Item, itemVisibilityChoices } from 'app/shared/models/agenda/item'; import { Item, itemVisibilityChoices } from 'app/shared/models/agenda/item';
import { ViewItem } from '../models/view-item'; import { ViewItem } from '../models/view-item';
import { StorageService } from 'app/core/core-services/storage.service'; import { StorageService } from 'app/core/core-services/storage.service';
import { AgendaRepositoryService } from 'app/core/repositories/agenda/agenda-repository.service'; import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -19,7 +19,7 @@ export class AgendaFilterListService extends BaseFilterListService<Item, ViewIte
* @param store * @param store
* @param repo * @param repo
*/ */
public constructor(store: StorageService, repo: AgendaRepositoryService) { public constructor(store: StorageService, repo: ItemRepositoryService) {
super(store, repo); super(store, repo);
this.filterOptions = [ this.filterOptions = [
{ {

View File

@ -1,6 +1,7 @@
import { AppConfig } from '../../core/app-config'; import { AppConfig } from '../../core/app-config';
import { Assignment } from '../../shared/models/assignments/assignment'; import { Assignment } from '../../shared/models/assignments/assignment';
import { AssignmentRepositoryService } from 'app/core/repositories/assignments/assignment-repository.service'; import { AssignmentRepositoryService } from 'app/core/repositories/assignments/assignment-repository.service';
import { ViewAssignment } from './models/view-assignment';
export const AssignmentsAppConfig: AppConfig = { export const AssignmentsAppConfig: AppConfig = {
name: 'assignments', name: 'assignments',
@ -8,6 +9,7 @@ export const AssignmentsAppConfig: AppConfig = {
{ {
collectionString: 'assignments/assignment', collectionString: 'assignments/assignment',
model: Assignment, model: Assignment,
viewModel: ViewAssignment,
searchOrder: 3, searchOrder: 3,
repository: AssignmentRepositoryService repository: AssignmentRepositoryService
} }

View File

@ -1,14 +1,17 @@
import { BaseViewModel } from '../../base/base-view-model';
import { Assignment } from 'app/shared/models/assignments/assignment'; import { Assignment } from 'app/shared/models/assignments/assignment';
import { Tag } from 'app/shared/models/core/tag'; import { BaseAgendaViewModel } from 'app/site/base/base-agenda-view-model';
import { User } from 'app/shared/models/users/user'; import { SearchRepresentation } from 'app/core/ui-services/search.service';
import { Item } from 'app/shared/models/agenda/item'; import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
import { ViewUser } from 'app/site/users/models/view-user';
import { ViewItem } from 'app/site/agenda/models/view-item';
import { ViewTag } from 'app/site/tags/models/view-tag';
import { BaseViewModel } from 'app/site/base/base-view-model';
export class ViewAssignment extends BaseViewModel { export class ViewAssignment extends BaseAgendaViewModel {
private _assignment: Assignment; private _assignment: Assignment;
private _relatedUser: User[]; private _relatedUser: ViewUser[];
private _agendaItem: Item; private _agendaItem: ViewItem;
private _tags: Tag[]; private _tags: ViewTag[];
public get id(): number { public get id(): number {
return this._assignment ? this._assignment.id : null; return this._assignment ? this._assignment.id : null;
@ -18,15 +21,15 @@ export class ViewAssignment extends BaseViewModel {
return this._assignment; return this._assignment;
} }
public get candidates(): User[] { public get candidates(): ViewUser[] {
return this._relatedUser; return this._relatedUser;
} }
public get agendaItem(): Item { public get agendaItem(): ViewItem {
return this._agendaItem; return this._agendaItem;
} }
public get tags(): Tag[] { public get tags(): ViewTag[] {
return this._tags; return this._tags;
} }
@ -41,17 +44,35 @@ export class ViewAssignment extends BaseViewModel {
return this.candidates ? this.candidates.length : 0; return this.candidates ? this.candidates.length : 0;
} }
public constructor(assignment: Assignment, relatedUser: User[], agendaItem?: Item, tags?: Tag[]) { public constructor(assignment: Assignment, relatedUser?: ViewUser[], agendaItem?: ViewItem, tags?: ViewTag[]) {
super(); super('Election');
this._assignment = assignment; this._assignment = assignment;
this._relatedUser = relatedUser; this._relatedUser = relatedUser;
this._agendaItem = agendaItem; this._agendaItem = agendaItem;
this._tags = tags; this._tags = tags;
} }
public updateValues(): void {} public updateDependencies(update: BaseViewModel): void {
console.log('TODO: assignment updateDependencies');
}
public getAgendaItem(): ViewItem {
return this.agendaItem;
}
public getTitle(): string { public getTitle(): string {
return this.assignment ? this.assignment.title : null; return this.assignment.title;
}
public formatForSearch(): SearchRepresentation {
throw new Error('TODO');
}
public getDetailStateURL(): string {
return 'TODO';
}
public getSlide(): ProjectorElementBuildDeskriptor {
throw new Error('TODO');
} }
} }

View File

@ -1,4 +1,5 @@
import { DetailNavigable } from './detail-navigable'; import { DetailNavigable } from '../../shared/models/base/detail-navigable';
import { ViewItem } from '../agenda/models/view-item';
/** /**
* An Interface for all extra information needed for content objects of items. * An Interface for all extra information needed for content objects of items.
@ -13,4 +14,14 @@ export interface AgendaInformation extends DetailNavigable {
* Should return the title for the list of speakers view. * Should return the title for the list of speakers view.
*/ */
getAgendaTitleWithType(): string; getAgendaTitleWithType(): string;
/**
* An (optional) descriptive text to be exported in the CSV.
*/
getCSVExportText(): string;
/**
* Get access to the agenda item
*/
getAgendaItem(): ViewItem;
} }

View File

@ -1,22 +1,13 @@
import { AgendaInformation } from './agenda-information'; import { AgendaInformation } from 'app/site/base/agenda-information';
import { Searchable } from './searchable'; import { BaseProjectableViewModel } from './base-projectable-view-model';
import { SearchRepresentation } from 'app/core/ui-services/search.service'; import { SearchRepresentation } from 'app/core/ui-services/search.service';
import { BaseModel } from './base-model'; import { ViewItem } from '../agenda/models/view-item';
/** /**
* A base model for models, that can be content objects in the agenda. Provides title and navigation * Base view class for projectable models.
* information for the agenda.
*/ */
export abstract class AgendaBaseModel extends BaseModel<AgendaBaseModel> implements AgendaInformation, Searchable { export abstract class BaseAgendaViewModel extends BaseProjectableViewModel implements AgendaInformation {
/** public abstract getAgendaItem(): ViewItem;
* A model that can be a content object of an agenda item.
* @param collectionString
* @param verboseName
* @param input
*/
protected constructor(collectionString: string, verboseName: string, input?: any) {
super(collectionString, verboseName, input);
}
/** /**
* @returns the agenda title * @returns the agenda title
@ -41,14 +32,10 @@ export abstract class AgendaBaseModel extends BaseModel<AgendaBaseModel> impleme
return ''; return '';
} }
public abstract getDetailStateURL(): string;
/** /**
* Should return a string representation of the object, so there can be searched for. * Should return a string representation of the object, so there can be searched for.
*/ */
public abstract formatForSearch(): SearchRepresentation; public abstract formatForSearch(): SearchRepresentation;
/**
* Should return the URL to the detail view. Used for the agenda, that the
* user can navigate to the content object.
*/
public abstract getDetailStateURL(): string;
} }

View File

@ -4,6 +4,10 @@ import { BaseViewModel } from './base-view-model';
/** /**
* Base view class for projectable models. * Base view class for projectable models.
*/ */
export abstract class BaseProjectableModel extends BaseViewModel implements Projectable { export abstract class BaseProjectableViewModel extends BaseViewModel implements Projectable {
public constructor(verboseName: string) {
super(verboseName);
}
public abstract getSlide(): ProjectorElementBuildDeskriptor; public abstract getSlide(): ProjectorElementBuildDeskriptor;
} }

View File

@ -1,6 +1,7 @@
import { Displayable } from '../../shared/models/base/displayable'; import { Displayable } from './displayable';
import { Identifiable } from '../../shared/models/base/identifiable'; import { Identifiable } from '../../shared/models/base/identifiable';
import { Deserializable } from 'app/shared/models/base/deserializable';
export type ViewModelConstructor<T extends BaseViewModel> = new (...args: any[]) => T;
/** /**
* Base class for view models. alls view models should have titles. * Base class for view models. alls view models should have titles.
@ -11,7 +12,32 @@ export abstract class BaseViewModel implements Displayable, Identifiable {
*/ */
public abstract id: number; public abstract id: number;
public abstract updateValues(update: Deserializable): void; /**
* Children should also have a verbose name for generic display purposes
*/
protected _verboseName: string;
public constructor(verboseName: string) {
this._verboseName = verboseName;
}
/**
* Returns the verbose name. Makes it plural by adding a 's'.
*
* @param plural If the name should be plural
* @returns the verbose name of the model
*/
public getVerboseName(plural: boolean = false): string {
if (plural) {
return this._verboseName + 's'; // I love english. This works for all our models (participantS, electionS,
// topicS, motionS, (media)fileS, motion blockS, commentS, personal noteS, projectorS, messageS, countdownS, ...)
// Just categorIES need to overwrite this...
} else {
return this._verboseName;
}
}
public abstract updateDependencies(update: BaseViewModel): void;
public abstract getTitle(): string; public abstract getTitle(): string;

View File

@ -1,4 +1,4 @@
import { Displayable } from 'app/shared/models/base/displayable'; import { Displayable } from 'app/site/base/displayable';
import { IdentifiableProjectorElement } from 'app/shared/models/core/projector'; import { IdentifiableProjectorElement } from 'app/shared/models/core/projector';
import { SlideOptions } from './slide-options'; import { SlideOptions } from './slide-options';

View File

@ -1,5 +1,5 @@
import { DetailNavigable } from './detail-navigable';
import { SearchRepresentation } from 'app/core/ui-services/search.service'; import { SearchRepresentation } from 'app/core/ui-services/search.service';
import { DetailNavigable } from 'app/shared/models/base/detail-navigable';
/** /**
* Asserts, if the given object is searchable. * Asserts, if the given object is searchable.

View File

@ -1,10 +1,18 @@
import { AppConfig } from '../../core/app-config'; import { AppConfig } from '../../core/app-config';
import { ChatMessage } from '../../shared/models/core/chat-message'; import { ChatMessage } from '../../shared/models/core/chat-message';
import { ChatMessageRepositoryService } from 'app/core/repositories/common/chatmessage-repository.service'; import { ChatMessageRepositoryService } from 'app/core/repositories/common/chatmessage-repository.service';
import { ViewChatMessage } from './models/view-chatmessage';
export const CommonAppConfig: AppConfig = { export const CommonAppConfig: AppConfig = {
name: 'common', name: 'common',
models: [{ collectionString: 'core/chat-message', model: ChatMessage, repository: ChatMessageRepositoryService }], models: [
{
collectionString: 'core/chat-message',
model: ChatMessage,
viewModel: ViewChatMessage,
repository: ChatMessageRepositoryService
}
],
mainMenuEntries: [ mainMenuEntries: [
{ {
route: '/', route: '/',

View File

@ -1,9 +1,10 @@
import { Component, OnInit } from '@angular/core'; 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 { TranslateService } from '@ngx-translate/core'; // showcase import { TranslateService } from '@ngx-translate/core'; // showcase
import { BaseComponent } from 'app/base.component';
// for testing the DS and BaseModel // for testing the DS and BaseModel
import { Config } from 'app/shared/models/core/config'; import { Config } from 'app/shared/models/core/config';
import { DataStoreService } from 'app/core/core-services/data-store.service'; import { DataStoreService } from 'app/core/core-services/data-store.service';

View File

@ -2,30 +2,28 @@ import { ChatMessage } from 'app/shared/models/core/chat-message';
import { BaseViewModel } from 'app/site/base/base-view-model'; import { BaseViewModel } from 'app/site/base/base-view-model';
export class ViewChatMessage extends BaseViewModel { export class ViewChatMessage extends BaseViewModel {
private _message: ChatMessage; private _chatMessage: ChatMessage;
public get chatmessage(): ChatMessage { public get chatmessage(): ChatMessage {
return this._message ? this._message : null; return this._chatMessage;
} }
public get id(): number { public get id(): number {
return this.chatmessage ? this.chatmessage.id : null; return this.chatmessage.id;
} }
public get message(): string { public get message(): string {
return this.chatmessage ? this.chatmessage.message : null; return this.chatmessage.message;
} }
public constructor(message?: ChatMessage) { public constructor(message?: ChatMessage) {
super(); super('Chatmessage');
this._message = message; this._chatMessage = message;
} }
public getTitle(): string { public getTitle(): string {
return 'Chatmessage'; return 'Chatmessage';
} }
public updateValues(message: ChatMessage): void { public updateDependencies(message: BaseViewModel): void {}
console.log('Update message TODO with vals:', message);
}
} }

View File

@ -1,15 +1,15 @@
import { Component, OnInit, Input, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core'; import { Component, OnInit, Input, ChangeDetectorRef, ChangeDetectionStrategy } from '@angular/core';
import { FormGroup, FormBuilder } from '@angular/forms'; import { FormGroup, FormBuilder } from '@angular/forms';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
import { distinctUntilChanged } from 'rxjs/operators';
import { distinctUntilChanged } from 'rxjs/operators';
import { DateTimeAdapter } from 'ng-pick-datetime'; import { DateTimeAdapter } from 'ng-pick-datetime';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BaseComponent } from '../../../../base.component'; import { BaseComponent } from 'app/base.component';
import { ConfigRepositoryService } from '../../services/config-repository.service';
import { ParentErrorStateMatcher } from 'app/shared/parent-error-state-matcher'; import { ParentErrorStateMatcher } from 'app/shared/parent-error-state-matcher';
import { ViewConfig } from '../../models/view-config'; import { ViewConfig } from '../../models/view-config';
import { ConfigRepositoryService } from 'app/core/repositories/config/config-repository.service';
/** /**
* List view for the categories. * List view for the categories.

View File

@ -1,7 +1,7 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { ConfigRepositoryService, ConfigGroup } from '../../services/config-repository.service'; import { ConfigRepositoryService, ConfigGroup } from '../../../../core/repositories/config/config-repository.service';
import { BaseComponent } from '../../../../base.component'; import { BaseComponent } from '../../../../base.component';
/** /**

View File

@ -1,10 +1,13 @@
import { AppConfig } from '../../core/app-config'; import { AppConfig } from '../../core/app-config';
import { Config } from '../../shared/models/core/config'; import { Config } from '../../shared/models/core/config';
import { ConfigRepositoryService } from './services/config-repository.service'; import { ConfigRepositoryService } from '../../core/repositories/config/config-repository.service';
import { ViewConfig } from './models/view-config';
export const ConfigAppConfig: AppConfig = { export const ConfigAppConfig: AppConfig = {
name: 'settings', name: 'settings',
models: [{ collectionString: 'core/config', model: Config, repository: ConfigRepositoryService }], models: [
{ collectionString: 'core/config', model: Config, viewModel: ViewConfig, repository: ConfigRepositoryService }
],
mainMenuEntries: [ mainMenuEntries: [
{ {
route: '/settings', route: '/settings',

View File

@ -94,7 +94,7 @@ export class ViewConfig extends BaseViewModel {
} }
public constructor(config: Config) { public constructor(config: Config) {
super(); super('Config');
this._config = config; this._config = config;
} }
@ -102,9 +102,7 @@ export class ViewConfig extends BaseViewModel {
return this.label; return this.label;
} }
public updateValues(update: Config): void { public updateDependencies(update: BaseViewModel): void {}
this._config = update;
}
/** /**
* Returns the time this config field needs to debounce before sending a request to the server. * Returns the time this config field needs to debounce before sending a request to the server.

View File

@ -2,16 +2,15 @@ import { Component, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
import { MatSnackBar } from '@angular/material'; import { MatSnackBar } from '@angular/material';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Subject } from 'rxjs';
import { Subject } from 'rxjs';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { ListViewBaseComponent } from 'app/site/base/list-view-base'; import { ListViewBaseComponent } from 'app/site/base/list-view-base';
import { HistoryRepositoryService } from 'app/core/repositories/history/history-repository.service'; import { HistoryRepositoryService } from 'app/core/repositories/history/history-repository.service';
import { DataStoreService } from 'app/core/core-services/data-store.service';
import { isDetailNavigable } from 'app/shared/models/base/detail-navigable'; import { isDetailNavigable } from 'app/shared/models/base/detail-navigable';
import { ViewHistory } from '../../models/view-history'; import { ViewHistory } from '../../models/view-history';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
/** /**
* A list view for the history. * A list view for the history.
@ -42,7 +41,7 @@ export class HistoryListComponent extends ListViewBaseComponent<ViewHistory> imp
translate: TranslateService, translate: TranslateService,
matSnackBar: MatSnackBar, matSnackBar: MatSnackBar,
private repo: HistoryRepositoryService, private repo: HistoryRepositoryService,
private DS: DataStoreService, private viewModelStore: ViewModelStoreService,
private router: Router private router: Router
) { ) {
super(titleService, translate, matSnackBar); super(titleService, translate, matSnackBar);
@ -89,7 +88,7 @@ export class HistoryListComponent extends ListViewBaseComponent<ViewHistory> imp
* @returns the title of an old element or null if it could not be found * @returns the title of an old element or null if it could not be found
*/ */
public getElementInfo(history: ViewHistory): string { public getElementInfo(history: ViewHistory): string {
const oldElementTitle = this.repo.getOldModelInfo(history.getCollectionString(), history.getModelID()); const oldElementTitle = this.repo.getOldModelInfo(history.getCollectionString(), history.getModelId());
if (oldElementTitle) { if (oldElementTitle) {
return oldElementTitle; return oldElementTitle;
@ -104,22 +103,19 @@ export class HistoryListComponent extends ListViewBaseComponent<ViewHistory> imp
* *
* @param history Represents the selected element * @param history Represents the selected element
*/ */
public onClickRow(history: ViewHistory): void { public async onClickRow(history: ViewHistory): Promise<void> {
this.repo.browseHistory(history).then(() => { await this.repo.browseHistory(history);
const element = this.DS.get(history.getCollectionString(), history.getModelID()); const element = this.viewModelStore.get(history.getCollectionString(), history.getModelId());
let message = this.translate.instant('Temporarily reset OpenSlides to the state from '); let message = this.translate.instant('Temporarily reset OpenSlides to the state from ');
message += history.getLocaleString('DE-de') + '. '; message += history.getLocaleString('DE-de') + '. ';
if (isDetailNavigable(element)) { if (isDetailNavigable(element)) {
message += this.translate.instant( message += this.translate.instant('You will be redirected to the detail state of the last changed item.');
'You will be redirected to the detail state of the last changed item.'
);
this.raiseError(message); this.raiseError(message);
this.router.navigate([element.getDetailStateURL()]); this.router.navigate([element.getDetailStateURL()]);
} else { } else {
this.raiseError(message); this.raiseError(message);
} }
});
} }
/** /**

View File

@ -1,6 +1,7 @@
import { AppConfig } from '../../core/app-config'; import { AppConfig } from '../../core/app-config';
import { History } from 'app/shared/models/core/history'; import { History } from 'app/shared/models/core/history';
import { HistoryRepositoryService } from 'app/core/repositories/history/history-repository.service'; import { HistoryRepositoryService } from 'app/core/repositories/history/history-repository.service';
import { ViewHistory } from './models/view-history';
/** /**
* Config object for history. * Config object for history.
@ -8,7 +9,14 @@ import { HistoryRepositoryService } from 'app/core/repositories/history/history-
*/ */
export const HistoryAppConfig: AppConfig = { export const HistoryAppConfig: AppConfig = {
name: 'history', name: 'history',
models: [{ collectionString: 'core/history', model: History, repository: HistoryRepositoryService }], models: [
{
collectionString: 'core/history',
model: History,
viewModel: ViewHistory,
repository: HistoryRepositoryService
}
],
mainMenuEntries: [ mainMenuEntries: [
{ {
route: '/history', route: '/history',

Some files were not shown because too many files have changed in this diff Show More