Split AgendaItem and ListOfSpeakers

Server:
- ListOfSpeakers (LOS) is now a speprate model, containing of an id,
speakers, closed and a content_object.
- Moved all speaker related views from ItemViewSet to the new
ListOfSpeakersViewSet.
- Make Mixins for content objects of items and lists of speakers.
- Migrations: Move the lists of speakers from items to the LOS model.

Client:
- Removed the speaker repo and moved functionality to the new
ListOfSpeakersRepositoryService.
- Splitted base classes for agenda item content objects to items and
LOS.
- CurrentAgendaItemService -> CurrentListOfSpeakersSerivce
- Cleaned up the list of speakers component.
This commit is contained in:
FinnStutzenstein 2019-04-23 16:57:35 +02:00
parent 61057a6e09
commit 9f12763f8b
162 changed files with 3353 additions and 2425 deletions

View File

@ -8,7 +8,7 @@ import { BaseViewModel, ViewModelConstructor } from 'app/site/base/base-view-mod
interface BaseModelEntry { interface BaseModelEntry {
collectionString: string; collectionString: string;
repository: Type<BaseRepository<any, any>>; repository: Type<BaseRepository<any, any, any>>;
model: ModelConstructor<BaseModel>; model: ModelConstructor<BaseModel>;
} }

View File

@ -69,7 +69,7 @@ export class AppLoadService {
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, any> = null;
repository = this.injector.get(entry.repository); repository = this.injector.get(entry.repository);
repositories.push(repository); repositories.push(repository);
this.modelMapper.registerCollectionElement( this.modelMapper.registerCollectionElement(

View File

@ -123,11 +123,7 @@ export class AutoupdateService {
// Add the objects to the DataStore. // Add the objects to the DataStore.
for (const collection of Object.keys(autoupdate.changed)) { for (const collection of Object.keys(autoupdate.changed)) {
if (this.modelMapper.isCollectionRegistered(collection)) { await this.DS.add(this.mapObjectsToBaseModels(collection, autoupdate.changed[collection]));
await this.DS.add(this.mapObjectsToBaseModels(collection, autoupdate.changed[collection]));
} else {
console.error(`Unregistered collection "${collection}". Ignore it.`);
}
} }
await this.DS.flushToStorage(autoupdate.to_change_id); await this.DS.flushToStorage(autoupdate.to_change_id);
@ -140,14 +136,21 @@ export class AutoupdateService {
} }
/** /**
* Creates baseModels for each plain object * Creates baseModels for each plain object. If the collection is not registered,
* A console error will be issued and an empty list returned.
*
* @param collection The collection all models have to be from. * @param collection The collection all models have to be from.
* @param models All models that should be mapped to BaseModels * @param models All models that should be mapped to BaseModels
* @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); if (this.modelMapper.isCollectionRegistered(collection)) {
return models.map(model => new targetClass(model)); const targetClass = this.modelMapper.getModelConstructor(collection);
return models.map(model => new targetClass(model));
} else {
console.error(`Unregistered collection "${collection}". Ignore it.`);
return [];
}
} }
/** /**

View File

@ -2,7 +2,7 @@ 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'; import { ViewModelConstructor, BaseViewModel, TitleInformation } from 'app/site/base/base-view-model';
/** /**
* Unifies the ModelConstructor and ViewModelConstructor. * Unifies the ModelConstructor and ViewModelConstructor.
@ -15,12 +15,12 @@ interface UnifiedConstructors {
/** /**
* Every types supported: (View)ModelConstructors, repos and collectionstrings. * Every types supported: (View)ModelConstructors, repos and collectionstrings.
*/ */
type TypeIdentifier = UnifiedConstructors | BaseRepository<any, any> | string; type TypeIdentifier = UnifiedConstructors | BaseRepository<any, any, any> | string;
type CollectionStringMappedTypes = [ type CollectionStringMappedTypes = [
ModelConstructor<BaseModel>, ModelConstructor<BaseModel>,
ViewModelConstructor<BaseViewModel>, ViewModelConstructor<BaseViewModel>,
BaseRepository<BaseViewModel, BaseModel> BaseRepository<BaseViewModel, BaseModel, TitleInformation>
]; ];
/** /**
@ -50,7 +50,7 @@ export class CollectionStringMapperService {
collectionString: string, collectionString: string,
model: ModelConstructor<M>, model: ModelConstructor<M>,
viewModel: ViewModelConstructor<V>, viewModel: ViewModelConstructor<V>,
repository: BaseRepository<V, M> repository: BaseRepository<V, M, TitleInformation>
): void { ): void {
this.collectionStringMapping[collectionString] = [model, viewModel, repository]; this.collectionStringMapping[collectionString] = [model, viewModel, repository];
} }
@ -98,18 +98,18 @@ export class CollectionStringMapperService {
* @param obj The object to get the repository from. * @param obj The object to get the repository from.
* @returns the repository * @returns the repository
*/ */
public getRepository<V extends BaseViewModel, M extends BaseModel>( public getRepository<V extends BaseViewModel, M extends BaseModel, T extends TitleInformation>(
obj: TypeIdentifier obj: TypeIdentifier
): BaseRepository<V, M> | null { ): BaseRepository<V & T, M, T> | null {
if (this.isCollectionRegistered(this.getCollectionString(obj))) { if (this.isCollectionRegistered(this.getCollectionString(obj))) {
return this.collectionStringMapping[this.getCollectionString(obj)][2] as BaseRepository<V, M>; return this.collectionStringMapping[this.getCollectionString(obj)][2] as BaseRepository<V & T, M, T>;
} }
} }
/** /**
* @returns all registered repositories. * @returns all registered repositories.
*/ */
public getAllRepositories(): BaseRepository<any, any>[] { public getAllRepositories(): BaseRepository<any, any, any>[] {
return Object.values(this.collectionStringMapping).map((types: CollectionStringMappedTypes) => types[2]); return Object.values(this.collectionStringMapping).map((types: CollectionStringMappedTypes) => types[2]);
} }
} }

View File

@ -120,7 +120,12 @@ export class HttpService {
return error; return error;
} }
if (!e.error) { if (e.status === 405) {
// this should only happen, if the url is wrong -> a bug.
error += this.translate.instant(
'The requested method is not allowed. Please contact your system administrator.'
);
} else if (!e.error) {
error += this.translate.instant("The server didn't respond."); error += this.translate.instant("The server didn't respond.");
} else if (typeof e.error === 'object') { } else if (typeof e.error === 'object') {
if (e.error.detail) { if (e.error.detail) {

View File

@ -20,10 +20,10 @@ export class ViewModelStoreService {
* *
* @param collectionType The collection string or constructor. * @param collectionType The collection string or constructor.
*/ */
private getRepository<T extends BaseViewModel>( private getRepository<V extends BaseViewModel>(
collectionType: ViewModelConstructor<T> | string collectionType: ViewModelConstructor<V> | string
): BaseRepository<T, any> { ): BaseRepository<V, any, any> {
return this.mapperService.getRepository(collectionType) as BaseRepository<T, any>; return this.mapperService.getRepository(collectionType) as BaseRepository<V, any, any>;
} }
/** /**
@ -32,7 +32,7 @@ export class ViewModelStoreService {
* @param collectionString The collection of the view model * @param collectionString The collection of the view model
* @param id The id of the view model * @param id The id of the view model
*/ */
public get<T extends BaseViewModel>(collectionType: ViewModelConstructor<T> | string, id: number): T { public get<V extends BaseViewModel>(collectionType: ViewModelConstructor<V> | string, id: number): V {
return this.getRepository(collectionType).getViewModel(id); return this.getRepository(collectionType).getViewModel(id);
} }

View File

@ -4,33 +4,41 @@ import { map } from 'rxjs/operators';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BaseRepository } from '../base-repository';
import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.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';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';
import { HttpService } from 'app/core/core-services/http.service'; import { HttpService } from 'app/core/core-services/http.service';
import { Item } from 'app/shared/models/agenda/item'; import { Item } from 'app/shared/models/agenda/item';
import { ViewItem } from 'app/site/agenda/models/view-item';
import { TreeIdNode } from 'app/core/ui-services/tree.service'; import { TreeIdNode } from 'app/core/ui-services/tree.service';
import { BaseAgendaViewModel } from 'app/site/base/base-agenda-view-model'; import { ViewItem, ItemTitleInformation } from 'app/site/agenda/models/view-item';
import {
BaseViewModelWithAgendaItem,
isBaseViewModelWithAgendaItem
} from 'app/site/base/base-view-model-with-agenda-item';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { BaseViewModel } from 'app/site/base/base-view-model'; import { BaseViewModel } from 'app/site/base/base-view-model';
import { BaseAgendaContentObjectRepository } from '../base-agenda-content-object-repository';
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 { Topic } from 'app/shared/models/topics/topic'; import { Topic } from 'app/shared/models/topics/topic';
import { Assignment } from 'app/shared/models/assignments/assignment'; import { Assignment } from 'app/shared/models/assignments/assignment';
import { BaseIsAgendaItemContentObjectRepository } from '../base-is-agenda-item-content-object-repository';
import { BaseHasContentObjectRepository } from '../base-has-content-object-repository';
/** /**
* Repository service for users * Repository service for items
* *
* Documentation partially provided in {@link BaseRepository} * Documentation partially provided in {@link BaseRepository}
*/ */
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class ItemRepositoryService extends BaseRepository<ViewItem, Item> { export class ItemRepositoryService extends BaseHasContentObjectRepository<
ViewItem,
Item,
BaseViewModelWithAgendaItem,
ItemTitleInformation
> {
/** /**
* Contructor for agenda repository. * Contructor for agenda repository.
* *
@ -57,21 +65,24 @@ export class ItemRepositoryService extends BaseRepository<ViewItem, Item> {
MotionBlock MotionBlock
]); ]);
this.setSortFunction((a, b) => { this.setSortFunction((a, b) => a.weight - b.weight);
// TODO: In some occasions weight will be undefined, if the user has not the correct set of permission.
// That should not be the case here.
if (a.weight && b.weight) {
return a.weight - b.weight;
} else {
return -1;
}
});
} }
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Items' : 'Item'); return this.translate.instant(plural ? 'Items' : 'Item');
}; };
public getTitle = (titleInformation: ItemTitleInformation) => {
if (titleInformation.contentObject) {
return titleInformation.contentObject.getAgendaListTitle();
} else {
const repo = this.collectionStringMapperService.getRepository(
titleInformation.contentObjectData.collection
) as BaseIsAgendaItemContentObjectRepository<any, any, any>;
return repo.getAgendaListTitle(titleInformation.title_information);
}
};
/** /**
* Creates the viewItem out of a given item * Creates the viewItem out of a given item
* *
@ -80,31 +91,16 @@ export class ItemRepositoryService extends BaseRepository<ViewItem, Item> {
*/ */
public createViewModel(item: Item): ViewItem { public createViewModel(item: Item): ViewItem {
const contentObject = this.getContentObject(item); const contentObject = this.getContentObject(item);
const viewItem = new ViewItem(item, contentObject); return new ViewItem(item, contentObject);
viewItem.getVerboseName = this.getVerboseName;
viewItem.getTitle = () => {
const numberPrefix = viewItem.itemNumber ? `${viewItem.itemNumber} · ` : '';
if (viewItem.contentObject) {
return numberPrefix + viewItem.contentObject.getAgendaTitleWithType();
} else {
const repo = this.collectionStringMapperService.getRepository(
viewItem.item.content_object.collection
) as BaseAgendaContentObjectRepository<any, any>;
return numberPrefix + repo.getAgendaTitleWithType(viewItem.title_information);
}
};
viewItem.getListTitle = viewItem.getTitle;
return viewItem;
} }
/** /**
* Returns the corresponding content object to a given {@link Item} as an {@link AgendaBaseViewModel} * Returns the corresponding content object to a given {@link Item} as an {@link BaseAgendaItemViewModel}
* *
* @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): BaseAgendaViewModel { public getContentObject(agendaItem: Item): BaseViewModelWithAgendaItem {
const contentObject = this.viewModelStoreService.get<BaseViewModel>( const contentObject = this.viewModelStoreService.get<BaseViewModel>(
agendaItem.content_object.collection, agendaItem.content_object.collection,
agendaItem.content_object.id agendaItem.content_object.id
@ -112,13 +108,13 @@ export class ItemRepositoryService extends BaseRepository<ViewItem, Item> {
if (!contentObject) { if (!contentObject) {
return null; return null;
} }
if (contentObject instanceof BaseAgendaViewModel) { if (isBaseViewModelWithAgendaItem(contentObject)) {
return contentObject as BaseAgendaViewModel; return contentObject;
} 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 AgendaBaseViewModel.` }) of item ${agendaItem.id} is not a BaseAgendaItemViewModel.`
); );
} }
} }

View File

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

View File

@ -0,0 +1,227 @@
import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
import { DataSendService } from 'app/core/core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.service';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { ViewListOfSpeakers, ListOfSpeakersTitleInformation } from 'app/site/agenda/models/view-list-of-speakers';
import { ListOfSpeakers } from 'app/shared/models/agenda/list-of-speakers';
import {
BaseViewModelWithListOfSpeakers,
isBaseViewModelWithListOfSpeakers
} from 'app/site/base/base-view-model-with-list-of-speakers';
import { BaseViewModel } from 'app/site/base/base-view-model';
import { ViewSpeaker } from 'app/site/agenda/models/view-speaker';
import { ViewUser } from 'app/site/users/models/view-user';
import { Identifiable } from 'app/shared/models/base/identifiable';
import { HttpService } from 'app/core/core-services/http.service';
import { BaseIsListOfSpeakersContentObjectRepository } from '../base-is-list-of-speakers-content-object-repository';
import { BaseHasContentObjectRepository } from '../base-has-content-object-repository';
import { Topic } from 'app/shared/models/topics/topic';
import { Assignment } from 'app/shared/models/assignments/assignment';
import { Motion } from 'app/shared/models/motions/motion';
import { MotionBlock } from 'app/shared/models/motions/motion-block';
import { Mediafile } from 'app/shared/models/mediafiles/mediafile';
import { ItemRepositoryService } from './item-repository.service';
import { User } from 'app/shared/models/users/user';
/**
* Repository service for lists of speakers
*
* Documentation partially provided in {@link BaseRepository}
*/
@Injectable({
providedIn: 'root'
})
export class ListOfSpeakersRepositoryService extends BaseHasContentObjectRepository<
ViewListOfSpeakers,
ListOfSpeakers,
BaseViewModelWithListOfSpeakers,
ListOfSpeakersTitleInformation
> {
/**
* Contructor for agenda repository.
*
* @param DS The DataStore
* @param httpService OpenSlides own HttpService
* @param mapperService OpenSlides mapping service for collection strings
* @param config Read config variables
* @param dataSend send models to the server
* @param treeService sort the data according to weight and parents
*/
public constructor(
DS: DataStoreService,
dataSend: DataSendService,
mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService,
translate: TranslateService,
private httpService: HttpService,
private itemRepo: ItemRepositoryService
) {
super(DS, dataSend, mapperService, viewModelStoreService, translate, ListOfSpeakers, [
Topic,
Assignment,
Motion,
MotionBlock,
Mediafile,
User
]);
}
public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Lists of speakers' : 'List of speakers');
};
public getTitle = (titleInformation: ListOfSpeakersTitleInformation) => {
if (titleInformation.contentObject) {
return titleInformation.contentObject.getListOfSpeakersTitle();
} else {
const repo = this.collectionStringMapperService.getRepository(
titleInformation.contentObjectData.collection
) as BaseIsListOfSpeakersContentObjectRepository<any, any, any>;
// Try to get the agenda item for this to get the item number
// TODO: This can be resolved with #4738
const item = this.itemRepo.findByContentObject(titleInformation.contentObjectData);
if (item) {
(<any>titleInformation.title_information).agenda_item_number = item.itemNumber;
}
return repo.getListOfSpeakersTitle(titleInformation.title_information);
}
};
/**
* Creates the viewListOfSpeakers out of a given list of speakers
*
* @param listOfSpeakers the list fo speakers that should be converted to view item
* @returns a new view list fo speakers
*/
public createViewModel(listOfSpeakers: ListOfSpeakers): ViewListOfSpeakers {
const contentObject = this.getContentObject(listOfSpeakers);
const speakers = this.getSpeakers(listOfSpeakers);
return new ViewListOfSpeakers(listOfSpeakers, speakers, contentObject);
}
private getSpeakers(listOfSpeakers: ListOfSpeakers): ViewSpeaker[] {
return listOfSpeakers.speakers.map(speaker => {
const user = this.viewModelStoreService.get(ViewUser, speaker.user_id);
return new ViewSpeaker(speaker, user);
});
}
/**
* Returns the corresponding content object to a given {@link ListOfSpeakers} as an {@link BaseListOfSpeakersViewModel}
*
* @param listOfSpeakers the target list fo speakers
* @returns the content object of the given list of sepakers. Might be null if it was not found.
*/
public getContentObject(listOfSpeakers: ListOfSpeakers): BaseViewModelWithListOfSpeakers {
const contentObject = this.viewModelStoreService.get<BaseViewModel>(
listOfSpeakers.content_object.collection,
listOfSpeakers.content_object.id
);
if (!contentObject) {
return null;
}
if (isBaseViewModelWithListOfSpeakers(contentObject)) {
return contentObject;
} else {
throw new Error(
`The content object (${listOfSpeakers.content_object.collection}, ${
listOfSpeakers.content_object.id
}) of list of speakers ${listOfSpeakers.id} is not a BaseListOfSpeakersViewModel.`
);
}
}
/**
* Add a new speaker to a list of speakers.
* Sends the users id to the server
*
* @param userId {@link User} id of the new speaker
* @param listOfSpeakers the target agenda item
*/
public async createSpeaker(listOfSpeakers: ViewListOfSpeakers, userId: number): Promise<Identifiable> {
const restUrl = this.getRestUrl(listOfSpeakers.id, 'manage_speaker');
return await this.httpService.post<Identifiable>(restUrl, { user: userId });
}
/**
* Removes the given speaker for the list of speakers
*
* @param listOfSpeakers the target list of speakers
* @param speakerId (otional) the speakers id. If no id is given, the speaker with the
* current operator is removed.
*/
public async delete(listOfSpeakers: ViewListOfSpeakers, speakerId?: number): Promise<void> {
const restUrl = this.getRestUrl(listOfSpeakers.id, 'manage_speaker');
await this.httpService.delete(restUrl, speakerId ? { speaker: speakerId } : null);
}
/**
* Deletes all speakers of the given list of speakers.
*
* @param listOfSpeakers the target list of speakers
*/
public async deleteAllSpeakers(listOfSpeakers: ViewListOfSpeakers): Promise<void> {
const restUrl = this.getRestUrl(listOfSpeakers.id, 'manage_speaker');
await this.httpService.delete(restUrl, { speaker: listOfSpeakers.speakers.map(speaker => speaker.id) });
}
/**
* Posts an (manually) sorted speaker list to the server
*
* @param speakerIds array of speaker id numbers
* @param Item the target agenda item
*/
public async sortSpeakers(listOfSpeakers: ViewListOfSpeakers, speakerIds: number[]): Promise<void> {
const restUrl = this.getRestUrl(listOfSpeakers.id, 'sort_speakers');
await this.httpService.post(restUrl, { speakers: speakerIds });
}
/**
* Marks all speakers for a given user
*
* @param userId {@link User} id of the user
* @param marked determine if the user should be marked or not
* @param listOfSpeakers the target list of speakers
*/
public async markSpeaker(listOfSpeakers: ViewListOfSpeakers, speaker: ViewSpeaker, marked: boolean): Promise<void> {
const restUrl = this.getRestUrl(listOfSpeakers.id, 'manage_speaker');
await this.httpService.patch(restUrl, { user: speaker.user.id, marked: marked });
}
/**
* Stops the current speaker
*
* @param listOfSpeakers the target list of speakers
*/
public async stopCurrentSpeaker(listOfSpeakers: ViewListOfSpeakers): Promise<void> {
const restUrl = this.getRestUrl(listOfSpeakers.id, 'speak');
await this.httpService.delete(restUrl);
}
/**
* Sets the given speaker id to speak
*
* @param speakerId the speakers id
* @param listOfSpeakers the target list of speakers
*/
public async startSpeaker(listOfSpeakers: ViewListOfSpeakers, speaker: ViewSpeaker): Promise<void> {
const restUrl = this.getRestUrl(listOfSpeakers.id, 'speak');
await this.httpService.put(restUrl, { speaker: speaker.id });
}
/**
* Helper function get the url to the speaker rest address
*
* @param listOfSpeakersId id of the list of speakers
* @param method the desired speaker action
*/
private getRestUrl(listOfSpeakersId: number, method: 'manage_speaker' | 'sort_speakers' | 'speak'): string {
return `/rest/agenda/list-of-speakers/${listOfSpeakersId}/${method}/`;
}
}

View File

@ -1,17 +0,0 @@
import { TestBed } from '@angular/core/testing';
import { SpeakerRepositoryService } from './speaker-repository.service';
import { E2EImportsModule } from 'e2e-imports.module';
describe('SpeakerRepositoryService', () => {
beforeEach(() =>
TestBed.configureTestingModule({
imports: [E2EImportsModule]
})
);
it('should be created', () => {
const service: SpeakerRepositoryService = TestBed.get(SpeakerRepositoryService);
expect(service).toBeTruthy();
});
});

View File

@ -1,163 +0,0 @@
import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { HttpService } from 'app/core/core-services/http.service';
import { Identifiable } from 'app/shared/models/base/identifiable';
import { Item } from 'app/shared/models/agenda/item';
import { Speaker } from 'app/shared/models/agenda/speaker';
import { ViewSpeaker } from 'app/site/agenda/models/view-speaker';
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';
/**
* Define what actions might occur on speaker lists
*/
type SpeakerAction = 'manage_speaker' | 'sort_speakers' | 'speak';
/**
* Repository for speakers.
*
* Since speakers are no base models, normal repository methods do not apply here.
*/
@Injectable({
providedIn: 'root'
})
export class SpeakerRepositoryService {
/**
* Constructor
*
* @param viewModelStoreService To get the view users
* @param httpService make custom requests
* @param translate translate
*/
public constructor(
private viewModelStoreService: ViewModelStoreService,
private httpService: HttpService,
private translate: TranslateService
) {}
/**
* Add a new speaker to an agenda item.
* Sends the users ID to the server
* Might need another repo
*
* @param speakerId {@link User} id of the new speaker
* @param item the target agenda item
*/
public async create(speakerId: number, item: ViewItem): Promise<void> {
const restUrl = this.getRestUrl(item.id, 'manage_speaker');
await this.httpService.post<Identifiable>(restUrl, { user: speakerId });
}
/**
* Removes the given speaker for the agenda item
*
* @param item the target agenda item
* @param speakerId (otional) the speakers id. If no id is given, the current operator
* is removed.
*/
public async delete(item: ViewItem, speakerId?: number): Promise<void> {
const restUrl = this.getRestUrl(item.id, 'manage_speaker');
await this.httpService.delete(restUrl, speakerId ? { speaker: speakerId } : null);
}
/**
* Creates and returns a new ViewSpeaker out of a speaker model
*
* @param speaker speaker to transform
* @return a new ViewSpeaker
*/
private createViewModel(speaker: Speaker): ViewSpeaker {
const user = this.viewModelStoreService.get(ViewUser, speaker.user_id);
const viewSpeaker = new ViewSpeaker(speaker, user);
viewSpeaker.getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Speakers' : 'Speaker');
};
return viewSpeaker;
}
/**
* Generate viewSpeaker objects from a given agenda Item
*
* @param item agenda Item holding speakers
* @returns the list of view speakers corresponding to the given item
*/
public createSpeakerList(item: Item): ViewSpeaker[] {
let viewSpeakers = [];
const speakers = item.speakers;
if (speakers && speakers.length > 0) {
viewSpeakers = speakers.map(speaker => {
return this.createViewModel(speaker);
});
}
// sort speakers by their weight
viewSpeakers = viewSpeakers.sort((a, b) => a.weight - b.weight);
return viewSpeakers;
}
/**
* Deletes all speakers of the given agenda item.
*
* @param item the target agenda item
*/
public async deleteAllSpeakers(item: ViewItem): Promise<void> {
const restUrl = this.getRestUrl(item.id, 'manage_speaker');
await this.httpService.delete(restUrl, { speaker: item.speakers.map(speaker => speaker.id) });
}
/**
* Posts an (manually) sorted speaker list to the server
*
* @param speakerIds array of speaker id numbers
* @param Item the target agenda item
*/
public async sortSpeakers(speakerIds: number[], item: Item): Promise<void> {
const restUrl = this.getRestUrl(item.id, 'sort_speakers');
await this.httpService.post(restUrl, { speakers: speakerIds });
}
/**
* Marks the current speaker
*
* @param speakerId {@link User} id of the new speaker
* @param mark determine if the user was marked or not
* @param item the target agenda item
*/
public async markSpeaker(speakerId: number, mark: boolean, item: ViewItem): Promise<void> {
const restUrl = this.getRestUrl(item.id, 'manage_speaker');
await this.httpService.patch(restUrl, { user: speakerId, marked: mark });
}
/**
* Stops the current speaker
*
* @param item the target agenda item
*/
public async stopCurrentSpeaker(item: ViewItem): Promise<void> {
const restUrl = this.getRestUrl(item.id, 'speak');
await this.httpService.delete(restUrl);
}
/**
* Sets the given speaker ID to Speak
*
* @param speakerId the speakers id
* @param item the target agenda item
*/
public async startSpeaker(speakerId: number, item: ViewItem): Promise<void> {
const restUrl = this.getRestUrl(item.id, 'speak');
await this.httpService.put(restUrl, { speaker: speakerId });
}
/**
* Helper function get the url to the speaker rest address
*
* @param itemId id of the agenda item
* @param suffix the desired speaker action
*/
private getRestUrl(itemId: number, suffix: SpeakerAction): string {
return `/rest/agenda/item/${itemId}/${suffix}/`;
}
}

View File

@ -4,16 +4,14 @@ import { TranslateService } from '@ngx-translate/core';
import { Assignment } from 'app/shared/models/assignments/assignment'; import { Assignment } from 'app/shared/models/assignments/assignment';
import { AssignmentRelatedUser } from 'app/shared/models/assignments/assignment-related-user'; import { AssignmentRelatedUser } from 'app/shared/models/assignments/assignment-related-user';
import { BaseAgendaContentObjectRepository } from '../base-agenda-content-object-repository';
import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
import { DataSendService } from 'app/core/core-services/data-send.service'; import { DataSendService } from 'app/core/core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';
import { HttpService } from 'app/core/core-services/http.service'; import { HttpService } from 'app/core/core-services/http.service';
import { Item } from 'app/shared/models/agenda/item';
import { AssignmentPoll } from 'app/shared/models/assignments/assignment-poll'; import { AssignmentPoll } from 'app/shared/models/assignments/assignment-poll';
import { Tag } from 'app/shared/models/core/tag'; import { Tag } from 'app/shared/models/core/tag';
import { User } from 'app/shared/models/users/user'; import { User } from 'app/shared/models/users/user';
import { ViewAssignment } from 'app/site/assignments/models/view-assignment'; import { ViewAssignment, AssignmentTitleInformation } from 'app/site/assignments/models/view-assignment';
import { ViewItem } from 'app/site/agenda/models/view-item'; import { ViewItem } from 'app/site/agenda/models/view-item';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { ViewTag } from 'app/site/tags/models/view-tag'; import { ViewTag } from 'app/site/tags/models/view-tag';
@ -23,6 +21,8 @@ import { ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-
import { ViewAssignmentPollOption } from 'app/site/assignments/models/view-assignment-poll-option'; import { ViewAssignmentPollOption } from 'app/site/assignments/models/view-assignment-poll-option';
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile'; import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
import { Mediafile } from 'app/shared/models/mediafiles/mediafile'; import { Mediafile } from 'app/shared/models/mediafiles/mediafile';
import { ViewListOfSpeakers } from 'app/site/agenda/models/view-list-of-speakers';
import { BaseIsAgendaItemAndListOfSpeakersContentObjectRepository } from '../base-is-agenda-item-and-list-of-speakers-content-object-repository';
/** /**
* Repository Service for Assignments. * Repository Service for Assignments.
@ -32,7 +32,11 @@ import { Mediafile } from 'app/shared/models/mediafiles/mediafile';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class AssignmentRepositoryService extends BaseAgendaContentObjectRepository<ViewAssignment, Assignment> { export class AssignmentRepositoryService extends BaseIsAgendaItemAndListOfSpeakersContentObjectRepository<
ViewAssignment,
Assignment,
AssignmentTitleInformation
> {
private readonly restPath = '/rest/assignments/assignment/'; private readonly restPath = '/rest/assignments/assignment/';
private readonly restPollPath = '/rest/assignments/poll/'; private readonly restPollPath = '/rest/assignments/poll/';
private readonly candidatureOtherPath = '/candidature_other/'; private readonly candidatureOtherPath = '/candidature_other/';
@ -58,15 +62,11 @@ export class AssignmentRepositoryService extends BaseAgendaContentObjectReposito
protected translate: TranslateService, protected translate: TranslateService,
private httpService: HttpService private httpService: HttpService
) { ) {
super(DS, dataSend, mapperService, viewModelStoreService, translate, Assignment, [User, Item, Tag, Mediafile]); super(DS, dataSend, mapperService, viewModelStoreService, translate, Assignment, [User, Tag, Mediafile]);
} }
public getAgendaTitle = (assignment: Partial<Assignment> | Partial<ViewAssignment>) => { public getTitle = (titleInformation: AssignmentTitleInformation) => {
return assignment.title; return titleInformation.title;
};
public getAgendaTitleWithType = (assignment: Partial<Assignment> | Partial<ViewAssignment>) => {
return assignment.title + ' (' + this.getVerboseName() + ')';
}; };
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {
@ -74,24 +74,22 @@ export class AssignmentRepositoryService extends BaseAgendaContentObjectReposito
}; };
public createViewModel(assignment: Assignment): ViewAssignment { public createViewModel(assignment: Assignment): ViewAssignment {
const agendaItem = this.viewModelStoreService.get(ViewItem, assignment.agenda_item_id); const item = this.viewModelStoreService.get(ViewItem, assignment.agenda_item_id);
const listOfSpeakers = this.viewModelStoreService.get(ViewListOfSpeakers, assignment.list_of_speakers_id);
const tags = this.viewModelStoreService.getMany(ViewTag, assignment.tags_id); const tags = this.viewModelStoreService.getMany(ViewTag, assignment.tags_id);
const attachments = this.viewModelStoreService.getMany(ViewMediafile, assignment.attachments_id); const attachments = this.viewModelStoreService.getMany(ViewMediafile, assignment.attachments_id);
const assignmentRelatedUsers = this.createViewAssignmentRelatedUsers(assignment.assignment_related_users); const assignmentRelatedUsers = this.createViewAssignmentRelatedUsers(assignment.assignment_related_users);
const assignmentPolls = this.createViewAssignmentPolls(assignment.polls); const assignmentPolls = this.createViewAssignmentPolls(assignment.polls);
const viewAssignment = new ViewAssignment( return new ViewAssignment(
assignment, assignment,
assignmentRelatedUsers, assignmentRelatedUsers,
assignmentPolls, assignmentPolls,
agendaItem, item,
listOfSpeakers,
tags, tags,
attachments attachments
); );
viewAssignment.getVerboseName = this.getVerboseName;
viewAssignment.getAgendaTitle = () => this.getAgendaTitle(viewAssignment);
viewAssignment.getAgendaTitleWithType = () => this.getAgendaTitleWithType(viewAssignment);
return viewAssignment;
} }
private createViewAssignmentRelatedUsers( private createViewAssignmentRelatedUsers(

View File

@ -1,44 +0,0 @@
import { TranslateService } from '@ngx-translate/core';
import { BaseViewModel } from '../../site/base/base-view-model';
import { BaseModel, ModelConstructor } from '../../shared/models/base/base-model';
import { CollectionStringMapperService } from '../core-services/collection-string-mapper.service';
import { DataSendService } from '../core-services/data-send.service';
import { DataStoreService } from '../core-services/data-store.service';
import { ViewModelStoreService } from '../core-services/view-model-store.service';
import { BaseRepository } from './base-repository';
export function isBaseAgendaContentObjectRepository(obj: any): obj is BaseAgendaContentObjectRepository<any, any> {
const repo = obj as BaseAgendaContentObjectRepository<any, any>;
return !!obj && repo.getAgendaTitle !== undefined && repo.getAgendaTitleWithType !== undefined;
}
export abstract class BaseAgendaContentObjectRepository<
V extends BaseViewModel,
M extends BaseModel
> extends BaseRepository<V, M> {
public abstract getAgendaTitle: (model: Partial<M> | Partial<V>) => string;
public abstract getAgendaTitleWithType: (model: Partial<M> | Partial<V>) => string;
/**
*/
public constructor(
DS: DataStoreService,
dataSend: DataSendService,
collectionStringMapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService,
translate: TranslateService,
baseModelCtor: ModelConstructor<M>,
depsModelCtors?: ModelConstructor<BaseModel>[]
) {
super(
DS,
dataSend,
collectionStringMapperService,
viewModelStoreService,
translate,
baseModelCtor,
depsModelCtors
);
}
}

View File

@ -0,0 +1,75 @@
import { BaseRepository } from './base-repository';
import { BaseModelWithContentObject } from 'app/shared/models/base/base-model-with-content-object';
import { BaseViewModelWithContentObject } from 'app/site/base/base-view-model-with-content-object';
import { ContentObject } from 'app/shared/models/base/content-object';
import { BaseViewModel, TitleInformation } from 'app/site/base/base-view-model';
/**
* A base repository for objects that *have* content objects, e.g. items and lists of speakers.
* Ensures that these objects must have a content objects via generics and adds a way of
* efficient querying objects by their content objects:
* If one wants to have the object for "motions/motion:1", call `findByContentObject` with
* these information represented as a {@link ContentObject}.
*/
export abstract class BaseHasContentObjectRepository<
V extends BaseViewModelWithContentObject<M, C> & T,
M extends BaseModelWithContentObject,
C extends BaseViewModel,
T extends TitleInformation
> extends BaseRepository<V, M, T> {
protected contentObjectMapping: {
[collection: string]: {
[id: number]: V;
};
} = {};
/**
* Returns the object with has the given content object as the content object.
*
* @param contentObject The content object to query.
*/
public findByContentObject(contentObject: ContentObject): V | null {
if (
this.contentObjectMapping[contentObject.collection] &&
this.contentObjectMapping[contentObject.collection][contentObject.id]
) {
return this.contentObjectMapping[contentObject.collection][contentObject.id];
}
}
/**
* @override
*/
public changedModels(ids: number[]): void {
ids.forEach(id => {
const v = this.createViewModelWithTitles(this.DS.get(this.collectionString, id));
this.viewModelStore[id] = v;
const contentObject = v.contentObjectData;
if (!this.contentObjectMapping[contentObject.collection]) {
this.contentObjectMapping[contentObject.collection] = {};
}
this.contentObjectMapping[contentObject.collection][contentObject.id] = v;
this.updateViewModelObservable(id);
});
this.updateViewModelListObservable();
}
/**
* @override
*/
public deleteModels(ids: number[]): void {
ids.forEach(id => {
const v = this.viewModelStore[id];
if (v) {
const contentObject = v.contentObjectData;
if (this.contentObjectMapping[contentObject.collection]) {
delete this.contentObjectMapping[contentObject.collection][contentObject.id];
}
}
delete this.viewModelStore[id];
this.updateViewModelObservable(id);
});
this.updateViewModelListObservable();
}
}

View File

@ -0,0 +1,98 @@
import { TranslateService } from '@ngx-translate/core';
import { BaseModel, ModelConstructor } from '../../shared/models/base/base-model';
import { BaseRepository } from './base-repository';
import {
isBaseIsAgendaItemContentObjectRepository,
IBaseIsAgendaItemContentObjectRepository
} from './base-is-agenda-item-content-object-repository';
import {
isBaseIsListOfSpeakersContentObjectRepository,
IBaseIsListOfSpeakersContentObjectRepository
} from './base-is-list-of-speakers-content-object-repository';
import { DataStoreService } from '../core-services/data-store.service';
import { DataSendService } from '../core-services/data-send.service';
import { ViewModelStoreService } from '../core-services/view-model-store.service';
import { Item } from 'app/shared/models/agenda/item';
import { ListOfSpeakers } from 'app/shared/models/agenda/list-of-speakers';
import { CollectionStringMapperService } from '../core-services/collection-string-mapper.service';
import {
TitleInformationWithAgendaItem,
IBaseViewModelWithAgendaItem
} from 'app/site/base/base-view-model-with-agenda-item';
import { BaseProjectableViewModel } from 'app/site/base/base-projectable-view-model';
import { IBaseViewModelWithListOfSpeakers } from 'app/site/base/base-view-model-with-list-of-speakers';
export function isBaseIsAgendaItemAndListOfSpeakersContentObjectRepository(
obj: any
): obj is BaseIsAgendaItemAndListOfSpeakersContentObjectRepository<any, any, any> {
return (
!!obj && isBaseIsAgendaItemContentObjectRepository(obj) && isBaseIsListOfSpeakersContentObjectRepository(obj)
);
}
/**
* The base repository for objects with an agenda item and a list of speakers. This is some kind of
* multi-inheritance by implementing both inherit classes again...
*/
export abstract class BaseIsAgendaItemAndListOfSpeakersContentObjectRepository<
V extends BaseProjectableViewModel & IBaseViewModelWithAgendaItem & IBaseViewModelWithListOfSpeakers & T,
M extends BaseModel,
T extends TitleInformationWithAgendaItem
> extends BaseRepository<V, M, T>
implements
IBaseIsAgendaItemContentObjectRepository<V, M, T>,
IBaseIsListOfSpeakersContentObjectRepository<V, M, T> {
public constructor(
DS: DataStoreService,
dataSend: DataSendService,
collectionStringMapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService,
translate: TranslateService,
baseModelCtor: ModelConstructor<M>,
depsModelCtors?: ModelConstructor<BaseModel>[]
) {
super(
DS,
dataSend,
collectionStringMapperService,
viewModelStoreService,
translate,
baseModelCtor,
depsModelCtors
);
if (!this.depsModelCtors) {
this.depsModelCtors = [];
}
this.depsModelCtors.push(Item);
this.depsModelCtors.push(ListOfSpeakers);
}
public getAgendaListTitle(titleInformation: T): string {
// Return the agenda title with the model's verbose name appended
const numberPrefix = titleInformation.agenda_item_number ? `${titleInformation.agenda_item_number} · ` : '';
return numberPrefix + this.getTitle(titleInformation) + ' (' + this.getVerboseName() + ')';
}
public getAgendaSlideTitle(titleInformation: T): string {
const numberPrefix = titleInformation.agenda_item_number ? `${titleInformation.agenda_item_number} · ` : '';
return numberPrefix + this.getTitle(titleInformation);
}
public getListOfSpeakersTitle = (titleInformation: T) => {
return this.getAgendaListTitle(titleInformation);
};
public getListOfSpeakersSlideTitle = (titleInformation: T) => {
return this.getAgendaSlideTitle(titleInformation);
};
protected createViewModelWithTitles(model: M): V {
const viewModel = super.createViewModelWithTitles(model);
viewModel.getAgendaListTitle = () => this.getAgendaListTitle(viewModel);
viewModel.getAgendaSlideTitle = () => this.getAgendaSlideTitle(viewModel);
viewModel.getListOfSpeakersTitle = () => this.getListOfSpeakersTitle(viewModel);
viewModel.getListOfSpeakersSlideTitle = () => this.getListOfSpeakersSlideTitle(viewModel);
return viewModel;
}
}

View File

@ -0,0 +1,92 @@
import { TranslateService } from '@ngx-translate/core';
import { BaseModel, ModelConstructor } from '../../shared/models/base/base-model';
import { CollectionStringMapperService } from '../core-services/collection-string-mapper.service';
import { DataSendService } from '../core-services/data-send.service';
import { BaseRepository } from './base-repository';
import { Item } from 'app/shared/models/agenda/item';
import { DataStoreService } from '../core-services/data-store.service';
import { ViewModelStoreService } from '../core-services/view-model-store.service';
import {
TitleInformationWithAgendaItem,
BaseViewModelWithAgendaItem
} from 'app/site/base/base-view-model-with-agenda-item';
export function isBaseIsAgendaItemContentObjectRepository(
obj: any
): obj is BaseIsAgendaItemContentObjectRepository<any, any, any> {
const repo = obj as BaseIsAgendaItemContentObjectRepository<any, any, any>;
return !!obj && repo.getAgendaSlideTitle !== undefined && repo.getAgendaListTitle !== undefined;
}
/**
* Describes a base repository which objects do have an assigned agenda item.
*/
export interface IBaseIsAgendaItemContentObjectRepository<
V extends BaseViewModelWithAgendaItem & T,
M extends BaseModel,
T extends TitleInformationWithAgendaItem
> extends BaseRepository<V, M, T> {
getAgendaListTitle: (titleInformation: T) => string;
getAgendaSlideTitle: (titleInformation: T) => string;
}
/**
* The base repository for objects with an agenda item.
*/
export abstract class BaseIsAgendaItemContentObjectRepository<
V extends BaseViewModelWithAgendaItem & T,
M extends BaseModel,
T extends TitleInformationWithAgendaItem
> extends BaseRepository<V, M, T> implements IBaseIsAgendaItemContentObjectRepository<V, M, T> {
public constructor(
DS: DataStoreService,
dataSend: DataSendService,
collectionStringMapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService,
translate: TranslateService,
baseModelCtor: ModelConstructor<M>,
depsModelCtors?: ModelConstructor<BaseModel>[]
) {
super(
DS,
dataSend,
collectionStringMapperService,
viewModelStoreService,
translate,
baseModelCtor,
depsModelCtors
);
if (!this.depsModelCtors) {
this.depsModelCtors = [];
}
this.depsModelCtors.push(Item);
}
/**
* @returns the agenda title for the agenda item list. Should
* be `<item number> · <title> (<type>)`. E.g. `7 · the is an election (Election)`.
*/
public getAgendaListTitle(titleInformation: T): string {
// Return the agenda title with the model's verbose name appended
const numberPrefix = titleInformation.agenda_item_number ? `${titleInformation.agenda_item_number} · ` : '';
return numberPrefix + this.getTitle(titleInformation) + ' (' + this.getVerboseName() + ')';
}
/**
* @returns the agenda title for the item slides
*/
public getAgendaSlideTitle(titleInformation: T): string {
return this.getTitle(titleInformation);
}
/**
* Adds the agenda titles to the viewmodel.
*/
protected createViewModelWithTitles(model: M): V {
const viewModel = super.createViewModelWithTitles(model);
viewModel.getAgendaListTitle = () => this.getAgendaListTitle(viewModel);
viewModel.getAgendaSlideTitle = () => this.getAgendaSlideTitle(viewModel);
return viewModel;
}
}

View File

@ -0,0 +1,81 @@
import { TranslateService } from '@ngx-translate/core';
import { TitleInformation } from '../../site/base/base-view-model';
import { BaseModel, ModelConstructor } from '../../shared/models/base/base-model';
import { BaseRepository } from './base-repository';
import { DataStoreService } from '../core-services/data-store.service';
import { DataSendService } from '../core-services/data-send.service';
import { CollectionStringMapperService } from '../core-services/collection-string-mapper.service';
import { ViewModelStoreService } from '../core-services/view-model-store.service';
import { ListOfSpeakers } from 'app/shared/models/agenda/list-of-speakers';
import { BaseViewModelWithListOfSpeakers } from 'app/site/base/base-view-model-with-list-of-speakers';
export function isBaseIsListOfSpeakersContentObjectRepository(
obj: any
): obj is BaseIsListOfSpeakersContentObjectRepository<any, any, any> {
const repo = obj as BaseIsListOfSpeakersContentObjectRepository<any, any, any>;
return !!obj && repo.getListOfSpeakersTitle !== undefined && repo.getListOfSpeakersSlideTitle !== undefined;
}
/**
* Describes a base repository which objects have a list of speakers assigned.
*/
export interface IBaseIsListOfSpeakersContentObjectRepository<
V extends BaseViewModelWithListOfSpeakers & T,
M extends BaseModel,
T extends TitleInformation
> extends BaseRepository<V, M, T> {
getListOfSpeakersTitle: (titleInformation: T) => string;
getListOfSpeakersSlideTitle: (titleInformation: T) => string;
}
/**
* The base repository for objects with a list of speakers.
*/
export abstract class BaseIsListOfSpeakersContentObjectRepository<
V extends BaseViewModelWithListOfSpeakers & T,
M extends BaseModel,
T extends TitleInformation
> extends BaseRepository<V, M, T> implements IBaseIsListOfSpeakersContentObjectRepository<V, M, T> {
public constructor(
DS: DataStoreService,
dataSend: DataSendService,
collectionStringMapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService,
translate: TranslateService,
baseModelCtor: ModelConstructor<M>,
depsModelCtors?: ModelConstructor<BaseModel>[]
) {
super(
DS,
dataSend,
collectionStringMapperService,
viewModelStoreService,
translate,
baseModelCtor,
depsModelCtors
);
if (!this.depsModelCtors) {
this.depsModelCtors = [];
}
this.depsModelCtors.push(ListOfSpeakers);
}
public getListOfSpeakersTitle(titleInformation: T): string {
return this.getTitle(titleInformation) + ' (' + this.getVerboseName() + ')';
}
public getListOfSpeakersSlideTitle(titleInformation: T): string {
return this.getTitle(titleInformation);
}
/**
* Adds the list of speakers titles to the view model
*/
protected createViewModelWithTitles(model: M): V {
const viewModel = super.createViewModelWithTitles(model);
viewModel.getListOfSpeakersTitle = () => this.getListOfSpeakersTitle(viewModel);
viewModel.getListOfSpeakersSlideTitle = () => this.getListOfSpeakersSlideTitle(viewModel);
return viewModel;
}
}

View File

@ -1,19 +1,19 @@
import { BehaviorSubject, Observable, Subject } from 'rxjs'; import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { auditTime } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BaseViewModel } from '../../site/base/base-view-model'; import { BaseViewModel, TitleInformation } from '../../site/base/base-view-model';
import { BaseModel, ModelConstructor } from '../../shared/models/base/base-model'; import { BaseModel, ModelConstructor } from '../../shared/models/base/base-model';
import { CollectionStringMapperService } from '../core-services/collection-string-mapper.service'; import { CollectionStringMapperService } from '../core-services/collection-string-mapper.service';
import { DataSendService } from '../core-services/data-send.service'; import { DataSendService } from '../core-services/data-send.service';
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 { ViewModelStoreService } from '../core-services/view-model-store.service'; import { ViewModelStoreService } from '../core-services/view-model-store.service';
import { OnAfterAppsLoaded } from '../onAfterAppsLoaded'; import { OnAfterAppsLoaded } from '../onAfterAppsLoaded';
import { Collection } from 'app/shared/models/base/collection'; import { Collection } from 'app/shared/models/base/collection';
import { CollectionIds } from '../core-services/data-store-update-manager.service'; import { CollectionIds } from '../core-services/data-store-update-manager.service';
export abstract class BaseRepository<V extends BaseViewModel, M extends BaseModel> export abstract class BaseRepository<V extends BaseViewModel & T, M extends BaseModel, T extends TitleInformation>
implements OnAfterAppsLoaded, Collection { implements OnAfterAppsLoaded, Collection {
/** /**
* Stores all the viewModel in an object * Stores all the viewModel in an object
@ -70,6 +70,7 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
} }
public abstract getVerboseName: (plural?: boolean) => string; public abstract getVerboseName: (plural?: boolean) => string;
public abstract getTitle: (titleInformation: T) => string;
/** /**
* Construction routine for the base repository * Construction routine for the base repository
@ -114,7 +115,7 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
protected loadInitialFromDS(): void { protected loadInitialFromDS(): 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.createViewModelWithTitles(model);
}); });
// Update the list and then all models on their own // Update the list and then all models on their own
@ -145,7 +146,7 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
*/ */
public changedModels(ids: number[]): void { public changedModels(ids: number[]): void {
ids.forEach(id => { ids.forEach(id => {
this.viewModelStore[id] = this.createViewModel(this.DS.get(this.collectionString, id)); this.viewModelStore[id] = this.createViewModelWithTitles(this.DS.get(this.collectionString, id));
this.updateViewModelObservable(id); this.updateViewModelObservable(id);
}); });
this.updateViewModelListObservable(); this.updateViewModelListObservable();
@ -188,6 +189,10 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
} }
} }
public getListTitle: (titleInformation: T) => string = (titleInformation: T) => {
return this.getTitle(titleInformation);
};
/** /**
* Saves the (full) update to an existing model. So called "update"-function * Saves the (full) update to an existing model. So called "update"-function
* Provides a default procedure, but can be overwritten if required * Provides a default procedure, but can be overwritten if required
@ -258,6 +263,18 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
*/ */
protected abstract createViewModel(model: M): V; protected abstract createViewModel(model: M): V;
/**
* After creating a view model, all functions for models form the repo
* are assigned to the new view model.
*/
protected createViewModelWithTitles(model: M): V {
const viewModel = this.createViewModel(model);
viewModel.getTitle = () => this.getTitle(viewModel);
viewModel.getListTitle = () => this.getListTitle(viewModel);
viewModel.getVerboseName = this.getVerboseName;
return viewModel;
}
/** /**
* Clears the repository. * Clears the repository.
*/ */

View File

@ -12,7 +12,7 @@ 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/collection-string-mapper.service'; import { CollectionStringMapperService } from 'app/core/core-services/collection-string-mapper.service';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { ViewConfig } from 'app/site/config/models/view-config'; import { ViewConfig, ConfigTitleInformation } from 'app/site/config/models/view-config';
/** /**
* Holds a single config item. * Holds a single config item.
@ -77,7 +77,7 @@ export interface ConfigGroup {
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class ConfigRepositoryService extends BaseRepository<ViewConfig, Config> { export class ConfigRepositoryService extends BaseRepository<ViewConfig, Config, ConfigTitleInformation> {
/** /**
* Own store for config groups. * Own store for config groups.
*/ */
@ -130,14 +130,16 @@ export class ConfigRepositoryService extends BaseRepository<ViewConfig, Config>
return this.translate.instant(plural ? 'Configs' : 'Config'); return this.translate.instant(plural ? 'Configs' : 'Config');
}; };
public getTitle = (titleInformation: ConfigTitleInformation) => {
return titleInformation.key;
};
/** /**
* Creates a new ViewConfig of a given Config object * Creates a new ViewConfig of a given Config object
* @param config * @param config
*/ */
public createViewModel(config: Config): ViewConfig { public createViewModel(config: Config): ViewConfig {
const viewConfig = new ViewConfig(config); return new ViewConfig(config);
viewConfig.getVerboseName = this.getVerboseName;
return viewConfig;
} }
/** /**

View File

@ -1,16 +1,17 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { CollectionStringMapperService } from 'app/core/core-services/collection-string-mapper.service'; import { CollectionStringMapperService } from 'app/core/core-services/collection-string-mapper.service';
import { DataStoreService } from 'app/core/core-services/data-store.service'; import { DataStoreService } from 'app/core/core-services/data-store.service';
import { BaseRepository } from 'app/core/repositories/base-repository'; import { BaseRepository } from 'app/core/repositories/base-repository';
import { History } from 'app/shared/models/core/history'; import { History } from 'app/shared/models/core/history';
import { Identifiable } from 'app/shared/models/base/identifiable'; 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, ProxyHistory } from 'app/site/history/models/view-history'; import { ViewHistory, ProxyHistory, HistoryTitleInformation } 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 { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { ViewUser } from 'app/site/users/models/view-user'; import { ViewUser } from 'app/site/users/models/view-user';
import { TranslateService } from '@ngx-translate/core';
import { DataSendService } from 'app/core/core-services/data-send.service'; import { DataSendService } from 'app/core/core-services/data-send.service';
/** /**
@ -21,7 +22,7 @@ import { DataSendService } from 'app/core/core-services/data-send.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class HistoryRepositoryService extends BaseRepository<ViewHistory, History> { export class HistoryRepositoryService extends BaseRepository<ViewHistory, History, HistoryTitleInformation> {
/** /**
* Constructs the history repository * Constructs the history repository
* *
@ -46,6 +47,10 @@ export class HistoryRepositoryService extends BaseRepository<ViewHistory, Histor
return this.translate.instant(plural ? 'Histories' : 'History'); return this.translate.instant(plural ? 'Histories' : 'History');
}; };
public getTitle = (titleInformation: HistoryTitleInformation) => {
return titleInformation.element_id;
};
/** /**
* Creates a new ViewHistory objects out of a historyObject * Creates a new ViewHistory objects out of a historyObject
* *
@ -53,9 +58,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 viewHistory = new ViewHistory(this.createProxyHistory(history)); return new ViewHistory(this.createProxyHistory(history));
viewHistory.getVerboseName = this.getVerboseName;
return viewHistory;
} }
/** /**

View File

@ -1,7 +1,9 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpHeaders } from '@angular/common/http';
import { BaseRepository } from '../base-repository'; import { TranslateService } from '@ngx-translate/core';
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
import { ViewMediafile, MediafileTitleInformation } from 'app/site/mediafiles/models/view-mediafile';
import { Mediafile } from 'app/shared/models/mediafiles/mediafile'; import { Mediafile } from 'app/shared/models/mediafiles/mediafile';
import { User } from 'app/shared/models/users/user'; import { User } from 'app/shared/models/users/user';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';
@ -9,10 +11,10 @@ import { Identifiable } from 'app/shared/models/base/identifiable';
import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
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 { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { ViewUser } from 'app/site/users/models/view-user'; import { ViewUser } from 'app/site/users/models/view-user';
import { TranslateService } from '@ngx-translate/core'; import { BaseIsListOfSpeakersContentObjectRepository } from '../base-is-list-of-speakers-content-object-repository';
import { ViewListOfSpeakers } from 'app/site/agenda/models/view-list-of-speakers';
/** /**
* Repository for MediaFiles * Repository for MediaFiles
@ -20,7 +22,11 @@ import { TranslateService } from '@ngx-translate/core';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class MediafileRepositoryService extends BaseRepository<ViewMediafile, Mediafile> { export class MediafileRepositoryService extends BaseIsListOfSpeakersContentObjectRepository<
ViewMediafile,
Mediafile,
MediafileTitleInformation
> {
/** /**
* Constructor for the mediafile repository * Constructor for the mediafile repository
* @param DS Data store * @param DS Data store
@ -40,6 +46,10 @@ export class MediafileRepositoryService extends BaseRepository<ViewMediafile, Me
this.initSorting(); this.initSorting();
} }
public getTitle = (titleInformation: MediafileTitleInformation) => {
return titleInformation.title;
};
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Files' : 'File'); return this.translate.instant(plural ? 'Files' : 'File');
}; };
@ -51,10 +61,9 @@ 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 listOfSpeakers = this.viewModelStoreService.get(ViewListOfSpeakers, file.list_of_speakers_id);
const uploader = this.viewModelStoreService.get(ViewUser, file.uploader_id); const uploader = this.viewModelStoreService.get(ViewUser, file.uploader_id);
const viewMediafile = new ViewMediafile(file, uploader); return new ViewMediafile(file, listOfSpeakers, uploader);
viewMediafile.getVerboseName = this.getVerboseName;
return viewMediafile;
} }
/** /**

View File

@ -9,7 +9,7 @@ import { ConfigService } from 'app/core/ui-services/config.service';
import { DataSendService } from '../../core-services/data-send.service'; import { DataSendService } from '../../core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';
import { HttpService } from '../../core-services/http.service'; import { HttpService } from '../../core-services/http.service';
import { ViewCategory } from 'app/site/motions/models/view-category'; import { ViewCategory, CategoryTitleInformation } from 'app/site/motions/models/view-category';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
type SortProperty = 'prefix' | 'name'; type SortProperty = 'prefix' | 'name';
@ -27,7 +27,7 @@ type SortProperty = 'prefix' | 'name';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class CategoryRepositoryService extends BaseRepository<ViewCategory, Category> { export class CategoryRepositoryService extends BaseRepository<ViewCategory, Category, CategoryTitleInformation> {
private sortProperty: SortProperty; private sortProperty: SortProperty;
/** /**
@ -60,14 +60,18 @@ export class CategoryRepositoryService extends BaseRepository<ViewCategory, Cate
}); });
} }
public getTitle = (titleInformation: CategoryTitleInformation) => {
return titleInformation.prefix
? titleInformation.prefix + ' - ' + titleInformation.name
: titleInformation.name;
};
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Categories' : 'Category'); return this.translate.instant(plural ? 'Categories' : 'Category');
}; };
protected createViewModel(category: Category): ViewCategory { protected createViewModel(category: Category): ViewCategory {
const viewCategory = new ViewCategory(category); return new ViewCategory(category);
viewCategory.getVerboseName = this.getVerboseName;
return viewCategory;
} }
/** /**

View File

@ -11,7 +11,10 @@ import { Workflow } from 'app/shared/models/motions/workflow';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
import { DataStoreService } from 'app/core/core-services/data-store.service'; import { DataStoreService } from 'app/core/core-services/data-store.service';
import { MotionChangeRecommendation } from 'app/shared/models/motions/motion-change-reco'; import { MotionChangeRecommendation } from 'app/shared/models/motions/motion-change-reco';
import { ViewMotionChangeRecommendation } from 'app/site/motions/models/view-change-recommendation'; import {
ViewMotionChangeRecommendation,
MotionChangeRecommendationTitleInformation
} from 'app/site/motions/models/view-motion-change-recommendation';
import { Identifiable } from 'app/shared/models/base/identifiable'; import { Identifiable } from 'app/shared/models/base/identifiable';
import { CollectionStringMapperService } from 'app/core/core-services/collection-string-mapper.service'; import { CollectionStringMapperService } from 'app/core/core-services/collection-string-mapper.service';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
@ -31,7 +34,8 @@ import { ViewModelStoreService } from 'app/core/core-services/view-model-store.s
}) })
export class ChangeRecommendationRepositoryService extends BaseRepository< export class ChangeRecommendationRepositoryService extends BaseRepository<
ViewMotionChangeRecommendation, ViewMotionChangeRecommendation,
MotionChangeRecommendation MotionChangeRecommendation,
MotionChangeRecommendationTitleInformation
> { > {
/** /**
* Creates a MotionRepository * Creates a MotionRepository
@ -57,6 +61,10 @@ export class ChangeRecommendationRepositoryService extends BaseRepository<
]); ]);
} }
public getTitle = (titleInformation: MotionChangeRecommendationTitleInformation) => {
return this.getVerboseName();
};
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Change recommendations' : 'Change recommendation'); return this.translate.instant(plural ? 'Change recommendations' : 'Change recommendation');
}; };
@ -67,9 +75,7 @@ export class ChangeRecommendationRepositoryService extends BaseRepository<
* @param {MotionChangeRecommendation} model * @param {MotionChangeRecommendation} model
*/ */
protected createViewModel(model: MotionChangeRecommendation): ViewMotionChangeRecommendation { protected createViewModel(model: MotionChangeRecommendation): ViewMotionChangeRecommendation {
const viewMotionChangeRecommendation = new ViewMotionChangeRecommendation(model); return new ViewMotionChangeRecommendation(model);
viewMotionChangeRecommendation.getVerboseName = this.getVerboseName;
return viewMotionChangeRecommendation;
} }
/** /**

View File

@ -12,11 +12,11 @@ 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 { 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, MotionBlockTitleInformation } from 'app/site/motions/models/view-motion-block';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; 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'; import { ViewItem } from 'app/site/agenda/models/view-item';
import { BaseAgendaContentObjectRepository } from '../base-agenda-content-object-repository'; import { ViewListOfSpeakers } from 'app/site/agenda/models/view-list-of-speakers';
import { BaseIsAgendaItemAndListOfSpeakersContentObjectRepository } from '../base-is-agenda-item-and-list-of-speakers-content-object-repository';
/** /**
* Repository service for motion blocks * Repository service for motion blocks
@ -24,7 +24,11 @@ import { BaseAgendaContentObjectRepository } from '../base-agenda-content-object
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class MotionBlockRepositoryService extends BaseAgendaContentObjectRepository<ViewMotionBlock, MotionBlock> { export class MotionBlockRepositoryService extends BaseIsAgendaItemAndListOfSpeakersContentObjectRepository<
ViewMotionBlock,
MotionBlock,
MotionBlockTitleInformation
> {
/** /**
* Constructor for the motion block repository * Constructor for the motion block repository
* *
@ -43,16 +47,12 @@ export class MotionBlockRepositoryService extends BaseAgendaContentObjectReposit
private motionRepo: MotionRepositoryService, private motionRepo: MotionRepositoryService,
private httpService: HttpService private httpService: HttpService
) { ) {
super(DS, dataSend, mapperService, viewModelStoreService, translate, MotionBlock, [Item]); super(DS, dataSend, mapperService, viewModelStoreService, translate, MotionBlock);
this.initSorting(); this.initSorting();
} }
public getAgendaTitle = (motionBlock: Partial<MotionBlock> | Partial<ViewMotionBlock>) => { public getTitle = (titleInformation: MotionBlockTitleInformation) => {
return motionBlock.title; return titleInformation.title;
};
public getAgendaTitleWithType = (motionBlock: Partial<MotionBlock> | Partial<ViewMotionBlock>) => {
return motionBlock.title + ' (' + this.getVerboseName() + ')';
}; };
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {
@ -67,11 +67,8 @@ export class MotionBlockRepositoryService extends BaseAgendaContentObjectReposit
*/ */
protected createViewModel(block: MotionBlock): ViewMotionBlock { protected createViewModel(block: MotionBlock): ViewMotionBlock {
const item = this.viewModelStoreService.get(ViewItem, block.agenda_item_id); const item = this.viewModelStoreService.get(ViewItem, block.agenda_item_id);
const viewMotionBlock = new ViewMotionBlock(block, item); const listOfSpeakers = this.viewModelStoreService.get(ViewListOfSpeakers, block.list_of_speakers_id);
viewMotionBlock.getVerboseName = this.getVerboseName; return new ViewMotionBlock(block, item, listOfSpeakers);
viewMotionBlock.getAgendaTitle = () => this.getAgendaTitle(viewMotionBlock);
viewMotionBlock.getAgendaTitleWithType = () => this.getAgendaTitleWithType(viewMotionBlock);
return viewMotionBlock;
} }
/** /**

View File

@ -1,16 +1,20 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { DataSendService } from '../../core-services/data-send.service'; import { DataSendService } from '../../core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
import { ViewMotionCommentSection } from 'app/site/motions/models/view-motion-comment-section'; import {
ViewMotionCommentSection,
MotionCommentSectionTitleInformation
} from 'app/site/motions/models/view-motion-comment-section';
import { MotionCommentSection } from 'app/shared/models/motions/motion-comment-section'; import { MotionCommentSection } from 'app/shared/models/motions/motion-comment-section';
import { Group } from 'app/shared/models/users/group'; import { Group } from 'app/shared/models/users/group';
import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.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 { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { ViewGroup } from 'app/site/users/models/view-group'; import { ViewGroup } from 'app/site/users/models/view-group';
import { TranslateService } from '@ngx-translate/core';
import { ViewMotion } from 'app/site/motions/models/view-motion'; import { ViewMotion } from 'app/site/motions/models/view-motion';
/** /**
@ -28,7 +32,8 @@ import { ViewMotion } from 'app/site/motions/models/view-motion';
}) })
export class MotionCommentSectionRepositoryService extends BaseRepository< export class MotionCommentSectionRepositoryService extends BaseRepository<
ViewMotionCommentSection, ViewMotionCommentSection,
MotionCommentSection MotionCommentSection,
MotionCommentSectionTitleInformation
> { > {
/** /**
* Creates a CategoryRepository * Creates a CategoryRepository
@ -51,6 +56,10 @@ export class MotionCommentSectionRepositoryService extends BaseRepository<
super(DS, dataSend, mapperService, viewModelStoreService, translate, MotionCommentSection, [Group]); super(DS, dataSend, mapperService, viewModelStoreService, translate, MotionCommentSection, [Group]);
} }
public getTitle = (titleInformation: MotionCommentSectionTitleInformation) => {
return titleInformation.name;
};
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Comment sections' : 'Comment section'); return this.translate.instant(plural ? 'Comment sections' : 'Comment section');
}; };
@ -64,9 +73,7 @@ export class MotionCommentSectionRepositoryService extends BaseRepository<
protected createViewModel(section: MotionCommentSection): ViewMotionCommentSection { protected createViewModel(section: MotionCommentSection): ViewMotionCommentSection {
const readGroups = this.viewModelStoreService.getMany(ViewGroup, section.read_groups_id); const readGroups = this.viewModelStoreService.getMany(ViewGroup, section.read_groups_id);
const writeGroups = this.viewModelStoreService.getMany(ViewGroup, section.write_groups_id); const writeGroups = this.viewModelStoreService.getMany(ViewGroup, section.write_groups_id);
const viewMotionCommentSection = new ViewMotionCommentSection(section, readGroups, writeGroups); return new ViewMotionCommentSection(section, readGroups, writeGroups);
viewMotionCommentSection.getVerboseName = this.getVerboseName;
return viewMotionCommentSection;
} }
/** /**

View File

@ -6,14 +6,13 @@ import { Observable } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { Category } from 'app/shared/models/motions/category'; import { Category } from 'app/shared/models/motions/category';
import { ChangeRecoMode, ViewMotion } from 'app/site/motions/models/view-motion'; import { ChangeRecoMode, ViewMotion, MotionTitleInformation } from 'app/site/motions/models/view-motion';
import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
import { ConfigService } from 'app/core/ui-services/config.service'; import { ConfigService } from 'app/core/ui-services/config.service';
import { DataSendService } from '../../core-services/data-send.service'; import { DataSendService } from '../../core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';
import { DiffLinesInParagraph, DiffService, LineRange, ModificationType } from '../../ui-services/diff.service'; import { DiffLinesInParagraph, DiffService, LineRange, ModificationType } from '../../ui-services/diff.service';
import { HttpService } from 'app/core/core-services/http.service'; import { HttpService } from 'app/core/core-services/http.service';
import { Item } from 'app/shared/models/agenda/item';
import { LinenumberingService, LineNumberRange } from '../../ui-services/linenumbering.service'; import { LinenumberingService, LineNumberRange } 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';
@ -22,7 +21,7 @@ import { MotionChangeRecommendation } from 'app/shared/models/motions/motion-cha
import { MotionPoll } from 'app/shared/models/motions/motion-poll'; import { MotionPoll } from 'app/shared/models/motions/motion-poll';
import { TreeIdNode } from 'app/core/ui-services/tree.service'; import { TreeIdNode } from 'app/core/ui-services/tree.service';
import { User } from 'app/shared/models/users/user'; import { User } from 'app/shared/models/users/user';
import { ViewMotionChangeRecommendation } from 'app/site/motions/models/view-change-recommendation'; import { ViewMotionChangeRecommendation } from 'app/site/motions/models/view-motion-change-recommendation';
import { ViewMotionAmendedParagraph } from 'app/site/motions/models/view-motion-amended-paragraph'; import { ViewMotionAmendedParagraph } from 'app/site/motions/models/view-motion-amended-paragraph';
import { ViewUnifiedChange } from 'app/shared/models/motions/view-unified-change'; import { ViewUnifiedChange } from 'app/shared/models/motions/view-unified-change';
import { ViewStatuteParagraph } from 'app/site/motions/models/view-statute-paragraph'; import { ViewStatuteParagraph } from 'app/site/motions/models/view-statute-paragraph';
@ -37,11 +36,12 @@ import { ViewItem } from 'app/site/agenda/models/view-item';
import { ViewMotionBlock } from 'app/site/motions/models/view-motion-block'; import { ViewMotionBlock } from 'app/site/motions/models/view-motion-block';
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile'; import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
import { ViewTag } from 'app/site/tags/models/view-tag'; import { ViewTag } from 'app/site/tags/models/view-tag';
import { BaseAgendaContentObjectRepository } from '../base-agenda-content-object-repository';
import { PersonalNote, PersonalNoteContent } from 'app/shared/models/users/personal-note';
import { ViewPersonalNote } from 'app/site/users/models/view-personal-note'; import { ViewPersonalNote } from 'app/site/users/models/view-personal-note';
import { OperatorService } from 'app/core/core-services/operator.service'; import { OperatorService } from 'app/core/core-services/operator.service';
import { CollectionIds } from 'app/core/core-services/data-store-update-manager.service'; import { CollectionIds } from 'app/core/core-services/data-store-update-manager.service';
import { PersonalNote, PersonalNoteContent } from 'app/shared/models/users/personal-note';
import { ViewListOfSpeakers } from 'app/site/agenda/models/view-list-of-speakers';
import { BaseIsAgendaItemAndListOfSpeakersContentObjectRepository } from '../base-is-agenda-item-and-list-of-speakers-content-object-repository';
type SortProperty = 'weight' | 'identifier'; type SortProperty = 'weight' | 'identifier';
@ -88,7 +88,11 @@ export interface ParagraphToChoose {
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class MotionRepositoryService extends BaseAgendaContentObjectRepository<ViewMotion, Motion> { export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersContentObjectRepository<
ViewMotion,
Motion,
MotionTitleInformation
> {
/** /**
* The property the incoming data is sorted by * The property the incoming data is sorted by
*/ */
@ -127,7 +131,6 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository<V
Category, Category,
User, User,
Workflow, Workflow,
Item,
MotionBlock, MotionBlock,
Mediafile, Mediafile,
Tag, Tag,
@ -141,37 +144,39 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository<V
}); });
} }
public getTitle = (motion: Partial<Motion> | Partial<ViewMotion>) => { public getTitle = (titleInformation: MotionTitleInformation) => {
if (motion.identifier) { if (titleInformation.identifier) {
return motion.identifier + ': ' + motion.title; return titleInformation.identifier + ': ' + titleInformation.title;
} else { } else {
return motion.title; return titleInformation.title;
} }
}; };
public getIdentifierOrTitle = (motion: Partial<Motion> | Partial<ViewMotion>) => { public getIdentifierOrTitle = (titleInformation: MotionTitleInformation) => {
if (motion.identifier) { if (titleInformation.identifier) {
return motion.identifier; return titleInformation.identifier;
} else { } else {
return motion.title; return titleInformation.title;
} }
}; };
public getAgendaTitle = (motion: Partial<Motion> | Partial<ViewMotion>) => { public getAgendaSlideTitle = (titleInformation: MotionTitleInformation) => {
const numberPrefix = titleInformation.agenda_item_number ? `${titleInformation.agenda_item_number} · ` : '';
// if the identifier is set, the title will be 'Motion <identifier>'. // if the identifier is set, the title will be 'Motion <identifier>'.
if (motion.identifier) { if (titleInformation.identifier) {
return this.translate.instant('Motion') + ' ' + motion.identifier; return numberPrefix + this.translate.instant('Motion') + ' ' + titleInformation.identifier;
} else { } else {
return motion.title; return numberPrefix + titleInformation.title;
} }
}; };
public getAgendaTitleWithType = (motion: Partial<Motion> | Partial<ViewMotion>) => { public getAgendaListTitle = (titleInformation: MotionTitleInformation) => {
const numberPrefix = titleInformation.agenda_item_number ? `${titleInformation.agenda_item_number} · ` : '';
// Append the verbose name only, if not the special format 'Motion <identifier>' is used. // Append the verbose name only, if not the special format 'Motion <identifier>' is used.
if (motion.identifier) { if (titleInformation.identifier) {
return this.translate.instant('Motion') + ' ' + motion.identifier; return numberPrefix + this.translate.instant('Motion') + ' ' + titleInformation.identifier;
} else { } else {
return motion.title + ' (' + this.getVerboseName() + ')'; return numberPrefix + titleInformation.title + ' (' + this.getVerboseName() + ')';
} }
}; };
@ -193,6 +198,7 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository<V
const supporters = this.viewModelStoreService.getMany(ViewUser, motion.supporters_id); const supporters = this.viewModelStoreService.getMany(ViewUser, motion.supporters_id);
const workflow = this.viewModelStoreService.get(ViewWorkflow, motion.workflow_id); const workflow = this.viewModelStoreService.get(ViewWorkflow, motion.workflow_id);
const item = this.viewModelStoreService.get(ViewItem, motion.agenda_item_id); const item = this.viewModelStoreService.get(ViewItem, motion.agenda_item_id);
const listOfSpeakers = this.viewModelStoreService.get(ViewListOfSpeakers, motion.list_of_speakers_id);
const block = this.viewModelStoreService.get(ViewMotionBlock, motion.motion_block_id); const block = this.viewModelStoreService.get(ViewMotionBlock, motion.motion_block_id);
const attachments = this.viewModelStoreService.getMany(ViewMediafile, motion.attachments_id); const attachments = this.viewModelStoreService.getMany(ViewMediafile, motion.attachments_id);
const tags = this.viewModelStoreService.getMany(ViewTag, motion.tags_id); const tags = this.viewModelStoreService.getMany(ViewTag, motion.tags_id);
@ -215,6 +221,7 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository<V
workflow, workflow,
state, state,
item, item,
listOfSpeakers,
block, block,
attachments, attachments,
tags, tags,
@ -224,11 +231,7 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository<V
personalNote personalNote
); );
viewMotion.getIdentifierOrTitle = () => this.getIdentifierOrTitle(viewMotion); viewMotion.getIdentifierOrTitle = () => this.getIdentifierOrTitle(viewMotion);
viewMotion.getTitle = () => this.getTitle(viewMotion); viewMotion.getProjectorTitle = () => this.getAgendaSlideTitle(viewMotion);
viewMotion.getVerboseName = this.getVerboseName;
viewMotion.getAgendaTitle = () => this.getAgendaTitle(viewMotion);
viewMotion.getProjectorTitle = viewMotion.getAgendaTitle;
viewMotion.getAgendaTitleWithType = () => this.getAgendaTitleWithType(viewMotion);
return viewMotion; return viewMotion;
} }

View File

@ -1,13 +1,14 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { DataSendService } from '../../core-services/data-send.service'; import { DataSendService } from '../../core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
import { ViewStatuteParagraph } from 'app/site/motions/models/view-statute-paragraph'; import { ViewStatuteParagraph, StatuteParagraphTitleInformation } from 'app/site/motions/models/view-statute-paragraph';
import { StatuteParagraph } from 'app/shared/models/motions/statute-paragraph'; import { StatuteParagraph } from 'app/shared/models/motions/statute-paragraph';
import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { TranslateService } from '@ngx-translate/core';
/** /**
* Repository Services for statute paragraphs * Repository Services for statute paragraphs
@ -19,7 +20,11 @@ import { TranslateService } from '@ngx-translate/core';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class StatuteParagraphRepositoryService extends BaseRepository<ViewStatuteParagraph, StatuteParagraph> { export class StatuteParagraphRepositoryService extends BaseRepository<
ViewStatuteParagraph,
StatuteParagraph,
StatuteParagraphTitleInformation
> {
/** /**
* Creates a StatuteParagraphRepository * Creates a StatuteParagraphRepository
* Converts existing and incoming statute paragraphs to ViewStatuteParagraphs * Converts existing and incoming statute paragraphs to ViewStatuteParagraphs
@ -39,13 +44,15 @@ export class StatuteParagraphRepositoryService extends BaseRepository<ViewStatut
super(DS, dataSend, mapperService, viewModelStoreService, translate, StatuteParagraph); super(DS, dataSend, mapperService, viewModelStoreService, translate, StatuteParagraph);
} }
public getTitle = (titleInformation: StatuteParagraphTitleInformation) => {
return titleInformation.title;
};
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Statute paragraphs' : 'Statute paragraph'); return this.translate.instant(plural ? 'Statute paragraphs' : 'Statute paragraph');
}; };
protected createViewModel(statuteParagraph: StatuteParagraph): ViewStatuteParagraph { protected createViewModel(statuteParagraph: StatuteParagraph): ViewStatuteParagraph {
const viewStatuteParagraph = new ViewStatuteParagraph(statuteParagraph); return new ViewStatuteParagraph(statuteParagraph);
viewStatuteParagraph.getVerboseName = this.getVerboseName;
return viewStatuteParagraph;
} }
} }

View File

@ -1,7 +1,9 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { Workflow } from 'app/shared/models/motions/workflow'; import { Workflow } from 'app/shared/models/motions/workflow';
import { ViewWorkflow } from 'app/site/motions/models/view-workflow'; import { ViewWorkflow, WorkflowTitleInformation } from 'app/site/motions/models/view-workflow';
import { DataSendService } from '../../core-services/data-send.service'; import { DataSendService } from '../../core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
@ -10,7 +12,6 @@ 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'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { TranslateService } from '@ngx-translate/core';
/** /**
* Repository Services for Categories * Repository Services for Categories
@ -25,7 +26,7 @@ import { TranslateService } from '@ngx-translate/core';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class WorkflowRepositoryService extends BaseRepository<ViewWorkflow, Workflow> { export class WorkflowRepositoryService extends BaseRepository<ViewWorkflow, Workflow, WorkflowTitleInformation> {
/** /**
* The url to state on rest * The url to state on rest
*/ */
@ -57,6 +58,10 @@ export class WorkflowRepositoryService extends BaseRepository<ViewWorkflow, Work
}); });
} }
public getTitle = (titleInformation: WorkflowTitleInformation) => {
return titleInformation.name;
};
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Workflows' : 'Workflow'); return this.translate.instant(plural ? 'Workflows' : 'Workflow');
}; };
@ -81,9 +86,7 @@ export class WorkflowRepositoryService extends BaseRepository<ViewWorkflow, Work
* @param workflow the Workflow to convert * @param workflow the Workflow to convert
*/ */
protected createViewModel(workflow: Workflow): ViewWorkflow { protected createViewModel(workflow: Workflow): ViewWorkflow {
const viewWorkflow = new ViewWorkflow(workflow); return new ViewWorkflow(workflow);
viewWorkflow.getVerboseName = this.getVerboseName;
return viewWorkflow;
} }
/** /**

View File

@ -6,7 +6,7 @@ import { DataSendService } from '../../core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
import { ViewCountdown } from 'app/site/projector/models/view-countdown'; import { ViewCountdown, CountdownTitleInformation } 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'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { ServertimeService } from 'app/core/core-services/servertime.service'; import { ServertimeService } from 'app/core/core-services/servertime.service';
@ -14,7 +14,7 @@ import { ServertimeService } from 'app/core/core-services/servertime.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class CountdownRepositoryService extends BaseRepository<ViewCountdown, Countdown> { export class CountdownRepositoryService extends BaseRepository<ViewCountdown, Countdown, CountdownTitleInformation> {
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
dataSend: DataSendService, dataSend: DataSendService,
@ -26,14 +26,18 @@ export class CountdownRepositoryService extends BaseRepository<ViewCountdown, Co
super(DS, dataSend, mapperService, viewModelStoreService, translate, Countdown); super(DS, dataSend, mapperService, viewModelStoreService, translate, Countdown);
} }
public getTitle = (titleInformation: CountdownTitleInformation) => {
return titleInformation.description
? `${titleInformation.title} (${titleInformation.description})`
: titleInformation.title;
};
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Countdowns' : 'Countdown'); return this.translate.instant(plural ? 'Countdowns' : 'Countdown');
}; };
protected createViewModel(countdown: Countdown): ViewCountdown { protected createViewModel(countdown: Countdown): ViewCountdown {
const viewCountdown = new ViewCountdown(countdown); return new ViewCountdown(countdown);
viewCountdown.getVerboseName = this.getVerboseName;
return viewCountdown;
} }
/** /**

View File

@ -9,7 +9,10 @@ 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 { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { ProjectionDefault } from 'app/shared/models/core/projection-default'; import { ProjectionDefault } from 'app/shared/models/core/projection-default';
import { ViewProjectionDefault } from 'app/site/projector/models/view-projection-default'; import {
ViewProjectionDefault,
ProjectionDefaultTitleInformation
} from 'app/site/projector/models/view-projection-default';
/** /**
* Manages all projection default instances. * Manages all projection default instances.
@ -17,7 +20,11 @@ import { ViewProjectionDefault } from 'app/site/projector/models/view-projection
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class ProjectionDefaultRepositoryService extends BaseRepository<ViewProjectionDefault, ProjectionDefault> { export class ProjectionDefaultRepositoryService extends BaseRepository<
ViewProjectionDefault,
ProjectionDefault,
ProjectionDefaultTitleInformation
> {
/** /**
* Constructor calls the parent constructor * Constructor calls the parent constructor
* *
@ -41,15 +48,12 @@ export class ProjectionDefaultRepositoryService extends BaseRepository<ViewProje
return this.translate.instant(plural ? 'Projectiondefaults' : 'Projectiondefault'); return this.translate.instant(plural ? 'Projectiondefaults' : 'Projectiondefault');
}; };
public getTitle = (projectionDefault: Partial<ProjectionDefault> | Partial<ViewProjectionDefault>) => { public getTitle = (titleInformation: ProjectionDefaultTitleInformation) => {
return this.translate.instant(projectionDefault.display_name); return this.translate.instant(titleInformation.display_name);
}; };
public createViewModel(projectionDefault: ProjectionDefault): ViewProjectionDefault { public createViewModel(projectionDefault: ProjectionDefault): ViewProjectionDefault {
const viewProjectionDefault = new ViewProjectionDefault(projectionDefault); return new ViewProjectionDefault(projectionDefault);
viewProjectionDefault.getVerboseName = this.getVerboseName;
viewProjectionDefault.getTitle = () => this.getTitle(viewProjectionDefault);
return viewProjectionDefault;
} }
/** /**

View File

@ -1,17 +1,26 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.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-projector-message'; import {
ViewProjectorMessage,
ProjectorMessageTitleInformation
} from 'app/site/projector/models/view-projector-message';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { TranslateService } from '@ngx-translate/core';
import { DataSendService } from 'app/core/core-services/data-send.service'; import { DataSendService } from 'app/core/core-services/data-send.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class ProjectorMessageRepositoryService extends BaseRepository<ViewProjectorMessage, ProjectorMessage> { export class ProjectorMessageRepositoryService extends BaseRepository<
ViewProjectorMessage,
ProjectorMessage,
ProjectorMessageTitleInformation
> {
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
dataSend: DataSendService, dataSend: DataSendService,
@ -22,13 +31,15 @@ export class ProjectorMessageRepositoryService extends BaseRepository<ViewProjec
super(DS, dataSend, mapperService, viewModelStoreService, translate, ProjectorMessage); super(DS, dataSend, mapperService, viewModelStoreService, translate, ProjectorMessage);
} }
public getTitle = (titleInformation: ProjectorMessageTitleInformation) => {
return this.getVerboseName();
};
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Messages' : 'Message'); return this.translate.instant(plural ? 'Messages' : 'Message');
}; };
protected createViewModel(message: ProjectorMessage): ViewProjectorMessage { protected createViewModel(message: ProjectorMessage): ViewProjectorMessage {
const viewProjectorMessage = new ViewProjectorMessage(message); return new ViewProjectorMessage(message);
viewProjectorMessage.getVerboseName = this.getVerboseName;
return viewProjectorMessage;
} }
} }

View File

@ -1,15 +1,16 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
import { DataSendService } from '../../core-services/data-send.service'; import { DataSendService } from '../../core-services/data-send.service';
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 { ViewProjector } from 'app/site/projector/models/view-projector'; import { ViewProjector, ProjectorTitleInformation } 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'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { TranslateService } from '@ngx-translate/core';
/** /**
* Directions for scale and scroll requests. * Directions for scale and scroll requests.
@ -26,7 +27,7 @@ export enum ScrollScaleDirection {
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class ProjectorRepositoryService extends BaseRepository<ViewProjector, Projector> { export class ProjectorRepositoryService extends BaseRepository<ViewProjector, Projector, ProjectorTitleInformation> {
/** /**
* Constructor calls the parent constructor * Constructor calls the parent constructor
* *
@ -46,14 +47,16 @@ export class ProjectorRepositoryService extends BaseRepository<ViewProjector, Pr
super(DS, dataSend, mapperService, viewModelStoreService, translate, Projector, [Projector]); super(DS, dataSend, mapperService, viewModelStoreService, translate, Projector, [Projector]);
} }
public getTitle = (titleInformation: ProjectorTitleInformation) => {
return titleInformation.name;
};
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Projectors' : 'Projector'); return this.translate.instant(plural ? 'Projectors' : 'Projector');
}; };
public createViewModel(projector: Projector): ViewProjector { public createViewModel(projector: Projector): ViewProjector {
const viewProjector = new ViewProjector(projector); return new ViewProjector(projector);
viewProjector.getVerboseName = this.getVerboseName;
return viewProjector;
} }
/** /**

View File

@ -1,13 +1,14 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { Tag } from 'app/shared/models/core/tag'; import { Tag } from 'app/shared/models/core/tag';
import { ViewTag } from 'app/site/tags/models/view-tag'; import { ViewTag, TagTitleInformation } from 'app/site/tags/models/view-tag';
import { DataSendService } from '../../core-services/data-send.service'; import { DataSendService } from '../../core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { TranslateService } from '@ngx-translate/core';
/** /**
* Repository Services for Tags * Repository Services for Tags
@ -22,7 +23,7 @@ import { TranslateService } from '@ngx-translate/core';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class TagRepositoryService extends BaseRepository<ViewTag, Tag> { export class TagRepositoryService extends BaseRepository<ViewTag, Tag, TagTitleInformation> {
/** /**
* Creates a TagRepository * Creates a TagRepository
* Converts existing and incoming Tags to ViewTags * Converts existing and incoming Tags to ViewTags
@ -43,14 +44,16 @@ export class TagRepositoryService extends BaseRepository<ViewTag, Tag> {
this.initSorting(); this.initSorting();
} }
public getTitle = (titleInformation: TagTitleInformation) => {
return titleInformation.name;
};
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Tags' : 'Tag'); return this.translate.instant(plural ? 'Tags' : 'Tag');
}; };
protected createViewModel(tag: Tag): ViewTag { protected createViewModel(tag: Tag): ViewTag {
const viewTag = new ViewTag(tag); return new ViewTag(tag);
viewTag.getVerboseName = this.getVerboseName;
return viewTag;
} }
/** /**

View File

@ -2,17 +2,17 @@ import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BaseAgendaContentObjectRepository } from '../base-agenda-content-object-repository';
import { CollectionStringMapperService } from 'app/core/core-services/collection-string-mapper.service'; import { CollectionStringMapperService } from 'app/core/core-services/collection-string-mapper.service';
import { DataStoreService } from 'app/core/core-services/data-store.service'; import { DataStoreService } from 'app/core/core-services/data-store.service';
import { DataSendService } from 'app/core/core-services/data-send.service'; import { DataSendService } from 'app/core/core-services/data-send.service';
import { Item } from 'app/shared/models/agenda/item';
import { Mediafile } from 'app/shared/models/mediafiles/mediafile'; import { Mediafile } from 'app/shared/models/mediafiles/mediafile';
import { Topic } from 'app/shared/models/topics/topic'; import { Topic } from 'app/shared/models/topics/topic';
import { ViewTopic } from 'app/site/agenda/models/view-topic'; import { ViewTopic, TopicTitleInformation } from 'app/site/agenda/models/view-topic';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile'; import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
import { ViewItem } from 'app/site/agenda/models/view-item'; import { ViewItem } from 'app/site/agenda/models/view-item';
import { ViewListOfSpeakers } from 'app/site/agenda/models/view-list-of-speakers';
import { BaseIsAgendaItemAndListOfSpeakersContentObjectRepository } from '../base-is-agenda-item-and-list-of-speakers-content-object-repository';
/** /**
* Repository for topics * Repository for topics
@ -20,7 +20,11 @@ import { ViewItem } from 'app/site/agenda/models/view-item';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class TopicRepositoryService extends BaseAgendaContentObjectRepository<ViewTopic, Topic> { export class TopicRepositoryService extends BaseIsAgendaItemAndListOfSpeakersContentObjectRepository<
ViewTopic,
Topic,
TopicTitleInformation
> {
/** /**
* Constructor calls the parent constructor * Constructor calls the parent constructor
* *
@ -35,16 +39,25 @@ export class TopicRepositoryService extends BaseAgendaContentObjectRepository<Vi
viewModelStoreService: ViewModelStoreService, viewModelStoreService: ViewModelStoreService,
translate: TranslateService translate: TranslateService
) { ) {
super(DS, dataSend, mapperService, viewModelStoreService, translate, Topic, [Mediafile, Item]); super(DS, dataSend, mapperService, viewModelStoreService, translate, Topic, [Mediafile]);
} }
public getAgendaTitle = (topic: Partial<Topic> | Partial<ViewTopic>) => { public getTitle = (titleInformation: TopicTitleInformation) => {
return topic.title; if (titleInformation.agenda_item_number) {
return titleInformation.agenda_item_number + ' · ' + titleInformation.title;
} else {
return titleInformation.title;
}
}; };
public getAgendaTitleWithType = (topic: Partial<Topic> | Partial<ViewTopic>) => { public getAgendaListTitle = (titleInformation: TopicTitleInformation) => {
// Do not append ' (Topic)' to the title. // Do not append ' (Topic)' to the title.
return topic.title; return this.getTitle(titleInformation);
};
public getAgendaSlideTitle = (titleInformation: TopicTitleInformation) => {
// Do not append ' (Topic)' to the title.
return this.getTitle(titleInformation);
}; };
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {
@ -60,11 +73,8 @@ export class TopicRepositoryService extends BaseAgendaContentObjectRepository<Vi
public createViewModel(topic: Topic): ViewTopic { public createViewModel(topic: Topic): ViewTopic {
const attachments = this.viewModelStoreService.getMany(ViewMediafile, topic.attachments_id); const attachments = this.viewModelStoreService.getMany(ViewMediafile, topic.attachments_id);
const item = this.viewModelStoreService.get(ViewItem, topic.agenda_item_id); const item = this.viewModelStoreService.get(ViewItem, topic.agenda_item_id);
const viewTopic = new ViewTopic(topic, attachments, item); const listOfSpeakers = this.viewModelStoreService.get(ViewListOfSpeakers, topic.list_of_speakers_id);
viewTopic.getVerboseName = this.getVerboseName; return new ViewTopic(topic, attachments, item, listOfSpeakers);
viewTopic.getAgendaTitle = () => this.getAgendaTitle(viewTopic);
viewTopic.getAgendaTitleWithType = () => this.getAgendaTitle(viewTopic);
return viewTopic;
} }
/** /**

View File

@ -1,14 +1,15 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
import { ConstantsService } from '../../core-services/constants.service'; import { ConstantsService } from '../../core-services/constants.service';
import { DataSendService } from '../../core-services/data-send.service'; import { DataSendService } from '../../core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.service'; 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 { ViewGroup } from 'app/site/users/models/view-group'; import { ViewGroup, GroupTitleInformation } from 'app/site/users/models/view-group';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { TranslateService } from '@ngx-translate/core';
import { HttpService } from 'app/core/core-services/http.service'; import { HttpService } from 'app/core/core-services/http.service';
/** /**
@ -35,7 +36,7 @@ export interface AppPermissions {
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class GroupRepositoryService extends BaseRepository<ViewGroup, Group> { export class GroupRepositoryService extends BaseRepository<ViewGroup, Group, GroupTitleInformation> {
/** /**
* holds sorted permissions per app. * holds sorted permissions per app.
*/ */
@ -61,14 +62,16 @@ export class GroupRepositoryService extends BaseRepository<ViewGroup, Group> {
this.sortPermsPerApp(); this.sortPermsPerApp();
} }
public getTitle = (titleInformation: GroupTitleInformation) => {
return titleInformation.name;
};
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Groups' : 'Group'); return this.translate.instant(plural ? 'Groups' : 'Group');
}; };
public createViewModel(group: Group): ViewGroup { public createViewModel(group: Group): ViewGroup {
const viewGroup = new ViewGroup(group); return new ViewGroup(group);
viewGroup.getVerboseName = this.getVerboseName;
return viewGroup;
} }
/** /**

View File

@ -8,7 +8,7 @@ import { BaseRepository } from '../base-repository';
import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service'; import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
import { Identifiable } from 'app/shared/models/base/identifiable'; import { Identifiable } from 'app/shared/models/base/identifiable';
import { PersonalNote } from 'app/shared/models/users/personal-note'; import { PersonalNote } from 'app/shared/models/users/personal-note';
import { ViewPersonalNote } from 'app/site/users/models/view-personal-note'; import { ViewPersonalNote, PersonalNoteTitleInformation } from 'app/site/users/models/view-personal-note';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
/** /**
@ -16,7 +16,11 @@ import { ViewModelStoreService } from 'app/core/core-services/view-model-store.s
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class PersonalNoteRepositoryService extends BaseRepository<ViewPersonalNote, PersonalNote> { export class PersonalNoteRepositoryService extends BaseRepository<
ViewPersonalNote,
PersonalNote,
PersonalNoteTitleInformation
> {
/** /**
* @param DS The DataStore * @param DS The DataStore
* @param mapperService Maps collection strings to classes * @param mapperService Maps collection strings to classes
@ -31,14 +35,16 @@ export class PersonalNoteRepositoryService extends BaseRepository<ViewPersonalNo
super(DS, dataSend, mapperService, viewModelStoreService, translate, PersonalNote); super(DS, dataSend, mapperService, viewModelStoreService, translate, PersonalNote);
} }
public getTitle = (titleInformation: PersonalNoteTitleInformation) => {
return this.getVerboseName();
};
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Personal notes' : 'Personal note'); return this.translate.instant(plural ? 'Personal notes' : 'Personal note');
}; };
protected createViewModel(personalNote: PersonalNote): ViewPersonalNote { protected createViewModel(personalNote: PersonalNote): ViewPersonalNote {
const viewPersonalNote = new ViewPersonalNote(personalNote); return new ViewPersonalNote(personalNote);
viewPersonalNote.getVerboseName = this.getVerboseName;
return viewPersonalNote;
} }
/** /**

View File

@ -12,7 +12,7 @@ import { Group } from 'app/shared/models/users/group';
import { HttpService } from 'app/core/core-services/http.service'; import { HttpService } from 'app/core/core-services/http.service';
import { NewEntry } from 'app/core/ui-services/base-import.service'; import { NewEntry } from 'app/core/ui-services/base-import.service';
import { User } from 'app/shared/models/users/user'; import { User } from 'app/shared/models/users/user';
import { ViewUser } from 'app/site/users/models/view-user'; import { ViewUser, UserTitleInformation } from 'app/site/users/models/view-user';
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'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
@ -32,7 +32,7 @@ type SortProperty = 'first_name' | 'last_name' | 'number';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class UserRepositoryService extends BaseRepository<ViewUser, User> { export class UserRepositoryService extends BaseRepository<ViewUser, User, UserTitleInformation> {
/** /**
* The property the incoming data is sorted by * The property the incoming data is sorted by
*/ */
@ -65,6 +65,55 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {
}); });
} }
public getTitle = (titleInformation: UserTitleInformation) => {
return this.getFullName(titleInformation);
};
/**
* Getter for the short name (Title, given name, surname)
*
* @returns a non-empty string
*/
public getShortName(titleInformation: UserTitleInformation): string {
const title = titleInformation.title ? titleInformation.title.trim() : '';
const firstName = titleInformation.first_name ? titleInformation.first_name.trim() : '';
const lastName = titleInformation.last_name ? titleInformation.last_name.trim() : '';
let shortName = `${firstName} ${lastName}`;
if (shortName.length <= 1) {
// We have at least one space from the concatination of
// first- and lastname.
shortName = titleInformation.username;
}
if (title) {
shortName = `${title} ${shortName}`;
}
return shortName;
}
public getFullName(titleInformation: UserTitleInformation): string {
let name = this.getShortName(titleInformation);
const additions: string[] = [];
// addition: add number and structure level
const structure_level = titleInformation.structure_level ? titleInformation.structure_level.trim() : '';
if (structure_level) {
additions.push(structure_level);
}
const number = titleInformation.number ? titleInformation.number.trim() : '';
if (number) {
additions.push(`${this.translate.instant('No.')} ${number}`);
}
if (additions.length > 0) {
name += ' (' + additions.join(' · ') + ')';
}
return name.trim();
}
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Participants' : 'Participant'); return this.translate.instant(plural ? 'Participants' : 'Participant');
}; };
@ -72,10 +121,8 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {
public createViewModel(user: User): ViewUser { public createViewModel(user: User): ViewUser {
const groups = this.viewModelStoreService.getMany(ViewGroup, user.groups_id); const groups = this.viewModelStoreService.getMany(ViewGroup, user.groups_id);
const viewUser = new ViewUser(user, groups); const viewUser = new ViewUser(user, groups);
viewUser.getVerboseName = this.getVerboseName; viewUser.getFullName = () => this.getFullName(viewUser);
viewUser.getNumberForName = (nr: number) => { viewUser.getShortName = () => this.getShortName(viewUser);
return `${this.translate.instant('No.')} ${nr}`;
};
return viewUser; return viewUser;
} }

View File

@ -2,7 +2,7 @@ import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { BaseModel } from 'app/shared/models/base/base-model'; import { BaseModel } from 'app/shared/models/base/base-model';
import { BaseRepository } from '../repositories/base-repository'; import { BaseRepository } from '../repositories/base-repository';
import { BaseViewModel } from '../../site/base/base-view-model'; import { BaseViewModel, TitleInformation } from '../../site/base/base-view-model';
import { StorageService } from '../core-services/storage.service'; import { StorageService } from '../core-services/storage.service';
/** /**
@ -208,7 +208,7 @@ export abstract class BaseFilterListService<V extends BaseViewModel> {
* @param exexcludeIds Set if certain ID's should be excluded from filtering * @param exexcludeIds Set if certain ID's should be excluded from filtering
*/ */
protected updateFilterForRepo( protected updateFilterForRepo(
repo: BaseRepository<BaseViewModel, BaseModel>, repo: BaseRepository<BaseViewModel, BaseModel, TitleInformation>,
filter: OsFilter, filter: OsFilter,
noneOptionLabel?: string, noneOptionLabel?: string,
excludeIds?: number[] excludeIds?: number[]

View File

@ -92,7 +92,7 @@ export class SearchService {
*/ */
public registerModel( public registerModel(
collectionString: string, collectionString: string,
repo: BaseRepository<any, any>, repo: BaseRepository<any, any, any>,
displayOrder: number, displayOrder: number,
openInNewTab: boolean = false openInNewTab: boolean = false
): void { ): void {

View File

@ -0,0 +1,16 @@
<ng-container *osPerms="'agenda.can_see_list_of_speakers'">
<ng-container *ngIf="listOfSpeakers">
<button type="button" *ngIf="!menuItem" mat-icon-button [routerLink]="listOfSpeakers.listOfSpeakersUrl" [disabled]="disabled">
<mat-icon
[matBadge]="listOfSpeakers.waitingSpeakerAmount > 0 ? listOfSpeakers.waitingSpeakerAmount : null"
matBadgeColor="accent"
>
mic
</mat-icon>
</button>
<button type="button" *ngIf="menuItem" mat-menu-item [routerLink]="listOfSpeakers.listOfSpeakersUrl">
<mat-icon>mic</mat-icon>
<span translate>List of speakers</span>
</button>
</ng-container>
</ng-container>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { E2EImportsModule } from 'e2e-imports.module';
import { SpeakerButtonComponent } from './speaker-button.component';
describe('SpeakerButtonComponent', () => {
let component: SpeakerButtonComponent;
let fixture: ComponentFixture<SpeakerButtonComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SpeakerButtonComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,45 @@
import { Component, Input } from '@angular/core';
import { ContentObject, isContentObject } from 'app/shared/models/base/content-object';
import { ViewListOfSpeakers } from 'app/site/agenda/models/view-list-of-speakers';
import {
BaseViewModelWithListOfSpeakers,
isBaseViewModelWithListOfSpeakers
} from 'app/site/base/base-view-model-with-list-of-speakers';
import { ListOfSpeakersRepositoryService } from 'app/core/repositories/agenda/list-of-speakers-repository.service';
/**
* A generic button to go to the list of speakers. Give the content object with
* [object]=object, which can be a ContentObject or a ViewModelWithListOfSpeakers.
* - Usage as a mini-fab (like in the agenda) with [menuItem]=false (default)
* - Usage in a dropdown (=list) with [menuItem]=true
*/
@Component({
selector: 'os-speaker-button',
templateUrl: './speaker-button.component.html'
})
export class SpeakerButtonComponent {
@Input()
public set object(obj: BaseViewModelWithListOfSpeakers | ContentObject | null) {
if (isBaseViewModelWithListOfSpeakers(obj)) {
this.listOfSpeakers = obj.listOfSpeakers;
} else if (isContentObject(obj)) {
this.listOfSpeakers = this.listOfSpeakersRepo.findByContentObject(obj);
} else {
this.listOfSpeakers = null;
}
}
public listOfSpeakers: ViewListOfSpeakers | null;
@Input()
public disabled: boolean;
@Input()
public menuItem = false;
/**
* The constructor
*/
public constructor(private listOfSpeakersRepo: ListOfSpeakersRepositoryService) {}
}

View File

@ -1,14 +1,5 @@
import { Speaker } from './speaker'; import { ContentObject } from '../base/content-object';
import { BaseModel } from '../base/base-model'; import { BaseModelWithContentObject } from '../base/base-model-with-content-object';
/**
* The representation of the content object for agenda items. The unique combination
* of the collection and id is given.
*/
interface ContentObject {
id: number;
collection: string;
}
/** /**
* Determine visibility states for agenda items * Determine visibility states for agenda items
@ -24,19 +15,36 @@ export const itemVisibilityChoices = [
* Representations of agenda Item * Representations of agenda Item
* @ignore * @ignore
*/ */
export class Item extends BaseModel<Item> { export class Item extends BaseModelWithContentObject<Item> {
public static COLLECTIONSTRING = 'agenda/item'; public static COLLECTIONSTRING = 'agenda/item';
// TODO: remove this, if the server can properly include the agenda item number
// in the title information. See issue #4738
private _itemNumber: string;
private _titleInformation: any;
public id: number; public id: number;
public item_number: string; public get item_number(): string {
public title_information: object; return this._itemNumber;
}
public set item_number(val: string) {
this._itemNumber = val;
if (this._titleInformation) {
this._titleInformation.agenda_item_number = this.item_number;
}
}
public get title_information(): object {
return this._titleInformation;
}
public set title_information(val: object) {
this._titleInformation = val;
this._titleInformation.agenda_item_number = this.item_number;
}
public comment: string; public comment: string;
public closed: boolean; public closed: boolean;
public type: number; public type: number;
public is_hidden: boolean; public is_hidden: boolean;
public duration: number; // minutes public duration: number; // minutes
public speakers: Speaker[];
public speaker_list_closed: boolean;
public content_object: ContentObject; public content_object: ContentObject;
public weight: number; public weight: number;
public parent_id: number; public parent_id: number;
@ -45,14 +53,4 @@ export class Item extends BaseModel<Item> {
public constructor(input?: any) { public constructor(input?: any) {
super(Item.COLLECTIONSTRING, input); super(Item.COLLECTIONSTRING, input);
} }
public deserialize(input: any): void {
Object.assign(this, input);
if (input.speakers instanceof Array) {
this.speakers = input.speakers.map(speakerData => {
return new Speaker(speakerData);
});
}
}
} }

View File

@ -0,0 +1,21 @@
import { Speaker } from './speaker';
import { ContentObject } from '../base/content-object';
import { BaseModelWithContentObject } from '../base/base-model-with-content-object';
/**
* Representations of agenda Item
* @ignore
*/
export class ListOfSpeakers extends BaseModelWithContentObject<ListOfSpeakers> {
public static COLLECTIONSTRING = 'agenda/list-of-speakers';
public id: number;
public title_information: object;
public speakers: Speaker[];
public closed: boolean;
public content_object: ContentObject;
public constructor(input?: any) {
super(ListOfSpeakers.COLLECTIONSTRING, input);
}
}

View File

@ -1,14 +1,3 @@
import { Deserializer } from '../base/deserializer';
/**
* Determine the state of the speaker
*/
export enum SpeakerState {
WAITING,
CURRENT,
FINISHED
}
/** /**
* Representation of a speaker in an agenda item. * Representation of a speaker in an agenda item.
* *
@ -16,49 +5,23 @@ export enum SpeakerState {
* Part of the 'speakers' list. * Part of the 'speakers' list.
* @ignore * @ignore
*/ */
export class Speaker extends Deserializer { export interface Speaker {
public static COLLECTIONSTRING = 'agenda/item/speakers'; id: number;
user_id: number;
public id: number;
public user_id: number;
/** /**
* ISO datetime string to indicate the begin time of the speech. Empty if * ISO datetime string to indicate the begin time of the speech. Empty if
* the speaker has not started * the speaker has not started
*/ */
public begin_time: string; begin_time: string;
/** /**
* ISO datetime string to indicate the end time of the speech. Empty if the * ISO datetime string to indicate the end time of the speech. Empty if the
* speech has not ended * speech has not ended
*/ */
public end_time: string; end_time: string;
public weight: number; weight: number;
public marked: boolean; marked: boolean;
public item_id: number; item_id: number;
/**
* Needs to be completely optional because agenda has (yet) the optional parameter 'speaker'
* @param input
*/
public constructor(input?: any) {
super(input);
}
/**
* @returns
* - waiting if there is no begin nor end time
* - current if there is a begin time and not end time
* - finished if there are both begin and end time
*/
public get state(): SpeakerState {
if (!this.begin_time && !this.end_time) {
return SpeakerState.WAITING;
} else if (this.begin_time && !this.end_time) {
return SpeakerState.CURRENT;
} else {
return SpeakerState.FINISHED;
}
}
} }

View File

@ -1,12 +1,12 @@
import { BaseModel } from '../base/base-model';
import { AssignmentRelatedUser } from './assignment-related-user'; import { AssignmentRelatedUser } from './assignment-related-user';
import { AssignmentPoll } from './assignment-poll'; import { AssignmentPoll } from './assignment-poll';
import { BaseModelWithAgendaItemAndListOfSpeakers } from '../base/base-model-with-agenda-item-and-list-of-speakers';
/** /**
* Representation of an assignment. * Representation of an assignment.
* @ignore * @ignore
*/ */
export class Assignment extends BaseModel<Assignment> { export class Assignment extends BaseModelWithAgendaItemAndListOfSpeakers<Assignment> {
public static COLLECTIONSTRING = 'assignments/assignment'; public static COLLECTIONSTRING = 'assignments/assignment';
public id: number; public id: number;
@ -17,7 +17,6 @@ export class Assignment extends BaseModel<Assignment> {
public assignment_related_users: AssignmentRelatedUser[]; public assignment_related_users: AssignmentRelatedUser[];
public poll_description_default: number; public poll_description_default: number;
public polls: AssignmentPoll[]; public polls: AssignmentPoll[];
public agenda_item_id: number;
public tags_id: number[]; public tags_id: number[];
public attachments_id: number[]; public attachments_id: number[];

View File

@ -0,0 +1,16 @@
import { BaseModel } from './base-model';
import { BaseModelWithAgendaItem, isBaseModelWithAgendaItem } from './base-model-with-agenda-item';
import { BaseModelWithListOfSpeakers, isBaseModelWithListOfSpeakers } from './base-model-with-list-of-speakers';
export function isBaseModelWithAgendaItemAndListOfSpeakers(obj: any): obj is BaseModelWithAgendaItemAndListOfSpeakers {
return !!obj && isBaseModelWithAgendaItem(obj) && isBaseModelWithListOfSpeakers(obj);
}
/**
* A base model with an agenda item and a list of speakers.
*/
export abstract class BaseModelWithAgendaItemAndListOfSpeakers<T = object> extends BaseModel<T>
implements BaseModelWithAgendaItem<T>, BaseModelWithListOfSpeakers<T> {
public agenda_item_id: number;
public list_of_speakers_id: number;
}

View File

@ -0,0 +1,12 @@
import { BaseModel } from './base-model';
export function isBaseModelWithAgendaItem(obj: any): obj is BaseModelWithAgendaItem {
return !!obj && (<BaseModelWithAgendaItem>obj).agenda_item_id !== undefined;
}
/**
* A base model which has an agenda item. These models have a `agenda_item_id` in any case.
*/
export abstract class BaseModelWithAgendaItem<T = object> extends BaseModel<T> {
public agenda_item_id: number;
}

View File

@ -0,0 +1,9 @@
import { ContentObject } from './content-object';
import { BaseModel } from './base-model';
/**
* A base model which has a content object, like items of list of speakers.
*/
export abstract class BaseModelWithContentObject<T = object> extends BaseModel<T> {
public abstract content_object: ContentObject;
}

View File

@ -0,0 +1,12 @@
import { BaseModel } from './base-model';
export function isBaseModelWithListOfSpeakers(obj: any): obj is BaseModelWithListOfSpeakers {
return !!obj && (<BaseModelWithListOfSpeakers>obj).list_of_speakers_id !== undefined;
}
/**
* A base model with a list of speakers. The id is always given by the server.
*/
export abstract class BaseModelWithListOfSpeakers<T = object> extends BaseModel<T> {
public list_of_speakers_id: number;
}

View File

@ -0,0 +1,12 @@
export function isContentObject(obj: any): obj is ContentObject {
return !!obj && obj.id !== undefined && obj.collection !== undefined;
}
/**
* The representation of content objects. Holds the unique combination
* of the collection and the id.
*/
export interface ContentObject {
id: number;
collection: string;
}

View File

@ -1,19 +0,0 @@
import { Deserializer } from '../base/deserializer';
/**
* The name and the type of a mediaFile.
* @ignore
*/
export class File extends Deserializer {
public name: string;
public type: string;
/**
* Needs to be fully optional, because the 'mediafile'-property in the mediaFile class is optional as well
* @param name The name of the file
* @param type The tape (jpg, png, pdf)
*/
public constructor(input?: any) {
super(input);
}
}

View File

@ -1,15 +1,23 @@
import { File } from './file'; import { BaseModelWithListOfSpeakers } from '../base/base-model-with-list-of-speakers';
import { BaseModel } from '../base/base-model';
interface FileMetadata {
name: string;
type: string;
// Only for PDFs
pages: number;
encrypted?: boolean;
}
/** /**
* Representation of MediaFile. Has the nested property "File" * Representation of MediaFile. Has the nested property "File"
* @ignore * @ignore
*/ */
export class Mediafile extends BaseModel<Mediafile> { export class Mediafile extends BaseModelWithListOfSpeakers<Mediafile> {
public static COLLECTIONSTRING = 'mediafiles/mediafile'; public static COLLECTIONSTRING = 'mediafiles/mediafile';
public id: number; public id: number;
public title: string; public title: string;
public mediafile: File; public mediafile: FileMetadata;
public media_url_prefix: string; public media_url_prefix: string;
public uploader_id: number; public uploader_id: number;
public filesize: string; public filesize: string;
@ -20,11 +28,6 @@ export class Mediafile extends BaseModel<Mediafile> {
super(Mediafile.COLLECTIONSTRING, input); super(Mediafile.COLLECTIONSTRING, input);
} }
public deserialize(input: any): void {
Object.assign(this, input);
this.mediafile = new File(input.mediafile);
}
/** /**
* Determine the downloadURL * Determine the downloadURL
* *

View File

@ -1,15 +1,14 @@
import { BaseModel } from '../base/base-model'; import { BaseModelWithAgendaItemAndListOfSpeakers } from '../base/base-model-with-agenda-item-and-list-of-speakers';
/** /**
* Representation of a motion block. * Representation of a motion block.
* @ignore * @ignore
*/ */
export class MotionBlock extends BaseModel { export class MotionBlock extends BaseModelWithAgendaItemAndListOfSpeakers<MotionBlock> {
public static COLLECTIONSTRING = 'motions/motion-block'; public static COLLECTIONSTRING = 'motions/motion-block';
public id: number; public id: number;
public title: string; public title: string;
public agenda_item_id: number;
public constructor(input?: any) { public constructor(input?: any) {
super(MotionBlock.COLLECTIONSTRING, input); super(MotionBlock.COLLECTIONSTRING, input);

View File

@ -1,6 +1,6 @@
import { MotionSubmitter } from './motion-submitter'; import { MotionSubmitter } from './motion-submitter';
import { MotionPoll } from './motion-poll'; import { MotionPoll } from './motion-poll';
import { BaseModel } from '../base/base-model'; import { BaseModelWithAgendaItemAndListOfSpeakers } from '../base/base-model-with-agenda-item-and-list-of-speakers';
export interface MotionComment { export interface MotionComment {
id: number; id: number;
@ -12,11 +12,11 @@ export interface MotionComment {
/** /**
* Representation of Motion. * Representation of Motion.
* *
* Slightly Defined cause heavy maintaince on server side. * Slightly defined cause heavy maintenance on server side.
* *
* @ignore * @ignore
*/ */
export class Motion extends BaseModel { export class Motion extends BaseModelWithAgendaItemAndListOfSpeakers<Motion> {
public static COLLECTIONSTRING = 'motions/motion'; public static COLLECTIONSTRING = 'motions/motion';
public id: number; public id: number;
@ -44,7 +44,6 @@ export class Motion extends BaseModel {
public tags_id: number[]; public tags_id: number[];
public attachments_id: number[]; public attachments_id: number[];
public polls: MotionPoll[]; public polls: MotionPoll[];
public agenda_item_id: number;
public weight: number; public weight: number;
public sort_parent_id: number; public sort_parent_id: number;
public created: string; public created: string;

View File

@ -1,17 +1,16 @@
import { BaseModel } from '../base/base-model'; import { BaseModelWithAgendaItemAndListOfSpeakers } from '../base/base-model-with-agenda-item-and-list-of-speakers';
/** /**
* Representation of a topic. * Representation of a topic.
* @ignore * @ignore
*/ */
export class Topic extends BaseModel<Topic> { export class Topic extends BaseModelWithAgendaItemAndListOfSpeakers<Topic> {
public static COLLECTIONSTRING = 'topics/topic'; public static COLLECTIONSTRING = 'topics/topic';
public id: number; public id: number;
public title: string; public title: string;
public text: string; public text: string;
public attachments_id: number[]; public attachments_id: number[];
public agenda_item_id: number;
public constructor(input?: any) { public constructor(input?: any) {
super(Topic.COLLECTIONSTRING, input); super(Topic.COLLECTIONSTRING, input);

View File

@ -83,6 +83,7 @@ import { SlideContainerComponent } from './components/slide-container/slide-cont
import { CountdownTimeComponent } from './components/contdown-time/countdown-time.component'; import { CountdownTimeComponent } from './components/contdown-time/countdown-time.component';
import { MediaUploadContentComponent } from './components/media-upload-content/media-upload-content.component'; import { MediaUploadContentComponent } from './components/media-upload-content/media-upload-content.component';
import { PrecisionPipe } from './pipes/precision.pipe'; import { PrecisionPipe } from './pipes/precision.pipe';
import { SpeakerButtonComponent } from './components/speaker-button/speaker-button.component';
/** /**
* Share Module for all "dumb" components and pipes. * Share Module for all "dumb" components and pipes.
@ -206,7 +207,8 @@ import { PrecisionPipe } from './pipes/precision.pipe';
CountdownTimeComponent, CountdownTimeComponent,
MediaUploadContentComponent, MediaUploadContentComponent,
PrecisionPipe, PrecisionPipe,
ScrollingModule ScrollingModule,
SpeakerButtonComponent
], ],
declarations: [ declarations: [
PermsDirective, PermsDirective,
@ -234,7 +236,8 @@ import { PrecisionPipe } from './pipes/precision.pipe';
SlideContainerComponent, SlideContainerComponent,
CountdownTimeComponent, CountdownTimeComponent,
MediaUploadContentComponent, MediaUploadContentComponent,
PrecisionPipe PrecisionPipe,
SpeakerButtonComponent
], ],
providers: [ providers: [
{ provide: DateAdapter, useClass: OpenSlidesDateAdapter }, { provide: DateAdapter, useClass: OpenSlidesDateAdapter },

View File

@ -7,7 +7,7 @@ import { BaseImportService, NewEntry } from 'app/core/ui-services/base-import.se
import { CreateTopic } from './models/create-topic'; import { CreateTopic } from './models/create-topic';
import { DurationService } from 'app/core/ui-services/duration.service'; import { DurationService } from 'app/core/ui-services/duration.service';
import { itemVisibilityChoices } from 'app/shared/models/agenda/item'; import { itemVisibilityChoices } from 'app/shared/models/agenda/item';
import { TopicRepositoryService } from '../../core/repositories/agenda/topic-repository.service'; import { TopicRepositoryService } from '../../core/repositories/topics/topic-repository.service';
import { ViewCreateTopic } from './models/view-create-topic'; import { ViewCreateTopic } from './models/view-create-topic';
@Injectable({ @Injectable({

View File

@ -18,9 +18,9 @@ const routes: Routes = [
canDeactivate: [WatchSortingTreeGuard], canDeactivate: [WatchSortingTreeGuard],
data: { basePerm: 'agenda.can_manage' } data: { basePerm: 'agenda.can_manage' }
}, },
{ path: 'speakers', component: ListOfSpeakersComponent, data: { basePerm: 'agenda.can_see' } }, { path: 'speakers', component: ListOfSpeakersComponent, data: { basePerm: 'agenda.can_see_list_of_speakers' } },
{ path: 'topics/:id', component: TopicDetailComponent, data: { basePerm: 'agenda.can_see' } }, { path: 'speakers/:id', component: ListOfSpeakersComponent, data: { basePerm: 'agenda.can_see_list_of_speakers' } },
{ path: ':id/speakers', component: ListOfSpeakersComponent, data: { basePerm: 'agenda.can_see' } } { path: 'topics/:id', component: TopicDetailComponent, data: { basePerm: 'agenda.can_see' } }
]; ];
@NgModule({ @NgModule({

View File

@ -2,14 +2,23 @@ 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 { ItemRepositoryService } from 'app/core/repositories/agenda/item-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/topics/topic-repository.service';
import { ViewTopic } from './models/view-topic'; import { ViewTopic } from './models/view-topic';
import { ViewItem } from './models/view-item'; import { ViewItem } from './models/view-item';
import { ListOfSpeakers } from 'app/shared/models/agenda/list-of-speakers';
import { ViewListOfSpeakers } from './models/view-list-of-speakers';
import { ListOfSpeakersRepositoryService } from 'app/core/repositories/agenda/list-of-speakers-repository.service';
export const AgendaAppConfig: AppConfig = { export const AgendaAppConfig: AppConfig = {
name: 'agenda', name: 'agenda',
models: [ models: [
{ collectionString: 'agenda/item', model: Item, viewModel: ViewItem, repository: ItemRepositoryService }, { collectionString: 'agenda/item', model: Item, viewModel: ViewItem, repository: ItemRepositoryService },
{
collectionString: 'agenda/list-of-speakers',
model: ListOfSpeakers,
viewModel: ViewListOfSpeakers,
repository: ListOfSpeakersRepositoryService
},
{ {
collectionString: 'topics/topic', collectionString: 'topics/topic',
model: Topic, model: Topic,

View File

@ -77,14 +77,7 @@
<ng-container matColumnDef="speakers"> <ng-container matColumnDef="speakers">
<mat-header-cell *matHeaderCellDef mat-sort-header>Speakers</mat-header-cell> <mat-header-cell *matHeaderCellDef mat-sort-header>Speakers</mat-header-cell>
<mat-cell *matCellDef="let item"> <mat-cell *matCellDef="let item">
<button mat-icon-button (click)="onSpeakerIcon(item, $event)" [disabled]="isMultiSelect"> <os-speaker-button [object]="item.contentObjectData" [disabled]="isMultiSelect"></os-speaker-button>
<mat-icon
[matBadge]="item.waitingSpeakerAmount > 0 ? item.waitingSpeakerAmount : null"
matBadgeColor="accent"
>
mic
</mat-icon>
</button>
</mat-cell> </mat-cell>
</ng-container> </ng-container>

View File

@ -21,6 +21,8 @@ import { ViewItem } from '../../models/view-item';
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable'; import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
import { _ } from 'app/core/translate/translation-marker'; import { _ } from 'app/core/translate/translation-marker';
import { StorageService } from 'app/core/core-services/storage.service'; import { StorageService } from 'app/core/core-services/storage.service';
import { ListOfSpeakersRepositoryService } from 'app/core/repositories/agenda/list-of-speakers-repository.service';
import { ViewListOfSpeakers } from '../../models/view-list-of-speakers';
/** /**
* List view for the agenda. * List view for the agenda.
@ -35,12 +37,12 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem, Item, I
/** /**
* Determine the display columns in desktop view * Determine the display columns in desktop view
*/ */
public displayedColumnsDesktop: string[] = ['title', 'info', 'speakers']; public displayedColumnsDesktop: string[] = ['title', 'info'];
/** /**
* Determine the display columns in mobile view * Determine the display columns in mobile view
*/ */
public displayedColumnsMobile: string[] = ['title', 'speakers']; public displayedColumnsMobile: string[] = ['title'];
public isNumberingAllowed: boolean; public isNumberingAllowed: boolean;
@ -105,7 +107,8 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem, Item, I
private csvExport: AgendaCsvExportService, private csvExport: AgendaCsvExportService,
public filterService: AgendaFilterListService, public filterService: AgendaFilterListService,
private agendaPdfService: AgendaPdfService, private agendaPdfService: AgendaPdfService,
private pdfService: PdfDocumentService private pdfService: PdfDocumentService,
private listOfSpeakersRepo: ListOfSpeakersRepositoryService
) { ) {
super(titleService, translate, matSnackBar, repo, route, storage, filterService); super(titleService, translate, matSnackBar, repo, route, storage, filterService);
@ -126,6 +129,16 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem, Item, I
this.setFulltextFilter(); this.setFulltextFilter();
} }
/**
* Gets the list of speakers for an agenda item. Might be null, if the items content
* object does not have a list of speakers.
*
* @param item The item to get the list of speakers from
*/
public getListOfSpeakers(item: ViewItem): ViewListOfSpeakers | null {
return this.listOfSpeakersRepo.findByContentObject(item.item.content_object);
}
/** /**
* Links to the content object. * Links to the content object.
* *
@ -183,16 +196,6 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem, Item, I
await this.repo.update({ closed: !item.closed }, item).then(null, this.raiseError); await this.repo.update({ closed: !item.closed }, item).then(null, this.raiseError);
} }
/**
* Handler for the speakers button
*
* @param item indicates the row that was clicked on
*/
public onSpeakerIcon(item: ViewItem, event: MouseEvent): void {
event.stopPropagation();
this.router.navigate([`${item.id}/speakers`], { relativeTo: this.route });
}
/** /**
* Handler for the plus button. * Handler for the plus button.
* Comes from the HeadBar Component * Comes from the HeadBar Component
@ -258,6 +261,9 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem, Item, I
*/ */
public getColumnDefinition(): string[] { public getColumnDefinition(): string[] {
let columns = this.vp.isMobile ? this.displayedColumnsMobile : this.displayedColumnsDesktop; let columns = this.vp.isMobile ? this.displayedColumnsMobile : this.displayedColumnsDesktop;
if (this.operator.hasPerms('agenda.can_see_list_of_speakers')) {
columns = columns.concat(['speakers']);
}
if (this.operator.hasPerms('agenda.can_manage')) { if (this.operator.hasPerms('agenda.can_manage')) {
columns = columns.concat(['menu']); columns = columns.concat(['menu']);
} }

View File

@ -2,8 +2,8 @@
<!-- Title --> <!-- Title -->
<div class="title-slot"> <div class="title-slot">
<h2> <h2>
<span *ngIf="!currentListOfSpeakers" translate>List of speakers</span> <span *ngIf="!isCurrentListOfSpeakers" translate>List of speakers</span>
<span *ngIf="currentListOfSpeakers" translate>Current list of speakers</span> <span *ngIf="isCurrentListOfSpeakers" translate>Current list of speakers</span>
</h2> </h2>
</div> </div>
<div class="menu-slot" *osPerms="['agenda.can_manage_list_of_speakers', 'core.can_manage_projector']"> <div class="menu-slot" *osPerms="['agenda.can_manage_list_of_speakers', 'core.can_manage_projector']">
@ -11,9 +11,9 @@
</div> </div>
</os-head-bar> </os-head-bar>
<mat-card class="os-card speaker-card" *ngIf="viewItem"> <mat-card class="os-card speaker-card" *ngIf="viewListOfSpeakers">
<!-- Title --> <!-- Title -->
<h1 class="los-title on-transition-fade" *ngIf="viewItem">{{ viewItem.getTitle() }}</h1> <h1 class="los-title on-transition-fade">{{ viewListOfSpeakers.getTitle() }}</h1>
<!-- List of finished speakers --> <!-- List of finished speakers -->
<mat-expansion-panel *ngIf="finishedSpeakers && finishedSpeakers.length > 0" class="finished-list"> <mat-expansion-panel *ngIf="finishedSpeakers && finishedSpeakers.length > 0" class="finished-list">
@ -24,7 +24,7 @@
<mat-list-item *ngFor="let speaker of finishedSpeakers; let number = index"> <mat-list-item *ngFor="let speaker of finishedSpeakers; let number = index">
<div class="finished-speaker-grid"> <div class="finished-speaker-grid">
<div class="number">{{ number + 1 }}.</div> <div class="number">{{ number + 1 }}.</div>
<div class="name">{{ speaker }}</div> <div class="name">{{ speaker.getTitle() }}</div>
<div class="time"> <div class="time">
{{ durationString(speaker) }} ({{ 'Start time' | translate }}: {{ startTimeToString(speaker) }}) {{ durationString(speaker) }} ({{ 'Start time' | translate }}: {{ startTimeToString(speaker) }})
</div> </div>
@ -53,7 +53,7 @@
<mat-icon>mic</mat-icon> <mat-icon>mic</mat-icon>
</span> </span>
<span class="name">{{ activeSpeaker }}</span> <span class="name">{{ activeSpeaker.getTitle() }}</span>
<span class="suffix"> <span class="suffix">
<!-- Stop speaker button --> <!-- Stop speaker button -->
@ -77,29 +77,29 @@
[enable]="opCanManage()" [enable]="opCanManage()"
(sortEvent)="onSortingChange($event)" (sortEvent)="onSortingChange($event)"
> >
<!-- implicit item references into the component using ng-template slot --> <!-- implicit speaker references into the component using ng-template slot -->
<ng-template let-item> <ng-template let-speaker>
<span *osPerms="'agenda.can_manage_list_of_speakers'"> <span *osPerms="'agenda.can_manage_list_of_speakers'">
<span *ngIf="hasSpokenCount(item)" class="red-warning-text speaker-warning"> <span *ngIf="hasSpokenCount(speaker)" class="red-warning-text speaker-warning">
{{ hasSpokenCount(item) + 1 }}. <span translate>contribution</span> {{ hasSpokenCount(speaker) + 1 }}. <span translate>contribution</span>
</span> </span>
<span *ngIf="item.gender">({{ item.gender | translate }})</span> <span *ngIf="speaker.gender">({{ speaker.gender | translate }})</span>
</span> </span>
<!-- Start, start and delete buttons --> <!-- Start, start and delete buttons -->
<span *osPerms="'agenda.can_manage_list_of_speakers'"> <span *osPerms="'agenda.can_manage_list_of_speakers'">
<!-- start button --> <!-- start button -->
<button mat-icon-button matTooltip="{{ 'Begin speech' | translate }}" (click)="onStartButton(item)"> <button mat-icon-button matTooltip="{{ 'Begin speech' | translate }}" (click)="onStartButton(speaker)">
<mat-icon>play_arrow</mat-icon> <mat-icon>play_arrow</mat-icon>
</button> </button>
<!-- star button --> <!-- star button -->
<button mat-icon-button matTooltip="{{ 'Mark speaker' | translate }}" (click)="onMarkButton(item)"> <button mat-icon-button matTooltip="{{ 'Mark speaker' | translate }}" (click)="onMarkButton(speaker)">
<mat-icon>{{ item.marked ? 'star' : 'star_border' }}</mat-icon> <mat-icon>{{ speaker.marked ? 'star' : 'star_border' }}</mat-icon>
</button> </button>
<!-- delete button --> <!-- delete button -->
<button mat-icon-button matTooltip="{{ 'Remove' | translate }}" (click)="onDeleteButton(item)"> <button mat-icon-button matTooltip="{{ 'Remove' | translate }}" (click)="onDeleteButton(speaker)">
<mat-icon>close</mat-icon> <mat-icon>close</mat-icon>
</button> </button>
</span> </span>
@ -139,39 +139,39 @@
<mat-menu #speakerMenu="matMenu"> <mat-menu #speakerMenu="matMenu">
<os-projector-button <os-projector-button
*ngIf="viewItem && projectors && projectors.length > 1" *ngIf="viewListOfSpeakers && projectors && projectors.length > 1"
[object]="getClosSlide()" [object]="getClosSlide()"
[menuItem]="true" [menuItem]="true"
text="Current list of speakers (as slide)" text="Current list of speakers (as slide)"
></os-projector-button> ></os-projector-button>
<os-projector-button <os-projector-button
*ngIf="viewItem" *ngIf="viewListOfSpeakers"
[object]="viewItem.listOfSpeakersSlide" [object]="viewListOfSpeakers"
[menuItem]="true" [menuItem]="true"
text="List of speakers" text="List of speakers"
></os-projector-button> ></os-projector-button>
<os-projector-button <os-projector-button
*ngIf="viewItem" *ngIf="viewListOfSpeakers"
[object]="viewItem.contentObject" [object]="viewListOfSpeakers.contentObject"
[menuItem]="true" [menuItem]="true"
[text]="getContentObjectProjectorButtonText()" [text]="getContentObjectProjectorButtonText()"
></os-projector-button> ></os-projector-button>
<button mat-menu-item *ngIf="closedList" (click)="openSpeakerList()"> <button mat-menu-item *ngIf="isListOfSpeakersClosed" (click)="openSpeakerList()">
<mat-icon>mic</mat-icon> <mat-icon>mic</mat-icon>
<span translate>Open list of speakers</span> <span translate>Open list of speakers</span>
</button> </button>
<button mat-menu-item *ngIf="!closedList" (click)="closeSpeakerList()"> <button mat-menu-item *ngIf="!isListOfSpeakersClosed" (click)="closeSpeakerList()">
<mat-icon>mic_off</mat-icon> <mat-icon>mic_off</mat-icon>
<span translate>Close list of speakers</span> <span translate>Close list of speakers</span>
</button> </button>
<mat-divider *ngIf="!emptyList"></mat-divider> <mat-divider *ngIf="!isListOfSpeakersEmpty"></mat-divider>
<button mat-menu-item (click)="clearSpeakerList()" *ngIf="!emptyList" class="red-warning-text"> <button mat-menu-item (click)="clearSpeakerList()" *ngIf="!isListOfSpeakersEmpty" class="red-warning-text">
<mat-icon>delete</mat-icon> <mat-icon>delete</mat-icon>
<span translate>Remove all speakers</span> <span translate>Remove all speakers</span>
</button> </button>

View File

@ -11,19 +11,17 @@ import { BaseViewComponent } from 'app/site/base/base-view';
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 { ViewSpeaker, SpeakerState } from '../../models/view-speaker';
import { SpeakerRepositoryService } from 'app/core/repositories/agenda/speaker-repository.service';
import { ViewItem } from '../../models/view-item';
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 { ViewUser } from 'app/site/users/models/view-user';
import { UserRepositoryService } from 'app/core/repositories/users/user-repository.service'; import { UserRepositoryService } from 'app/core/repositories/users/user-repository.service';
import { DurationService } from 'app/core/ui-services/duration.service'; import { DurationService } from 'app/core/ui-services/duration.service';
import { CurrentAgendaItemService } from 'app/site/projector/services/current-agenda-item.service';
import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
import { CollectionStringMapperService } from 'app/core/core-services/collection-string-mapper.service'; import { CollectionStringMapperService } from 'app/core/core-services/collection-string-mapper.service';
import { CurrentListOfSpeakersService } from 'app/site/projector/services/current-agenda-item.service';
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 { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable'; import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
import { ListOfSpeakersRepositoryService } from 'app/core/repositories/agenda/list-of-speakers-repository.service';
import { ViewListOfSpeakers } from '../../models/view-list-of-speakers';
/** /**
* The list of speakers for agenda items. * The list of speakers for agenda items.
@ -37,12 +35,12 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
/** /**
* Determine if the user is viewing the current list if speakers * Determine if the user is viewing the current list if speakers
*/ */
public currentListOfSpeakers = false; public isCurrentListOfSpeakers = false;
/** /**
* Holds the view item to the given topic * Holds the view item to the given topic
*/ */
public viewItem: ViewItem; public viewListOfSpeakers: ViewListOfSpeakers;
/** /**
* Holds the speakers * Holds the speakers
@ -80,19 +78,19 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
public addSpeakerForm: FormGroup; public addSpeakerForm: FormGroup;
/** /**
* @returns true if the items' speaker list is currently not open * @returns true if the list of speakers list is currently closed
*/ */
public get closedList(): boolean { public get isListOfSpeakersClosed(): boolean {
return this.viewItem && this.viewItem.item.speaker_list_closed; return this.viewListOfSpeakers && this.viewListOfSpeakers.closed;
} }
public get emptyList(): boolean { public get isListOfSpeakersEmpty(): boolean {
if (this.speakers && this.speakers.length) { if (this.speakers && this.speakers.length) {
return false; return false;
} else if (this.finishedSpeakers && this.finishedSpeakers.length) { } else if (this.finishedSpeakers && this.finishedSpeakers.length) {
return false; return false;
} }
return this.activeSpeaker ? false : true; return !this.activeSpeaker;
} }
/** /**
@ -100,11 +98,10 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
*/ */
private closReferenceProjectorId: number | null; private closReferenceProjectorId: number | null;
private closItemSubscription: Subscription | null; private closSubscription: Subscription | null;
/** /**
* Constructor for speaker list component. Generates the forms and subscribes * Constructor for speaker list component. Generates the forms.
* to the {@link currentListOfSpeakers}
* *
* @param title * @param title
* @param translate * @param translate
@ -112,43 +109,29 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
* @param projectorRepo * @param projectorRepo
* @param route Angulars ActivatedRoute * @param route Angulars ActivatedRoute
* @param DS the DataStore * @param DS the DataStore
* @param repo Repository for speakers * @param listOfSpeakersRepo Repository for list of speakers
* @param itemRepo Repository for agendaItems * @param operator the current operator
* @param op the current operator
* @param promptService * @param promptService
* @param currentAgendaItemService * @param currentListOfSpeakersService
* @param durationService helper for speech duration display * @param durationService helper for speech duration display
*/ */
public constructor( public constructor(
title: Title, title: Title,
protected translate: TranslateService, // protected required for ng-translate-extract protected translate: TranslateService, // protected required for ng-translate-extract
snackBar: MatSnackBar, snackBar: MatSnackBar,
projectorRepo: ProjectorRepositoryService, private projectorRepo: ProjectorRepositoryService,
private route: ActivatedRoute, private route: ActivatedRoute,
private repo: SpeakerRepositoryService, private listOfSpeakersRepo: ListOfSpeakersRepositoryService,
private itemRepo: ItemRepositoryService, private operator: OperatorService,
private op: OperatorService,
private promptService: PromptService, private promptService: PromptService,
private currentAgendaItemService: CurrentAgendaItemService, private currentListOfSpeakersService: CurrentListOfSpeakersService,
private durationService: DurationService, private durationService: DurationService,
private userRepository: UserRepositoryService, private userRepository: UserRepositoryService,
private collectionStringMapper: CollectionStringMapperService, private collectionStringMapper: CollectionStringMapperService,
private currentListOfSpeakersSlideService: CurrentListOfSpeakersSlideService private currentListOfSpeakersSlideService: CurrentListOfSpeakersSlideService
) { ) {
super(title, translate, snackBar); super(title, translate, snackBar);
this.isCurrentListOfSpeakers();
this.addSpeakerForm = new FormGroup({ user_id: new FormControl([]) }); this.addSpeakerForm = new FormGroup({ user_id: new FormControl([]) });
if (this.currentListOfSpeakers) {
this.projectors = projectorRepo.getSortedViewModelList();
this.updateClosProjector();
projectorRepo.getViewModelListObservable().subscribe(newProjectors => {
this.projectors = newProjectors;
this.updateClosProjector();
});
} else {
this.getItemByUrl();
}
} }
/** /**
@ -158,6 +141,24 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
* React to form changes * React to form changes
*/ */
public ngOnInit(): void { public ngOnInit(): void {
// Check, if we are on the current list of speakers.
this.isCurrentListOfSpeakers =
this.route.snapshot.url.length > 0
? this.route.snapshot.url[this.route.snapshot.url.length - 1].path === 'speakers'
: true;
if (this.isCurrentListOfSpeakers) {
this.projectors = this.projectorRepo.getSortedViewModelList();
this.updateClosProjector();
this.projectorRepo.getViewModelListObservable().subscribe(newProjectors => {
this.projectors = newProjectors;
this.updateClosProjector();
});
} else {
const id = +this.route.snapshot.url[this.route.snapshot.url.length - 1].path;
this.setListOfSpeakersId(id);
}
// load and observe users // load and observe users
this.users = this.userRepository.getViewModelListBehaviorSubject(); this.users = this.userRepository.getViewModelListBehaviorSubject();
@ -171,16 +172,7 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
} }
public opCanManage(): boolean { public opCanManage(): boolean {
return this.op.hasPerms('agenda.can_manage_list_of_speakers'); return this.operator.hasPerms('agenda.can_manage_list_of_speakers');
}
/**
* Check the URL to determine a current list of Speakers
*/
private isCurrentListOfSpeakers(): void {
if (this.route.snapshot.url[0]) {
this.currentListOfSpeakers = this.route.snapshot.url[0].path === 'speakers';
}
} }
/** /**
@ -198,14 +190,14 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
if (this.projectorSubscription) { if (this.projectorSubscription) {
this.projectorSubscription.unsubscribe(); this.projectorSubscription.unsubscribe();
this.viewItem = null; this.viewListOfSpeakers = null;
} }
this.projectorSubscription = this.currentAgendaItemService this.projectorSubscription = this.currentListOfSpeakersService
.getAgendaItemObservable(referenceProjector) .getListOfSpeakersObservable(referenceProjector)
.subscribe(item => { .subscribe(listOfSpeakers => {
if (item) { if (listOfSpeakers) {
this.setSpeakerList(item.id); this.setListOfSpeakersId(listOfSpeakers.id);
} }
}); });
} }
@ -218,31 +210,20 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
} }
/** /**
* Extract the ID from the url * Sets the current list of speakers id to show
* Determine whether the speaker list belongs to a motion or a topic
*/
private getItemByUrl(): void {
const id = +this.route.snapshot.url[0];
this.setSpeakerList(id);
}
/**
* Sets the current item as list of speakers
* *
* @param item the item to use as List of Speakers * @param id the list of speakers id
*/ */
private setSpeakerList(id: number): void { private setListOfSpeakersId(id: number): void {
if (this.closItemSubscription) { if (this.closSubscription) {
this.closItemSubscription.unsubscribe(); this.closSubscription.unsubscribe();
} }
this.closItemSubscription = this.itemRepo.getViewModelObservable(id).subscribe(newAgendaItem => { this.closSubscription = this.listOfSpeakersRepo.getViewModelObservable(id).subscribe(listOfSpeakers => {
if (newAgendaItem) { if (listOfSpeakers) {
this.viewItem = newAgendaItem; this.viewListOfSpeakers = listOfSpeakers;
const allSpeakers = this.repo.createSpeakerList(newAgendaItem.item); const allSpeakers = this.viewListOfSpeakers.speakers.sort((a, b) => a.weight - b.weight);
this.speakers = allSpeakers.filter(speaker => speaker.state === SpeakerState.WAITING); this.speakers = allSpeakers.filter(speaker => speaker.state === SpeakerState.WAITING);
// Since the speaker repository is not a normal repository, sorting cannot be handled there
this.speakers.sort((a: ViewSpeaker, b: ViewSpeaker) => a.weight - b.weight);
this.finishedSpeakers = allSpeakers.filter(speaker => speaker.state === SpeakerState.FINISHED); this.finishedSpeakers = allSpeakers.filter(speaker => speaker.state === SpeakerState.FINISHED);
// convert begin time to date and sort // convert begin time to date and sort
@ -259,11 +240,11 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
/** /**
* @returns the verbose name of the model of the content object from viewItem. * @returns the verbose name of the model of the content object from viewItem.
* If a motion is the current content object, "Motion" will be the returned value. * E.g. if a motion is the current content object, "Motion" will be the returned value.
*/ */
public getContentObjectProjectorButtonText(): string { public getContentObjectProjectorButtonText(): string {
const verboseName = this.collectionStringMapper const verboseName = this.collectionStringMapper
.getRepository(this.viewItem.item.content_object.collection) .getRepository(this.viewListOfSpeakers.listOfSpeakers.content_object.collection)
.getVerboseName(); .getVerboseName();
return verboseName; return verboseName;
} }
@ -274,7 +255,9 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
* @param userId the user id to add to the list. No parameter adds the operators user as speaker. * @param userId the user id to add to the list. No parameter adds the operators user as speaker.
*/ */
public addNewSpeaker(userId?: number): void { public addNewSpeaker(userId?: number): void {
this.repo.create(userId, this.viewItem).then(() => this.addSpeakerForm.reset(), this.raiseError); this.listOfSpeakersRepo
.createSpeaker(this.viewListOfSpeakers, userId)
.then(() => this.addSpeakerForm.reset(), this.raiseError);
} }
/** /**
@ -285,32 +268,34 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
public onSortingChange(listInNewOrder: ViewSpeaker[]): void { public onSortingChange(listInNewOrder: ViewSpeaker[]): void {
// extract the ids from the ViewSpeaker array // extract the ids from the ViewSpeaker array
const userIds = listInNewOrder.map(speaker => speaker.id); const userIds = listInNewOrder.map(speaker => speaker.id);
this.repo.sortSpeakers(userIds, this.viewItem.item).then(null, this.raiseError); this.listOfSpeakersRepo.sortSpeakers(this.viewListOfSpeakers, userIds).then(null, this.raiseError);
} }
/** /**
* Click on the mic button to mark a speaker as speaking * Click on the mic button to mark a speaker as speaking
* *
* @param item the speaker marked in the list * @param speaker the speaker marked in the list
*/ */
public onStartButton(item: ViewSpeaker): void { public onStartButton(speaker: ViewSpeaker): void {
this.repo.startSpeaker(item.id, this.viewItem).then(null, this.raiseError); this.listOfSpeakersRepo.startSpeaker(this.viewListOfSpeakers, speaker).then(null, this.raiseError);
} }
/** /**
* Click on the mic-cross button * Click on the mic-cross button
*/ */
public onStopButton(): void { public onStopButton(): void {
this.repo.stopCurrentSpeaker(this.viewItem).then(null, this.raiseError); this.listOfSpeakersRepo.stopCurrentSpeaker(this.viewListOfSpeakers).then(null, this.raiseError);
} }
/** /**
* Click on the star button * Click on the star button. Toggles the marked attribute.
* *
* @param item * @param speaker The speaker clicked on.
*/ */
public onMarkButton(item: ViewSpeaker): void { public onMarkButton(speaker: ViewSpeaker): void {
this.repo.markSpeaker(item.user.id, !item.marked, this.viewItem).then(null, this.raiseError); this.listOfSpeakersRepo
.markSpeaker(this.viewListOfSpeakers, speaker, !speaker.marked)
.then(null, this.raiseError);
} }
/** /**
@ -319,7 +304,9 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
* @param speaker * @param speaker
*/ */
public onDeleteButton(speaker?: ViewSpeaker): void { public onDeleteButton(speaker?: ViewSpeaker): void {
this.repo.delete(this.viewItem, speaker ? speaker.id : null).then(null, this.raiseError); this.listOfSpeakersRepo
.delete(this.viewListOfSpeakers, speaker ? speaker.id : null)
.then(null, this.raiseError);
} }
/** /**
@ -328,7 +315,7 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
* @returns whether or not the current operator is in the list * @returns whether or not the current operator is in the list
*/ */
public isOpInList(): boolean { public isOpInList(): boolean {
return this.speakers.some(speaker => speaker.user.id === this.op.user.id); return this.speakers.some(speaker => speaker.userId === this.operator.user.id);
} }
/** /**
@ -342,20 +329,24 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
} }
/** /**
* Closes the current speaker list * Closes the current list of speakers
*/ */
public closeSpeakerList(): Promise<void> { public closeSpeakerList(): Promise<void> {
if (!this.viewItem.item.speaker_list_closed) { if (!this.viewListOfSpeakers.closed) {
return this.itemRepo.update({ speaker_list_closed: true }, this.viewItem); return this.listOfSpeakersRepo
.update({ closed: true }, this.viewListOfSpeakers)
.then(null, this.raiseError);
} }
} }
/** /**
* Opens the speaker list for the current item * Opens the list of speaker for the current item
*/ */
public openSpeakerList(): Promise<void> { public openSpeakerList(): Promise<void> {
if (this.viewItem.item.speaker_list_closed) { if (this.viewListOfSpeakers.closed) {
return this.itemRepo.update({ speaker_list_closed: false }, this.viewItem); return this.listOfSpeakersRepo
.update({ closed: false }, this.viewListOfSpeakers)
.then(null, this.raiseError);
} }
} }
@ -368,7 +359,7 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
'Are you sure you want to delete all speakers from this list of speakers?' 'Are you sure you want to delete all speakers from this list of speakers?'
); );
if (await this.promptService.open(title, null)) { if (await this.promptService.open(title, null)) {
this.repo.deleteAllSpeakers(this.viewItem); this.listOfSpeakersRepo.deleteAllSpeakers(this.viewListOfSpeakers);
} }
} }

View File

@ -106,10 +106,7 @@
</mat-card> </mat-card>
<mat-menu #topicExtraMenu="matMenu"> <mat-menu #topicExtraMenu="matMenu">
<button mat-menu-item *ngIf="topic" [routerLink]="getSpeakerLink()"> <os-speaker-button [object]="topic" [menuItem]="true"></os-speaker-button>
<mat-icon>mic</mat-icon>
<span translate>List of speakers</span>
</button>
<div *osPerms="'agenda.can_manage'"> <div *osPerms="'agenda.can_manage'">
<mat-divider></mat-divider> <mat-divider></mat-divider>
<button mat-menu-item class="red-warning-text" (click)="onDeleteButton()"> <button mat-menu-item class="red-warning-text" (click)="onDeleteButton()">

View File

@ -8,7 +8,7 @@ import { TranslateService } from '@ngx-translate/core';
import { BaseViewComponent } from 'app/site/base/base-view'; import { BaseViewComponent } from 'app/site/base/base-view';
import { PromptService } from 'app/core/ui-services/prompt.service'; import { PromptService } from 'app/core/ui-services/prompt.service';
import { TopicRepositoryService } from 'app/core/repositories/agenda/topic-repository.service'; import { TopicRepositoryService } from 'app/core/repositories/topics/topic-repository.service';
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';
@ -195,20 +195,6 @@ export class TopicDetailComponent extends BaseViewComponent {
}); });
} }
/**
* Create the absolute path to the corresponding list of speakers
*
* @returns the link to the list of speakers as string
*/
public getSpeakerLink(): string {
if (!this.newTopic && this.topic) {
const item = this.topic.getAgendaItem();
if (item) {
return `/agenda/${item.id}/speakers`;
}
}
}
/** /**
* Handler for the delete button. Uses the PromptService * Handler for the delete button. Uses the PromptService
*/ */

View File

@ -1,6 +1,5 @@
import { CreateTopic } from './create-topic'; import { CreateTopic } from './create-topic';
import { ViewTopic } from './view-topic'; import { ViewTopic } from './view-topic';
import { Topic } from 'app/shared/models/topics/topic';
/** /**
* View model for Topic('Agenda item') creation. * View model for Topic('Agenda item') creation.
@ -8,7 +7,7 @@ import { Topic } from 'app/shared/models/topics/topic';
*/ */
export class ViewCreateTopic extends ViewTopic { export class ViewCreateTopic extends ViewTopic {
public get topic(): CreateTopic { public get topic(): CreateTopic {
return this._topic as CreateTopic; return this._model as CreateTopic;
} }
/** /**
@ -109,10 +108,6 @@ export class ViewCreateTopic extends ViewTopic {
super(topic); super(topic);
} }
public getModel(): Topic {
return super.getModel();
}
public getVerboseName = () => { public getVerboseName = () => {
throw new Error('This should not be used'); throw new Error('This should not be used');
}; };

View File

@ -1,25 +1,23 @@
import { BaseViewModel } from '../../base/base-view-model';
import { Item, itemVisibilityChoices } from 'app/shared/models/agenda/item'; import { Item, itemVisibilityChoices } from 'app/shared/models/agenda/item';
import { Speaker, SpeakerState } from 'app/shared/models/agenda/speaker'; import {
import { BaseAgendaViewModel, isAgendaBaseModel } from 'app/site/base/base-agenda-view-model'; BaseViewModelWithAgendaItem,
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable'; isBaseViewModelWithAgendaItem
} from 'app/site/base/base-view-model-with-agenda-item';
import { BaseViewModelWithContentObject } from 'app/site/base/base-view-model-with-content-object';
import { ContentObject } from 'app/shared/models/base/content-object';
export class ViewItem extends BaseViewModel { export interface ItemTitleInformation {
contentObject: BaseViewModelWithAgendaItem;
contentObjectData: ContentObject;
title_information: object;
}
export class ViewItem extends BaseViewModelWithContentObject<Item, BaseViewModelWithAgendaItem>
implements ItemTitleInformation {
public static COLLECTIONSTRING = Item.COLLECTIONSTRING; public static COLLECTIONSTRING = Item.COLLECTIONSTRING;
private _item: Item;
private _contentObject: BaseAgendaViewModel;
public get item(): Item { public get item(): Item {
return this._item; return this._model;
}
public get contentObject(): BaseAgendaViewModel {
return this._contentObject;
}
public get id(): number {
return this.item.id;
} }
public get itemNumber(): string { public get itemNumber(): string {
@ -34,13 +32,6 @@ export class ViewItem extends BaseViewModel {
return this.item.duration; return this.item.duration;
} }
/**
* Gets the amount of waiting speakers
*/
public get waitingSpeakerAmount(): number {
return this.item.speakers.filter(speaker => speaker.state === SpeakerState.WAITING).length;
}
public get type(): number { public get type(): number {
return this.item.type; return this.item.type;
} }
@ -81,13 +72,6 @@ export class ViewItem extends BaseViewModel {
return type ? type.csvName : ''; return type ? type.csvName : '';
} }
/**
* TODO: make the repository set the ViewSpeakers here.
*/
public get speakers(): Speaker[] {
return this.item.speakers;
}
/** /**
* @returns the weight the server assigns to that item. Mostly useful for sorting within * @returns the weight the server assigns to that item. Mostly useful for sorting within
* it's own hierarchy level (items sharing a parent) * it's own hierarchy level (items sharing a parent)
@ -103,46 +87,7 @@ export class ViewItem extends BaseViewModel {
return this.item.parent_id; return this.item.parent_id;
} }
/** public constructor(item: Item, contentObject?: BaseViewModelWithAgendaItem) {
* This is set by the repository super(Item.COLLECTIONSTRING, item, isBaseViewModelWithAgendaItem, 'BaseViewModelWithAgendaItem', contentObject);
*/
public getVerboseName: () => string;
public getTitle: () => string;
public getListTitle: () => string;
public listOfSpeakersSlide: ProjectorElementBuildDeskriptor = {
getBasicProjectorElement: options => ({
name: 'agenda/list-of-speakers',
id: this.id,
getIdentifiers: () => ['name', 'id']
}),
slideOptions: [],
projectionDefaultName: 'agenda_list_of_speakers',
getDialogTitle: () => this.getTitle()
};
public constructor(item: Item, contentObject: BaseAgendaViewModel) {
super(Item.COLLECTIONSTRING);
this._item = item;
this._contentObject = contentObject;
}
public getModel(): Item {
return this.item;
}
public updateDependencies(update: BaseViewModel): boolean {
if (
update &&
update.collectionString === this.item.content_object.collection &&
update.id === this.item.content_object.id
) {
if (!isAgendaBaseModel(update)) {
throw new Error('The item is not an BaseAgendaViewModel:' + update);
}
this._contentObject = update as BaseAgendaViewModel;
return true;
}
return false;
} }
} }

View File

@ -0,0 +1,92 @@
import { BaseViewModel } from '../../base/base-view-model';
import { Item } from 'app/shared/models/agenda/item';
import { ProjectorElementBuildDeskriptor, Projectable } from 'app/site/base/projectable';
import { ListOfSpeakers } from 'app/shared/models/agenda/list-of-speakers';
import { ViewSpeaker, SpeakerState } from './view-speaker';
import {
BaseViewModelWithListOfSpeakers,
isBaseViewModelWithListOfSpeakers
} from 'app/site/base/base-view-model-with-list-of-speakers';
import { ViewUser } from 'app/site/users/models/view-user';
import { BaseViewModelWithContentObject } from 'app/site/base/base-view-model-with-content-object';
import { ContentObject } from 'app/shared/models/base/content-object';
export interface ListOfSpeakersTitleInformation {
contentObject: BaseViewModelWithListOfSpeakers;
contentObjectData: ContentObject;
title_information: object;
}
export class ViewListOfSpeakers extends BaseViewModelWithContentObject<ListOfSpeakers, BaseViewModelWithListOfSpeakers>
implements ListOfSpeakersTitleInformation, Projectable {
public static COLLECTIONSTRING = ListOfSpeakers.COLLECTIONSTRING;
private _speakers?: ViewSpeaker[];
public get listOfSpeakers(): ListOfSpeakers {
return this._model;
}
public get speakers(): ViewSpeaker[] {
return this._speakers;
}
public get title_information(): object {
return this.listOfSpeakers.title_information;
}
/**
* Gets the amount of waiting speakers
*/
public get waitingSpeakerAmount(): number {
return this.speakers.filter(speaker => speaker.state === SpeakerState.WAITING).length;
}
public get closed(): boolean {
return this.listOfSpeakers.closed;
}
public get listOfSpeakersUrl(): string {
return `/agenda/speakers/${this.id}`;
}
public constructor(
listOfSpeakers: ListOfSpeakers,
speakers: ViewSpeaker[],
contentObject?: BaseViewModelWithListOfSpeakers
) {
super(
Item.COLLECTIONSTRING,
listOfSpeakers,
isBaseViewModelWithListOfSpeakers,
'BaseViewModelWithListOfSpeakers',
contentObject
);
this._speakers = speakers;
}
public getProjectorTitle(): string {
return this.getTitle();
}
public getSlide(): ProjectorElementBuildDeskriptor {
return {
getBasicProjectorElement: options => ({
name: 'agenda/list-of-speakers',
id: this.id,
getIdentifiers: () => ['name', 'id']
}),
slideOptions: [],
projectionDefaultName: 'agenda_list_of_speakers',
getDialogTitle: () => this.getTitle()
};
}
public updateDependencies(update: BaseViewModel): boolean {
const updated = super.updateDependencies(update);
if (!updated && update instanceof ViewUser) {
return this.speakers.map(speaker => speaker.updateDependencies(update)).some(x => x);
}
return updated;
}
}

View File

@ -1,14 +1,24 @@
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 } from 'app/shared/models/agenda/speaker';
import { ViewUser } from 'app/site/users/models/view-user'; import { ViewUser } from 'app/site/users/models/view-user';
import { User } from 'app/shared/models/users/user'; import { Updateable } from 'app/site/base/updateable';
import { Identifiable } from 'app/shared/models/base/identifiable';
/**
* Determine the state of the speaker
*/
export enum SpeakerState {
WAITING,
CURRENT,
FINISHED
}
/** /**
* 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 { export class ViewSpeaker implements Updateable, Identifiable {
private _speaker: Speaker; private _speaker: Speaker;
private _user: ViewUser | null; private _user?: ViewUser;
public get speaker(): Speaker { public get speaker(): Speaker {
return this._speaker; return this._speaker;
@ -22,6 +32,10 @@ export class ViewSpeaker extends BaseViewModel {
return this.speaker.id; return this.speaker.id;
} }
public get userId(): number {
return this.speaker.user_id;
}
public get weight(): number { public get weight(): number {
return this.speaker.weight; return this.speaker.weight;
} }
@ -44,8 +58,20 @@ export class ViewSpeaker extends BaseViewModel {
return this.speaker.end_time; return this.speaker.end_time;
} }
/**
* @returns
* - waiting if there is no begin nor end time
* - current if there is a begin time and not end time
* - finished if there are both begin and end time
*/
public get state(): SpeakerState { public get state(): SpeakerState {
return this.speaker.state; if (!this.begin_time && !this.end_time) {
return SpeakerState.WAITING;
} else if (this.begin_time && !this.end_time) {
return SpeakerState.CURRENT;
} else {
return SpeakerState.FINISHED;
}
} }
public get name(): string { public get name(): string {
@ -56,13 +82,7 @@ export class ViewSpeaker extends BaseViewModel {
return this.user ? this.user.gender : ''; return this.user ? this.user.gender : '';
} }
/**
* This is set by the repository
*/
public getVerboseName;
public constructor(speaker: Speaker, user?: ViewUser) { public constructor(speaker: Speaker, user?: ViewUser) {
super('TODO');
this._speaker = speaker; this._speaker = speaker;
this._user = user; this._user = user;
} }
@ -71,13 +91,11 @@ export class ViewSpeaker extends BaseViewModel {
return this.name; return this.name;
}; };
public getModel(): User { public updateDependencies(update: BaseViewModel): boolean {
return this.user.user; if (update instanceof ViewUser && update.id === this.speaker.user_id) {
this._user = update;
return true;
}
return false;
} }
/**
* Speaker is not a base model,
* @param update the incoming update
*/
public updateDependencies(update: BaseViewModel): void {}
} }

View File

@ -1,40 +1,33 @@
import { Topic } from 'app/shared/models/topics/topic'; import { Topic } from 'app/shared/models/topics/topic';
import { BaseAgendaViewModel } from 'app/site/base/base-agenda-view-model';
import { SearchRepresentation } from 'app/core/ui-services/search.service'; import { SearchRepresentation } from 'app/core/ui-services/search.service';
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable'; import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile'; import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
import { ViewItem } from './view-item'; import { ViewItem } from './view-item';
import { BaseViewModel } from 'app/site/base/base-view-model'; import { BaseViewModel } from 'app/site/base/base-view-model';
import { ViewListOfSpeakers } from './view-list-of-speakers';
import { BaseViewModelWithAgendaItemAndListOfSpeakers } from 'app/site/base/base-view-model-with-agenda-item-and-list-of-speakers';
import { TitleInformationWithAgendaItem } from 'app/site/base/base-view-model-with-agenda-item';
export interface TopicTitleInformation extends TitleInformationWithAgendaItem {
title: string;
agenda_item_number?: string;
}
/** /**
* 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 BaseAgendaViewModel { export class ViewTopic extends BaseViewModelWithAgendaItemAndListOfSpeakers implements TopicTitleInformation {
public static COLLECTIONSTRING = Topic.COLLECTIONSTRING; public static COLLECTIONSTRING = Topic.COLLECTIONSTRING;
protected _topic: Topic; private _attachments?: ViewMediafile[];
private _attachments: ViewMediafile[];
private _agendaItem: ViewItem;
public get topic(): Topic { public get topic(): Topic {
return this._topic; return this._model;
} }
public get attachments(): ViewMediafile[] { public get attachments(): ViewMediafile[] {
return this._attachments; return this._attachments || [];
}
public get agendaItem(): ViewItem {
return this._agendaItem;
}
public get id(): number {
return this.topic.id;
}
public get agenda_item_id(): number {
return this.topic.agenda_item_id;
} }
public get attachments_id(): number[] { public get attachments_id(): number[] {
@ -49,34 +42,14 @@ export class ViewTopic extends BaseAgendaViewModel {
return this.topic.text; return this.topic.text;
} }
/** public constructor(
* This is set by the repository topic: Topic,
*/ attachments?: ViewMediafile[],
public getVerboseName; item?: ViewItem,
public getAgendaTitle; listOfSpeakers?: ViewListOfSpeakers
public getAgendaTitleWithType; ) {
super(Topic.COLLECTIONSTRING, topic, item, listOfSpeakers);
public constructor(topic: Topic, attachments?: ViewMediafile[], item?: ViewItem) {
super(Topic.COLLECTIONSTRING);
this._topic = topic;
this._attachments = attachments; this._attachments = attachments;
this._agendaItem = item;
}
public getTitle = () => {
if (this.agendaItem && this.agendaItem.itemNumber) {
return this.agendaItem.itemNumber + ' · ' + this.title;
} else {
return this.title;
}
};
public getModel(): Topic {
return this.topic;
}
public getAgendaItem(): ViewItem {
return this.agendaItem;
} }
/** /**
@ -118,6 +91,7 @@ export class ViewTopic extends BaseAgendaViewModel {
} }
public updateDependencies(update: BaseViewModel): void { public updateDependencies(update: BaseViewModel): void {
super.updateDependencies(update);
if (update instanceof ViewMediafile && this.attachments_id.includes(update.id)) { if (update instanceof ViewMediafile && 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);
if (attachmentIndex < 0) { if (attachmentIndex < 0) {
@ -126,8 +100,5 @@ export class ViewTopic extends BaseAgendaViewModel {
this.attachments[attachmentIndex] = update; this.attachments[attachmentIndex] = update;
} }
} }
if (update instanceof ViewItem && this.agenda_item_id === update.id) {
this._agendaItem = update;
}
} }
} }

View File

@ -28,12 +28,7 @@
<span translate>PDF</span> <span translate>PDF</span>
</button> </button>
<!-- List of speakers --> <!-- List of speakers -->
<div *ngIf="assignment.agendaItem"> <os-speaker-button [object]="assignment" [menuItem]="true"></os-speaker-button>
<button mat-menu-item [routerLink]="getSpeakerLink()" *osPerms="'agenda.can_see'">
<mat-icon>mic</mat-icon>
<span translate>List of speakers</span>
</button>
</div>
</div> </div>
<!-- Project --> <!-- Project -->
<os-projector-button [object]="assignment" [menuItem]="true"></os-projector-button> <os-projector-button [object]="assignment" [menuItem]="true"></os-projector-button>
@ -62,7 +57,9 @@
<ng-template #metaInfoTemplate> <ng-template #metaInfoTemplate>
<mat-card class="os-card " *ngIf="assignment"> <mat-card class="os-card " *ngIf="assignment">
<h1>{{ assignment.getTitle() }}</h1> <div *ngIf="!editAssignment && assignment.getTitle">
<h1>{{ assignment.getTitle() }}</h1>
</div>
<div *ngIf="assignment"> <div *ngIf="assignment">
<div *ngIf="assignment.assignment.description" [innerHTML]="assignment.assignment.description"></div> <div *ngIf="assignment.assignment.description" [innerHTML]="assignment.assignment.description"></div>
</div> </div>
@ -227,7 +224,7 @@
matInput matInput
placeholder="{{ 'Title' | translate }}" placeholder="{{ 'Title' | translate }}"
formControlName="title" formControlName="title"
[value]="assignmentCopy.getTitle() || ''" [value]="assignmentCopy.title || ''"
/> />
</mat-form-field> </mat-form-field>
</div> </div>

View File

@ -501,11 +501,4 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
.sortCandidates(listInNewOrder.map(relatedUser => relatedUser.id), this.assignment) .sortCandidates(listInNewOrder.map(relatedUser => relatedUser.id), this.assignment)
.then(null, this.raiseError); .then(null, this.raiseError);
} }
/**
* Gets the link to the list of speakers associated with the assignment
*/
public getSpeakerLink(): string {
return `/agenda/${this.assignment.agendaItem.id}/speakers`;
}
} }

View File

@ -95,6 +95,10 @@ export class ViewAssignmentPoll implements Identifiable, Updateable, Projectable
return this.getTitle(); return this.getTitle();
} }
public getProjectorTitle(): string {
return this.getTitle();
}
/** /**
* Creates a copy with deep-copy on all changing numerical values, * Creates a copy with deep-copy on all changing numerical values,
* but intact uncopied references to the users * but intact uncopied references to the users

View File

@ -1,5 +1,4 @@
import { Assignment } from 'app/shared/models/assignments/assignment'; import { Assignment } from 'app/shared/models/assignments/assignment';
import { BaseAgendaViewModel } from 'app/site/base/base-agenda-view-model';
import { SearchRepresentation } from 'app/core/ui-services/search.service'; import { SearchRepresentation } from 'app/core/ui-services/search.service';
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable'; import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
import { ViewUser } from 'app/site/users/models/view-user'; import { ViewUser } from 'app/site/users/models/view-user';
@ -9,6 +8,13 @@ import { BaseViewModel } from 'app/site/base/base-view-model';
import { ViewAssignmentRelatedUser } from './view-assignment-related-user'; import { ViewAssignmentRelatedUser } from './view-assignment-related-user';
import { ViewAssignmentPoll } from './view-assignment-poll'; import { ViewAssignmentPoll } from './view-assignment-poll';
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile'; import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
import { BaseViewModelWithAgendaItemAndListOfSpeakers } from 'app/site/base/base-view-model-with-agenda-item-and-list-of-speakers';
import { ViewListOfSpeakers } from 'app/site/agenda/models/view-list-of-speakers';
import { TitleInformationWithAgendaItem } from 'app/site/base/base-view-model-with-agenda-item';
export interface AssignmentTitleInformation extends TitleInformationWithAgendaItem {
title: string;
}
/** /**
* A constant containing all possible assignment phases and their different * A constant containing all possible assignment phases and their different
@ -33,22 +39,17 @@ export const AssignmentPhases: { name: string; value: number; display_name: stri
} }
]; ];
export class ViewAssignment extends BaseAgendaViewModel { export class ViewAssignment extends BaseViewModelWithAgendaItemAndListOfSpeakers<Assignment>
implements AssignmentTitleInformation {
public static COLLECTIONSTRING = Assignment.COLLECTIONSTRING; public static COLLECTIONSTRING = Assignment.COLLECTIONSTRING;
private _assignment: Assignment;
private _assignmentRelatedUsers: ViewAssignmentRelatedUser[]; private _assignmentRelatedUsers: ViewAssignmentRelatedUser[];
private _assignmentPolls: ViewAssignmentPoll[]; private _assignmentPolls: ViewAssignmentPoll[];
private _agendaItem?: ViewItem;
private _tags?: ViewTag[]; private _tags?: ViewTag[];
private _attachments?: ViewMediafile[]; private _attachments?: ViewMediafile[];
public get id(): number {
return this._assignment ? this._assignment.id : null;
}
public get assignment(): Assignment { public get assignment(): Assignment {
return this._assignment; return this._model;
} }
public get polls(): ViewAssignmentPoll[] { public get polls(): ViewAssignmentPoll[] {
@ -75,10 +76,6 @@ export class ViewAssignment extends BaseAgendaViewModel {
return this._assignmentRelatedUsers; return this._assignmentRelatedUsers;
} }
public get agendaItem(): ViewItem | null {
return this._agendaItem;
}
public get tags(): ViewTag[] { public get tags(): ViewTag[] {
return this._tags || []; return this._tags || [];
} }
@ -123,35 +120,26 @@ export class ViewAssignment extends BaseAgendaViewModel {
return this._assignmentRelatedUsers ? this._assignmentRelatedUsers.length : 0; return this._assignmentRelatedUsers ? this._assignmentRelatedUsers.length : 0;
} }
/**
* Constructor. Is set by the repository
*/
public getVerboseName;
public getAgendaTitle;
public getAgendaTitleWithType;
public constructor( public constructor(
assignment: Assignment, assignment: Assignment,
assignmentRelatedUsers: ViewAssignmentRelatedUser[], assignmentRelatedUsers: ViewAssignmentRelatedUser[],
assignmentPolls: ViewAssignmentPoll[], assignmentPolls: ViewAssignmentPoll[],
agendaItem?: ViewItem, item?: ViewItem,
listOfSpeakers?: ViewListOfSpeakers,
tags?: ViewTag[], tags?: ViewTag[],
attachments?: ViewMediafile[] attachments?: ViewMediafile[]
) { ) {
super(Assignment.COLLECTIONSTRING); super(Assignment.COLLECTIONSTRING, assignment, item, listOfSpeakers);
this._assignment = assignment;
this._assignmentRelatedUsers = assignmentRelatedUsers; this._assignmentRelatedUsers = assignmentRelatedUsers;
this._assignmentPolls = assignmentPolls; this._assignmentPolls = assignmentPolls;
this._agendaItem = agendaItem;
this._tags = tags; this._tags = tags;
this._attachments = attachments; this._attachments = attachments;
} }
public updateDependencies(update: BaseViewModel): void { public updateDependencies(update: BaseViewModel): void {
if (update instanceof ViewItem && update.id === this.assignment.agenda_item_id) { super.updateDependencies(update);
this._agendaItem = update; if (update instanceof ViewTag && this.assignment.tags_id.includes(update.id)) {
} else if (update instanceof ViewTag && this.assignment.tags_id.includes(update.id)) {
const tagIndex = this._tags.findIndex(_tag => _tag.id === update.id); const tagIndex = this._tags.findIndex(_tag => _tag.id === update.id);
if (tagIndex < 0) { if (tagIndex < 0) {
this._tags.push(update); this._tags.push(update);
@ -171,18 +159,6 @@ export class ViewAssignment extends BaseAgendaViewModel {
} }
} }
public getAgendaItem(): ViewItem {
return this.agendaItem;
}
public getTitle = () => {
return this.title;
};
public getModel(): Assignment {
return this.assignment;
}
public formatForSearch(): SearchRepresentation { public formatForSearch(): SearchRepresentation {
return [this.title]; return [this.title];
} }

View File

@ -1,27 +0,0 @@
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.
*/
export interface AgendaInformation extends DetailNavigable {
/**
* Should return the title for the agenda list view.
*/
getAgendaTitle: () => string;
/**
* Should return the title for the list of speakers view.
*/
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,55 +0,0 @@
import { AgendaInformation } from 'app/site/base/agenda-information';
import { BaseProjectableViewModel } from './base-projectable-view-model';
import { SearchRepresentation } from 'app/core/ui-services/search.service';
import { ViewItem } from '../agenda/models/view-item';
export function isAgendaBaseModel(obj: object): obj is BaseAgendaViewModel {
const agendaViewModel = <BaseAgendaViewModel>obj;
return (
agendaViewModel.getAgendaTitle !== undefined &&
agendaViewModel.getAgendaTitleWithType !== undefined &&
agendaViewModel.getCSVExportText !== undefined &&
agendaViewModel.getAgendaItem !== undefined &&
agendaViewModel.getDetailStateURL !== undefined
);
}
/**
* Base view class for projectable models.
*/
export abstract class BaseAgendaViewModel extends BaseProjectableViewModel implements AgendaInformation {
/**
* @returns the contained agenda item
*/
public abstract getAgendaItem(): ViewItem;
/**
* @returns the agenda title
*/
public getAgendaTitle = () => {
return this.getTitle();
};
/**
* @return the agenda title with the verbose name of the content object
*/
public getAgendaTitleWithType = () => {
// Return the agenda title with the model's verbose name appended
return this.getAgendaTitle() + ' (' + this.getVerboseName() + ')';
};
/**
* @returns the (optional) descriptive text to be exported in the CSV.
* May be overridden by inheriting classes
*/
public getCSVExportText(): string {
return '';
}
public abstract getDetailStateURL(): string;
/**
* Should return a string representation of the object, so there can be searched for.
*/
public abstract formatForSearch(): SearchRepresentation;
}

View File

@ -1,11 +1,13 @@
import { Projectable, ProjectorElementBuildDeskriptor } from './projectable'; import { Projectable, ProjectorElementBuildDeskriptor } from './projectable';
import { BaseViewModel } from './base-view-model'; import { BaseViewModel } from './base-view-model';
import { ConfigService } from 'app/core/ui-services/config.service'; import { ConfigService } from 'app/core/ui-services/config.service';
import { BaseModel } from 'app/shared/models/base/base-model';
/** /**
* Base view class for projectable models. * Base view class for projectable models.
*/ */
export abstract class BaseProjectableViewModel extends BaseViewModel implements Projectable { export abstract class BaseProjectableViewModel<M extends BaseModel = any> extends BaseViewModel<M>
implements Projectable {
public abstract getSlide(configService?: ConfigService): ProjectorElementBuildDeskriptor; public abstract getSlide(configService?: ConfigService): ProjectorElementBuildDeskriptor;
/** /**

View File

@ -0,0 +1,82 @@
import { SearchRepresentation } from 'app/core/ui-services/search.service';
import { BaseModelWithAgendaItemAndListOfSpeakers } from 'app/shared/models/base/base-model-with-agenda-item-and-list-of-speakers';
import { ViewItem } from '../agenda/models/view-item';
import { ViewListOfSpeakers } from '../agenda/models/view-list-of-speakers';
import { BaseProjectableViewModel } from './base-projectable-view-model';
import { isBaseViewModelWithAgendaItem, IBaseViewModelWithAgendaItem } from './base-view-model-with-agenda-item';
import {
isBaseViewModelWithListOfSpeakers,
IBaseViewModelWithListOfSpeakers
} from './base-view-model-with-list-of-speakers';
import { BaseViewModel } from './base-view-model';
export function isBaseViewModelWithAgendaItemAndListOfSpeakers(
obj: any
): obj is BaseViewModelWithAgendaItemAndListOfSpeakers {
return !!obj && isBaseViewModelWithAgendaItem(obj) && isBaseViewModelWithListOfSpeakers(obj);
}
/**
* Base view class for view models with an agenda item and a list of speakers associated.
*/
export abstract class BaseViewModelWithAgendaItemAndListOfSpeakers<
M extends BaseModelWithAgendaItemAndListOfSpeakers = any
> extends BaseProjectableViewModel implements IBaseViewModelWithAgendaItem<M>, IBaseViewModelWithListOfSpeakers<M> {
protected _item?: ViewItem;
protected _listOfSpeakers?: ViewListOfSpeakers;
public get agendaItem(): ViewItem | null {
return this._item;
}
public get agenda_item_id(): number {
return this._model.agenda_item_id;
}
public get agenda_item_number(): string | null {
return this.agendaItem && this.agendaItem.itemNumber ? this.agendaItem.itemNumber : null;
}
public get listOfSpeakers(): ViewListOfSpeakers | null {
return this._listOfSpeakers;
}
public get list_of_speakers_id(): number {
return this._model.list_of_speakers_id;
}
public getAgendaSlideTitle: () => string;
public getAgendaListTitle: () => string;
public getListOfSpeakersTitle: () => string;
public getListOfSpeakersSlideTitle: () => string;
public constructor(collectionString: string, model: M, item?: ViewItem, listOfSpeakers?: ViewListOfSpeakers) {
super(collectionString, model);
// Explicit set to null instead of undefined, if not given
this._item = item || null;
this._listOfSpeakers = listOfSpeakers || null;
}
/**
* @returns the (optional) descriptive text to be exported in the CSV.
* May be overridden by inheriting classes
*/
public getCSVExportText(): string {
return '';
}
public abstract getDetailStateURL(): string;
/**
* Should return a string representation of the object, so there can be searched for.
*/
public abstract formatForSearch(): SearchRepresentation;
public updateDependencies(update: BaseViewModel): void {
if (update instanceof ViewItem && update.id === this.agenda_item_id) {
this._item = update;
} else if (update instanceof ViewListOfSpeakers && update.id === this.list_of_speakers_id) {
this._listOfSpeakers = update;
}
}
}

View File

@ -0,0 +1,114 @@
import { BaseProjectableViewModel } from './base-projectable-view-model';
import { SearchRepresentation } from 'app/core/ui-services/search.service';
import { ViewItem } from '../agenda/models/view-item';
import { isDetailNavigable, DetailNavigable } from 'app/shared/models/base/detail-navigable';
import { isSearchable, Searchable } from './searchable';
import { BaseModelWithAgendaItem } from 'app/shared/models/base/base-model-with-agenda-item';
import { BaseViewModel, TitleInformation } from './base-view-model';
import { Item } from 'app/shared/models/agenda/item';
export function isBaseViewModelWithAgendaItem(obj: any): obj is BaseViewModelWithAgendaItem {
const model = <BaseViewModelWithAgendaItem>obj;
return (
!!obj &&
isDetailNavigable(model) &&
isSearchable(model) &&
model.getAgendaSlideTitle !== undefined &&
model.getAgendaListTitle !== undefined &&
model.getCSVExportText !== undefined &&
model.agendaItem !== undefined &&
model.agenda_item_id !== undefined
);
}
export interface TitleInformationWithAgendaItem extends TitleInformation {
agenda_item_number?: string;
}
/**
* Describes a base class for view models.
*/
export interface IBaseViewModelWithAgendaItem<M extends BaseModelWithAgendaItem = any>
extends BaseProjectableViewModel<M>,
DetailNavigable,
Searchable {
agendaItem: ViewItem | null;
agenda_item_id: number;
agenda_item_number: string | null;
/**
* @returns the agenda title
*/
getAgendaSlideTitle: () => string;
/**
* @return the agenda title with the verbose name of the content object
*/
getAgendaListTitle: () => string;
/**
* @returns the (optional) descriptive text to be exported in the CSV.
* May be overridden by inheriting classes
*/
getCSVExportText(): string;
}
/**
* Base view model class for view models with an agenda item.
*/
export abstract class BaseViewModelWithAgendaItem<M extends BaseModelWithAgendaItem = any>
extends BaseProjectableViewModel<M>
implements IBaseViewModelWithAgendaItem<M> {
protected _item?: ViewItem;
public get agendaItem(): ViewItem | null {
return this._item;
}
public get agenda_item_id(): number {
return this._model.agenda_item_id;
}
public get agenda_item_number(): string | null {
return this.agendaItem && this.agendaItem.itemNumber ? this.agendaItem.itemNumber : null;
}
/**
* @returns the agenda title for the item slides
*/
public getAgendaSlideTitle: () => string;
/**
* @return the agenda title for the list view
*/
public getAgendaListTitle: () => string;
public constructor(collecitonString: string, model: M, item?: ViewItem) {
super(collecitonString, model);
this._item = item || null; // Explicit set to null instead of undefined, if not given
}
/**
* @returns the (optional) descriptive text to be exported in the CSV.
* May be overridden by inheriting classes
*/
public getCSVExportText(): string {
return '';
}
public abstract getDetailStateURL(): string;
/**
* Should return a string representation of the object, so there can be searched for.
*/
public abstract formatForSearch(): SearchRepresentation;
public updateDependencies(update: BaseViewModel): void {
// We cannot check with instanceof, because this gives circular dependency issues...
if (update.collectionString === Item.COLLECTIONSTRING && update.id === this.agenda_item_id) {
this._item = update as ViewItem;
}
}
}

View File

@ -0,0 +1,63 @@
import { BaseViewModel } from './base-view-model';
import { BaseModelWithContentObject } from 'app/shared/models/base/base-model-with-content-object';
import { ContentObject } from 'app/shared/models/base/content-object';
/**
* Base class for view models with content objects. Ensures a content object attribute and
* implements the generic logic for `updateDependencies`.
*
* Type M is the contained model
* Type C is the type of every content object.
*/
export abstract class BaseViewModelWithContentObject<
M extends BaseModelWithContentObject = any,
C extends BaseViewModel = any
> extends BaseViewModel<M> {
protected _contentObject?: C;
public get contentObjectData(): ContentObject {
return this.getModel().content_object;
}
public get contentObject(): C | null {
return this._contentObject;
}
/**
* @param collectionString The collection string of this model
* @param model the model this view model captures
* @param isC A function ensuring that an arbitrary object is a valid content object
* @param CVerbose is the verbose name of the base content object class, for debugging purposes
* @param contentObject (optional) The content object, if it is known during creation.
*/
public constructor(
collectionString: string,
model: M,
private isC: (obj: any) => obj is C,
private CVerbose: string,
contentObject?: C
) {
super(collectionString, model);
this._contentObject = contentObject;
}
/**
* Check, if the given model mathces the content object definition. If so, the function
* returns true, else false.
*/
public updateDependencies(update: BaseViewModel): boolean {
if (
update &&
update.collectionString === this.contentObjectData.collection &&
update.id === this.contentObjectData.id
) {
if (this.isC(update)) {
this._contentObject = update;
return true;
} else {
throw new Error(`The object is not an ${this.CVerbose}:` + update);
}
}
return false;
}
}

View File

@ -0,0 +1,66 @@
import { BaseProjectableViewModel } from './base-projectable-view-model';
import { isDetailNavigable, DetailNavigable } from 'app/shared/models/base/detail-navigable';
import { ViewListOfSpeakers } from '../agenda/models/view-list-of-speakers';
import { BaseModelWithListOfSpeakers } from 'app/shared/models/base/base-model-with-list-of-speakers';
import { BaseViewModel } from './base-view-model';
import { ListOfSpeakers } from 'app/shared/models/agenda/list-of-speakers';
export function isBaseViewModelWithListOfSpeakers(obj: any): obj is BaseViewModelWithListOfSpeakers {
const model = <BaseViewModelWithListOfSpeakers>obj;
return (
!!obj &&
isDetailNavigable(model) &&
model.getListOfSpeakersTitle !== undefined &&
model.listOfSpeakers !== undefined &&
model.list_of_speakers_id !== undefined
);
}
/**
* Describes a base view model with a list of speakers.
*/
export interface IBaseViewModelWithListOfSpeakers<M extends BaseModelWithListOfSpeakers = any>
extends BaseProjectableViewModel<M>,
DetailNavigable {
listOfSpeakers: ViewListOfSpeakers | null;
list_of_speakers_id: number;
getListOfSpeakersTitle: () => string;
getListOfSpeakersSlideTitle: () => string;
}
/**
* Base view model class for models with a list of speakers.
*/
export abstract class BaseViewModelWithListOfSpeakers<M extends BaseModelWithListOfSpeakers = any>
extends BaseProjectableViewModel<M>
implements IBaseViewModelWithListOfSpeakers<M> {
protected _listOfSpeakers?: ViewListOfSpeakers;
public get listOfSpeakers(): ViewListOfSpeakers | null {
return this._listOfSpeakers;
}
public get list_of_speakers_id(): number {
return this._model.list_of_speakers_id;
}
public getListOfSpeakersTitle: () => string;
public getListOfSpeakersSlideTitle: () => string;
public constructor(collectionString: string, model: M, listOfSpeakers?: ViewListOfSpeakers) {
super(collectionString, model);
this._listOfSpeakers = listOfSpeakers || null; // Explicit set to null instead of undefined, if not given
}
public abstract getDetailStateURL(): string;
public updateDependencies(update: BaseViewModel): void {
// We cannot check with instanceof, becuase this givec circular dependency issues...
if (update.collectionString === ListOfSpeakers.COLLECTIONSTRING && update.id === this.list_of_speakers_id) {
this._listOfSpeakers = update as ViewListOfSpeakers;
}
}
}

View File

@ -4,6 +4,8 @@ import { Collection } from 'app/shared/models/base/collection';
import { BaseModel } from 'app/shared/models/base/base-model'; import { BaseModel } from 'app/shared/models/base/base-model';
import { Updateable } from './updateable'; import { Updateable } from './updateable';
export type TitleInformation = object;
export interface ViewModelConstructor<T extends BaseViewModel> { export interface ViewModelConstructor<T extends BaseViewModel> {
COLLECTIONSTRING: string; COLLECTIONSTRING: string;
new (...args: any[]): T; new (...args: any[]): T;
@ -12,11 +14,13 @@ export interface ViewModelConstructor<T extends BaseViewModel> {
/** /**
* Base class for view models. alls view models should have titles. * Base class for view models. alls view models should have titles.
*/ */
export abstract class BaseViewModel implements Displayable, Identifiable, Collection, Updateable { export abstract class BaseViewModel<M extends BaseModel = any>
/** implements Displayable, Identifiable, Collection, Updateable {
* Force children to have an id. protected _model: M;
*/
public abstract id: number; public get id(): number {
return this._model.id;
}
/** /**
* force children of BaseModel to have a collectionString. * force children of BaseModel to have a collectionString.
@ -34,7 +38,8 @@ export abstract class BaseViewModel implements Displayable, Identifiable, Collec
return this._collectionString; return this._collectionString;
} }
public abstract getTitle: () => string; public getTitle: () => string;
public getListTitle: () => string;
/** /**
* Returns the verbose name. * Returns the verbose name.
@ -42,24 +47,23 @@ export abstract class BaseViewModel implements Displayable, Identifiable, Collec
* @param plural If the name should be plural * @param plural If the name should be plural
* @returns the verbose name of the model * @returns the verbose name of the model
*/ */
public abstract getVerboseName: (plural?: boolean) => string; public getVerboseName: (plural?: boolean) => string;
/** /**
* TODO: Remove verboseName, this must be overwritten by repos..
*
* @param verboseName
* @param collectionString * @param collectionString
* @param model
*/ */
public constructor(collectionString: string) { public constructor(collectionString: string, model: M) {
this._collectionString = collectionString; this._collectionString = collectionString;
this._model = model;
} }
public getListTitle: () => string = () => { /**
return this.getTitle(); * @returns the main underlying model of the view model
}; */
public getModel(): M {
/** return the main model of a view model */ return this._model;
public abstract getModel(): BaseModel; }
public abstract updateDependencies(update: BaseViewModel): void; public abstract updateDependencies(update: BaseViewModel): void;

View File

@ -1,22 +1,23 @@
import { MatTableDataSource, MatTable, MatSort, MatPaginator, MatSnackBar, PageEvent } from '@angular/material'; import { MatTableDataSource, MatTable, MatSort, MatPaginator, MatSnackBar, PageEvent } from '@angular/material';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { ViewChild, Type, OnDestroy } from '@angular/core'; import { ViewChild, Type, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs';
import { BaseViewComponent } from './base-view'; import { BaseViewComponent } from './base-view';
import { BaseViewModel } from './base-view-model'; import { BaseViewModel, TitleInformation } from './base-view-model';
import { BaseSortListService } from 'app/core/ui-services/base-sort-list.service'; import { BaseSortListService } from 'app/core/ui-services/base-sort-list.service';
import { BaseFilterListService } from 'app/core/ui-services/base-filter-list.service'; import { BaseFilterListService } from 'app/core/ui-services/base-filter-list.service';
import { BaseModel } from 'app/shared/models/base/base-model'; import { BaseModel } from 'app/shared/models/base/base-model';
import { StorageService } from 'app/core/core-services/storage.service'; import { StorageService } from 'app/core/core-services/storage.service';
import { BaseRepository } from 'app/core/repositories/base-repository'; import { BaseRepository } from 'app/core/repositories/base-repository';
import { Observable } from 'rxjs';
export abstract class ListViewBaseComponent< export abstract class ListViewBaseComponent<
V extends BaseViewModel, V extends BaseViewModel,
M extends BaseModel, M extends BaseModel,
R extends BaseRepository<V, M> R extends BaseRepository<V, M, TitleInformation>
> extends BaseViewComponent implements OnDestroy { > extends BaseViewComponent implements OnDestroy {
/** /**
* The data source for a table. Requires to be initialized with a BaseViewModel * The data source for a table. Requires to be initialized with a BaseViewModel

View File

@ -25,16 +25,14 @@ export interface ProjectorElementBuildDeskriptor {
} }
export function isProjectable(obj: any): obj is Projectable { export function isProjectable(obj: any): obj is Projectable {
if (obj) { return !!obj && obj.getSlide !== undefined && obj.getProjectorTitle !== undefined;
return (<Projectable>obj).getSlide !== undefined;
} else {
return false;
}
} }
/** /**
* Interface for every model, that should be projectable. * Interface for every model, that should be projectable.
*/ */
export interface Projectable extends Displayable { export interface Projectable extends Displayable {
getProjectorTitle: () => string;
getSlide(configSerice?: ConfigService): ProjectorElementBuildDeskriptor; getSlide(configSerice?: ConfigService): ProjectorElementBuildDeskriptor;
} }

View File

@ -32,17 +32,16 @@ interface ConfigConstant {
choices?: ConfigChoice[]; choices?: ConfigChoice[];
} }
export interface ConfigTitleInformation {
key: string;
}
/** /**
* The view model for configs. * The view model for configs.
*/ */
export class ViewConfig extends BaseViewModel { export class ViewConfig extends BaseViewModel<Config> implements ConfigTitleInformation {
public static COLLECTIONSTRING = Config.COLLECTIONSTRING; public static COLLECTIONSTRING = Config.COLLECTIONSTRING;
/**
* The underlying config.
*/
private _config: Config;
/* This private members are set by setConstantsInfo. */ /* This private members are set by setConstantsInfo. */
private _helpText: string; private _helpText: string;
private _inputType: ConfigInputType; private _inputType: ConfigInputType;
@ -60,11 +59,7 @@ export class ViewConfig extends BaseViewModel {
} }
public get config(): Config { public get config(): Config {
return this._config; return this._model;
}
public get id(): number {
return this.config.id;
} }
public get key(): string { public get key(): string {
@ -95,26 +90,12 @@ export class ViewConfig extends BaseViewModel {
return this._defaultValue; return this._defaultValue;
} }
/**
* This is set by the repository
*/
public getVerboseName;
public constructor(config: Config) { public constructor(config: Config) {
super(Config.COLLECTIONSTRING); super(Config.COLLECTIONSTRING, config);
this._config = config;
} }
public getTitle = () => {
return this.label;
};
public updateDependencies(update: BaseViewModel): void {} public updateDependencies(update: BaseViewModel): void {}
public getModel(): Config {
return this.config;
}
/** /**
* 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.
* A little debounce time for all inputs is given here and is usefull, if inputs sends multiple onChange-events, * A little debounce time for all inputs is given here and is usefull, if inputs sends multiple onChange-events,

View File

@ -4,22 +4,21 @@ import { ViewUser } from 'app/site/users/models/view-user';
export type ProxyHistory = History & { user?: ViewUser }; export type ProxyHistory = History & { user?: ViewUser };
export interface HistoryTitleInformation {
element_id: string;
}
/** /**
* View model for history objects * View model for history objects
*/ */
export class ViewHistory extends BaseViewModel { export class ViewHistory extends BaseViewModel<ProxyHistory> implements HistoryTitleInformation {
public static COLLECTIONSTRING = History.COLLECTIONSTRING; public static COLLECTIONSTRING = History.COLLECTIONSTRING;
/**
* Private BaseModel of the history
*/
private _history: ProxyHistory;
/** /**
* Read the history property * Read the history property
*/ */
public get history(): ProxyHistory { public get history(): ProxyHistory {
return this._history; return this._model;
} }
/** /**
@ -73,11 +72,6 @@ export class ViewHistory extends BaseViewModel {
return this.history.now; return this.history.now;
} }
/**
* This is set by the repository
*/
public getVerboseName;
/** /**
* Construction of a ViewHistory * Construction of a ViewHistory
* *
@ -85,8 +79,7 @@ export class ViewHistory extends BaseViewModel {
* @param user the real user BaseModel * @param user the real user BaseModel
*/ */
public constructor(history: ProxyHistory) { public constructor(history: ProxyHistory) {
super(History.COLLECTIONSTRING); super(History.COLLECTIONSTRING, history);
this._history = history;
} }
/** /**
@ -105,19 +98,5 @@ export class ViewHistory extends BaseViewModel {
return +this.element_id.split(':')[1]; return +this.element_id.split(':')[1];
} }
/**
* Get the history objects title
* Required by BaseViewModel
*
* @returns history.getTitle which returns the element_id
*/
public getTitle = () => {
return this.element_id;
};
public getModel(): History {
return this.history;
}
public updateDependencies(update: BaseViewModel): void {} public updateDependencies(update: BaseViewModel): void {}
} }

View File

@ -169,6 +169,8 @@
</div> </div>
</div> </div>
<os-speaker-button [object]="file" [menuItem]="true"></os-speaker-button>
<!-- Edit and delete for all images --> <!-- Edit and delete for all images -->
<mat-divider></mat-divider> <mat-divider></mat-divider>
<button mat-menu-item (click)="onEditFile(file)"> <button mat-menu-item (click)="onEditFile(file)">

View File

@ -3,27 +3,28 @@ import { Mediafile } from 'app/shared/models/mediafiles/mediafile';
import { Searchable } from 'app/site/base/searchable'; import { Searchable } from 'app/site/base/searchable';
import { SearchRepresentation } from 'app/core/ui-services/search.service'; import { SearchRepresentation } from 'app/core/ui-services/search.service';
import { ViewUser } from 'app/site/users/models/view-user'; import { ViewUser } from 'app/site/users/models/view-user';
import { BaseProjectableViewModel } from 'app/site/base/base-projectable-view-model';
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable'; import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
import { BaseViewModelWithListOfSpeakers } from 'app/site/base/base-view-model-with-list-of-speakers';
import { ViewListOfSpeakers } from 'app/site/agenda/models/view-list-of-speakers';
export class ViewMediafile extends BaseProjectableViewModel implements Searchable { export interface MediafileTitleInformation {
title: string;
}
export class ViewMediafile extends BaseViewModelWithListOfSpeakers<Mediafile>
implements MediafileTitleInformation, Searchable {
public static COLLECTIONSTRING = Mediafile.COLLECTIONSTRING; public static COLLECTIONSTRING = Mediafile.COLLECTIONSTRING;
private _mediafile: Mediafile;
private _uploader: ViewUser; private _uploader: ViewUser;
public get mediafile(): Mediafile { public get mediafile(): Mediafile {
return this._mediafile; return this._model;
} }
public get uploader(): ViewUser { public get uploader(): ViewUser {
return this._uploader; return this._uploader;
} }
public get id(): number {
return this.mediafile.id;
}
public get uploader_id(): number { public get uploader_id(): number {
return this.mediafile.uploader_id; return this.mediafile.uploader_id;
} }
@ -65,25 +66,11 @@ export class ViewMediafile extends BaseProjectableViewModel implements Searchabl
return this.mediafile.hidden; return this.mediafile.hidden;
} }
/** public constructor(mediafile: Mediafile, listOfSpeakers?: ViewListOfSpeakers, uploader?: ViewUser) {
* This is set by the repository super(Mediafile.COLLECTIONSTRING, mediafile, listOfSpeakers);
*/
public getVerboseName;
public constructor(mediafile: Mediafile, uploader?: ViewUser) {
super(Mediafile.COLLECTIONSTRING);
this._mediafile = mediafile;
this._uploader = uploader; this._uploader = uploader;
} }
public getTitle = () => {
return this.title;
};
public getModel(): Mediafile {
return this.mediafile;
}
public formatForSearch(): SearchRepresentation { public formatForSearch(): SearchRepresentation {
const searchValues = [this.title]; const searchValues = [this.title];
if (this.uploader) { if (this.uploader) {
@ -167,6 +154,7 @@ export class ViewMediafile extends BaseProjectableViewModel implements Searchabl
} }
public updateDependencies(update: BaseViewModel): void { public updateDependencies(update: BaseViewModel): void {
super.updateDependencies(update);
if (update instanceof ViewUser && this.uploader_id === update.id) { if (update instanceof ViewUser && this.uploader_id === update.id) {
this._uploader = update; this._uploader = update;
} }

View File

@ -3,6 +3,11 @@ import { BaseViewModel } from '../../base/base-view-model';
import { SearchRepresentation } from 'app/core/ui-services/search.service'; import { SearchRepresentation } from 'app/core/ui-services/search.service';
import { Searchable } from 'app/site/base/searchable'; import { Searchable } from 'app/site/base/searchable';
export interface CategoryTitleInformation {
prefix: string;
name: string;
}
/** /**
* Category class for the View * Category class for the View
* *
@ -10,17 +15,11 @@ import { Searchable } from 'app/site/base/searchable';
* Provides "safe" access to variables and functions in {@link Category} * Provides "safe" access to variables and functions in {@link Category}
* @ignore * @ignore
*/ */
export class ViewCategory extends BaseViewModel implements Searchable { export class ViewCategory extends BaseViewModel<Category> implements CategoryTitleInformation, Searchable {
public static COLLECTIONSTRING = Category.COLLECTIONSTRING; public static COLLECTIONSTRING = Category.COLLECTIONSTRING;
private _category: Category;
public get category(): Category { public get category(): Category {
return this._category; return this._model;
}
public get id(): number {
return this.category.id;
} }
public get name(): string { public get name(): string {
@ -31,32 +30,28 @@ export class ViewCategory extends BaseViewModel implements Searchable {
return this.category.prefix; return this.category.prefix;
} }
/**
* TODO: Where is this used? Try to avoid this.
*/
public set prefix(prefix: string) { public set prefix(prefix: string) {
this._category.prefix = prefix; this._model.prefix = prefix;
} }
/**
* TODO: Where is this used? Try to avoid this.
*/
public set name(name: string) { public set name(name: string) {
this._category.name = name; this._model.name = name;
} }
public get prefixedName(): string { public get prefixedName(): string {
return this.prefix ? this.prefix + ' - ' + this.name : this.name; return this.prefix ? this.prefix + ' - ' + this.name : this.name;
} }
/**
* This is set by the repository
*/
public getVerboseName;
public constructor(category: Category) { public constructor(category: Category) {
super(Category.COLLECTIONSTRING); super(Category.COLLECTIONSTRING, category);
this._category = category;
} }
public getTitle = () => {
return this.prefixedName;
};
public formatForSearch(): SearchRepresentation { public formatForSearch(): SearchRepresentation {
return [this.name, this.prefix]; return [this.name, this.prefix];
} }
@ -65,10 +60,6 @@ export class ViewCategory extends BaseViewModel implements Searchable {
return '/motions/category'; return '/motions/category';
} }
public getModel(): Category {
return this.category;
}
/** /**
* Updates the local objects if required * Updates the local objects if required
* @param update * @param update

View File

@ -6,6 +6,11 @@ import { ViewMotionBlock } from './view-motion-block';
import { ViewItem } from 'app/site/agenda/models/view-item'; import { ViewItem } from 'app/site/agenda/models/view-item';
import { ViewCategory } from './view-category'; import { ViewCategory } from './view-category';
import { ViewWorkflow } from './view-workflow'; import { ViewWorkflow } from './view-workflow';
import { ViewListOfSpeakers } from 'app/site/agenda/models/view-list-of-speakers';
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
import { ViewTag } from 'app/site/tags/models/view-tag';
import { ViewMotionChangeRecommendation } from './view-motion-change-recommendation';
import { PersonalNoteContent } from 'app/shared/models/users/personal-note';
/** /**
* Create motion class for the View. Its different to ViewMotion in fact that the submitter handling is different * Create motion class for the View. Its different to ViewMotion in fact that the submitter handling is different
@ -14,10 +19,10 @@ import { ViewWorkflow } from './view-workflow';
* @ignore * @ignore
*/ */
export class ViewCreateMotion extends ViewMotion { export class ViewCreateMotion extends ViewMotion {
protected _motion: CreateMotion; protected _model: CreateMotion;
public get motion(): CreateMotion { public get motion(): CreateMotion {
return this._motion; return this._model;
} }
public get submitters(): ViewUser[] { public get submitters(): ViewUser[] {
@ -30,20 +35,43 @@ export class ViewCreateMotion extends ViewMotion {
public set submitters(users: ViewUser[]) { public set submitters(users: ViewUser[]) {
this._submitters = users; this._submitters = users;
this._motion.submitters_id = users.map(user => user.id); this._model.submitters_id = users.map(user => user.id);
} }
public constructor( public constructor(
motion?: CreateMotion, motion: CreateMotion,
category?: ViewCategory, category?: ViewCategory,
submitters?: ViewUser[], submitters?: ViewUser[],
supporters?: ViewUser[], supporters?: ViewUser[],
workflow?: ViewWorkflow, workflow?: ViewWorkflow,
state?: WorkflowState, state?: WorkflowState,
item?: ViewItem, item?: ViewItem,
block?: ViewMotionBlock listOfSpeakers?: ViewListOfSpeakers,
block?: ViewMotionBlock,
attachments?: ViewMediafile[],
tags?: ViewTag[],
parent?: ViewMotion,
changeRecommendations?: ViewMotionChangeRecommendation[],
amendments?: ViewMotion[],
personalNote?: PersonalNoteContent
) { ) {
super(motion, category, submitters, supporters, workflow, state, item, block, null); super(
motion,
category,
submitters,
supporters,
workflow,
state,
item,
listOfSpeakers,
block,
attachments,
tags,
parent,
changeRecommendations,
amendments,
personalNote
);
} }
public getVerboseName = () => { public getVerboseName = () => {
@ -55,7 +83,7 @@ export class ViewCreateMotion extends ViewMotion {
*/ */
public copy(): ViewCreateMotion { public copy(): ViewCreateMotion {
return new ViewCreateMotion( return new ViewCreateMotion(
this._motion, this._model,
this._category, this._category,
this._submitters, this._submitters,
this._supporters, this._supporters,

View File

@ -1,52 +1,34 @@
import { MotionBlock } from 'app/shared/models/motions/motion-block'; import { MotionBlock } from 'app/shared/models/motions/motion-block';
import { BaseAgendaViewModel } from 'app/site/base/base-agenda-view-model';
import { SearchRepresentation } from 'app/core/ui-services/search.service'; import { SearchRepresentation } from 'app/core/ui-services/search.service';
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable'; import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
import { Searchable } from 'app/site/base/searchable'; import { Searchable } from 'app/site/base/searchable';
import { ViewItem } from 'app/site/agenda/models/view-item'; import { ViewItem } from 'app/site/agenda/models/view-item';
import { BaseViewModel } from 'app/site/base/base-view-model'; import { BaseViewModelWithAgendaItemAndListOfSpeakers } from 'app/site/base/base-view-model-with-agenda-item-and-list-of-speakers';
import { ViewListOfSpeakers } from 'app/site/agenda/models/view-list-of-speakers';
import { TitleInformationWithAgendaItem } from 'app/site/base/base-view-model-with-agenda-item';
export interface MotionBlockTitleInformation extends TitleInformationWithAgendaItem {
title: string;
}
/** /**
* ViewModel for motion blocks. * ViewModel for motion blocks.
* @ignore * @ignore
*/ */
export class ViewMotionBlock extends BaseAgendaViewModel implements Searchable { export class ViewMotionBlock extends BaseViewModelWithAgendaItemAndListOfSpeakers
implements MotionBlockTitleInformation, Searchable {
public static COLLECTIONSTRING = MotionBlock.COLLECTIONSTRING; public static COLLECTIONSTRING = MotionBlock.COLLECTIONSTRING;
private _motionBlock: MotionBlock;
private _agendaItem: ViewItem;
public get motionBlock(): MotionBlock { public get motionBlock(): MotionBlock {
return this._motionBlock; return this._model;
}
public get agendaItem(): ViewItem {
return this._agendaItem;
}
public get id(): number {
return this.motionBlock.id;
} }
public get title(): string { public get title(): string {
return this.motionBlock.title; return this.motionBlock.title;
} }
public get agenda_item_id(): number { public constructor(motionBlock: MotionBlock, agendaItem?: ViewItem, listOfSpeakers?: ViewListOfSpeakers) {
return this.motionBlock.agenda_item_id; super(MotionBlock.COLLECTIONSTRING, motionBlock, agendaItem, listOfSpeakers);
}
/**
* This is set by the repository
*/
public getVerboseName;
public getAgendaTitle;
public getAgendaTitleWithType;
public constructor(motionBlock: MotionBlock, agendaItem?: ViewItem) {
super(MotionBlock.COLLECTIONSTRING);
this._motionBlock = motionBlock;
this._agendaItem = agendaItem;
} }
/** /**
@ -58,10 +40,6 @@ export class ViewMotionBlock extends BaseAgendaViewModel implements Searchable {
return [this.title]; return [this.title];
} }
public getAgendaItem(): ViewItem {
return this.agendaItem;
}
/** /**
* Get the URL to the motion block * Get the URL to the motion block
* *
@ -71,20 +49,6 @@ export class ViewMotionBlock extends BaseAgendaViewModel implements Searchable {
return `/motions/blocks/${this.id}`; return `/motions/blocks/${this.id}`;
} }
public updateDependencies(update: BaseViewModel): void {
if (update instanceof ViewItem && this.agenda_item_id === update.id) {
this._agendaItem = update;
}
}
public getTitle = () => {
return this.title;
};
public getModel(): MotionBlock {
return this.motionBlock;
}
public getSlide(): ProjectorElementBuildDeskriptor { public getSlide(): ProjectorElementBuildDeskriptor {
return { return {
getBasicProjectorElement: options => ({ getBasicProjectorElement: options => ({

View File

@ -3,6 +3,8 @@ import { ModificationType } from 'app/core/ui-services/diff.service';
import { MotionChangeRecommendation } from 'app/shared/models/motions/motion-change-reco'; import { MotionChangeRecommendation } from 'app/shared/models/motions/motion-change-reco';
import { ViewUnifiedChange, ViewUnifiedChangeType } from '../../../shared/models/motions/view-unified-change'; import { ViewUnifiedChange, ViewUnifiedChangeType } from '../../../shared/models/motions/view-unified-change';
export type MotionChangeRecommendationTitleInformation = object;
/** /**
* Change recommendation class for the View * Change recommendation class for the View
* *
@ -10,76 +12,57 @@ import { ViewUnifiedChange, ViewUnifiedChangeType } from '../../../shared/models
* Provides "safe" access to variables and functions in {@link MotionChangeRecommendation} * Provides "safe" access to variables and functions in {@link MotionChangeRecommendation}
* @ignore * @ignore
*/ */
export class ViewMotionChangeRecommendation extends BaseViewModel implements ViewUnifiedChange { export class ViewMotionChangeRecommendation extends BaseViewModel<MotionChangeRecommendation>
implements MotionChangeRecommendationTitleInformation, ViewUnifiedChange {
public static COLLECTIONSTRING = MotionChangeRecommendation.COLLECTIONSTRING; public static COLLECTIONSTRING = MotionChangeRecommendation.COLLECTIONSTRING;
private _changeRecommendation: MotionChangeRecommendation;
public get id(): number {
return this._changeRecommendation.id;
}
public get changeRecommendation(): MotionChangeRecommendation { public get changeRecommendation(): MotionChangeRecommendation {
return this._changeRecommendation; return this._model;
} }
/** public constructor(motionChangeRecommendation: MotionChangeRecommendation) {
* This is set by the repository super(MotionChangeRecommendation.COLLECTIONSTRING, motionChangeRecommendation);
*/
public getVerboseName;
public constructor(changeReco: MotionChangeRecommendation) {
super(MotionChangeRecommendation.COLLECTIONSTRING);
this._changeRecommendation = changeReco;
} }
public getTitle = () => {
return 'Change recommendation';
};
public updateDependencies(update: BaseViewModel): void {} public updateDependencies(update: BaseViewModel): void {}
public getModel(): MotionChangeRecommendation {
return this.changeRecommendation;
}
public updateChangeReco(type: number, text: string, internal: boolean): void { public updateChangeReco(type: number, text: string, internal: boolean): void {
// @TODO HTML sanitazion // @TODO HTML sanitazion
this._changeRecommendation.type = type; this.changeRecommendation.type = type;
this._changeRecommendation.text = text; this.changeRecommendation.text = text;
this._changeRecommendation.internal = internal; this.changeRecommendation.internal = internal;
} }
public get rejected(): boolean { public get rejected(): boolean {
return this._changeRecommendation.rejected; return this.changeRecommendation.rejected;
} }
public get internal(): boolean { public get internal(): boolean {
return this._changeRecommendation.internal; return this.changeRecommendation.internal;
} }
public get type(): number { public get type(): number {
return this._changeRecommendation.type || ModificationType.TYPE_REPLACEMENT; return this.changeRecommendation.type || ModificationType.TYPE_REPLACEMENT;
} }
public get other_description(): string { public get other_description(): string {
return this._changeRecommendation.other_description; return this.changeRecommendation.other_description;
} }
public get line_from(): number { public get line_from(): number {
return this._changeRecommendation.line_from; return this.changeRecommendation.line_from;
} }
public get line_to(): number { public get line_to(): number {
return this._changeRecommendation.line_to; return this.changeRecommendation.line_to;
} }
public get text(): string { public get text(): string {
return this._changeRecommendation.text; return this.changeRecommendation.text;
} }
public get motion_id(): number { public get motion_id(): number {
return this._changeRecommendation.motion_id; return this.changeRecommendation.motion_id;
} }
public getChangeId(): string { public getChangeId(): string {

View File

@ -2,6 +2,10 @@ import { BaseViewModel } from '../../base/base-view-model';
import { MotionCommentSection } from 'app/shared/models/motions/motion-comment-section'; import { MotionCommentSection } from 'app/shared/models/motions/motion-comment-section';
import { ViewGroup } from 'app/site/users/models/view-group'; import { ViewGroup } from 'app/site/users/models/view-group';
export interface MotionCommentSectionTitleInformation {
name: string;
}
/** /**
* Motion comment section class for the View * Motion comment section class for the View
* *
@ -9,16 +13,15 @@ import { ViewGroup } from 'app/site/users/models/view-group';
* Provides "safe" access to variables and functions in {@link MotionCommentSection} * Provides "safe" access to variables and functions in {@link MotionCommentSection}
* @ignore * @ignore
*/ */
export class ViewMotionCommentSection extends BaseViewModel { export class ViewMotionCommentSection extends BaseViewModel<MotionCommentSection>
implements MotionCommentSectionTitleInformation {
public static COLLECTIONSTRING = MotionCommentSection.COLLECTIONSTRING; public static COLLECTIONSTRING = MotionCommentSection.COLLECTIONSTRING;
private _section: MotionCommentSection;
private _readGroups: ViewGroup[]; private _readGroups: ViewGroup[];
private _writeGroups: ViewGroup[]; private _writeGroups: ViewGroup[];
public get section(): MotionCommentSection { public get section(): MotionCommentSection {
return this._section; return this._model;
} }
public get id(): number { public get id(): number {
@ -45,30 +48,19 @@ export class ViewMotionCommentSection extends BaseViewModel {
return this._writeGroups; return this._writeGroups;
} }
/**
* TODO: Where is this needed? Try to avoid this.
*/
public set name(name: string) { public set name(name: string) {
this._section.name = name; this._model.name = name;
} }
/** public constructor(motionCommentSection: MotionCommentSection, readGroups: ViewGroup[], writeGroups: ViewGroup[]) {
* This is set by the repository super(MotionCommentSection.COLLECTIONSTRING, motionCommentSection);
*/
public getVerboseName;
public constructor(section: MotionCommentSection, readGroups: ViewGroup[], writeGroups: ViewGroup[]) {
super(MotionCommentSection.COLLECTIONSTRING);
this._section = section;
this._readGroups = readGroups; this._readGroups = readGroups;
this._writeGroups = writeGroups; this._writeGroups = writeGroups;
} }
public getTitle = () => {
return this.name;
};
public getModel(): MotionCommentSection {
return this.section;
}
/** /**
* Updates the local objects if required * Updates the local objects if required
* @param section * @param section

View File

@ -4,7 +4,6 @@ import { ViewMotionCommentSection } from './view-motion-comment-section';
import { WorkflowState } from 'app/shared/models/motions/workflow-state'; import { WorkflowState } from 'app/shared/models/motions/workflow-state';
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable'; import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
import { SearchRepresentation } from 'app/core/ui-services/search.service'; import { SearchRepresentation } from 'app/core/ui-services/search.service';
import { BaseAgendaViewModel } from 'app/site/base/base-agenda-view-model';
import { Searchable } from 'app/site/base/searchable'; import { Searchable } from 'app/site/base/searchable';
import { ViewUser } from 'app/site/users/models/view-user'; import { ViewUser } from 'app/site/users/models/view-user';
import { ViewTag } from 'app/site/tags/models/view-tag'; import { ViewTag } from 'app/site/tags/models/view-tag';
@ -15,9 +14,12 @@ import { ViewCategory } from './view-category';
import { ViewMotionBlock } from './view-motion-block'; import { ViewMotionBlock } from './view-motion-block';
import { BaseViewModel } from 'app/site/base/base-view-model'; import { BaseViewModel } from 'app/site/base/base-view-model';
import { ConfigService } from 'app/core/ui-services/config.service'; import { ConfigService } from 'app/core/ui-services/config.service';
import { ViewMotionChangeRecommendation } from './view-change-recommendation';
import { ViewPersonalNote } from 'app/site/users/models/view-personal-note'; import { ViewPersonalNote } from 'app/site/users/models/view-personal-note';
import { ViewMotionChangeRecommendation } from './view-motion-change-recommendation';
import { _ } from 'app/core/translate/translation-marker'; import { _ } from 'app/core/translate/translation-marker';
import { BaseViewModelWithAgendaItemAndListOfSpeakers } from 'app/site/base/base-view-model-with-agenda-item-and-list-of-speakers';
import { ViewListOfSpeakers } from 'app/site/agenda/models/view-list-of-speakers';
import { TitleInformationWithAgendaItem } from 'app/site/base/base-view-model-with-agenda-item';
/** /**
* The line numbering mode for the motion detail view. * The line numbering mode for the motion detail view.
@ -40,6 +42,11 @@ export enum ChangeRecoMode {
ModifiedFinal = 'modified_final_version' ModifiedFinal = 'modified_final_version'
} }
export interface MotionTitleInformation extends TitleInformationWithAgendaItem {
title: string;
identifier?: string;
}
/** /**
* Motion class for the View * Motion class for the View
* *
@ -47,30 +54,65 @@ export enum ChangeRecoMode {
* Provides "safe" access to variables and functions in {@link Motion} * Provides "safe" access to variables and functions in {@link Motion}
* @ignore * @ignore
*/ */
export class ViewMotion extends BaseAgendaViewModel implements Searchable { export class ViewMotion extends BaseViewModelWithAgendaItemAndListOfSpeakers<Motion>
implements MotionTitleInformation, Searchable {
public static COLLECTIONSTRING = Motion.COLLECTIONSTRING; public static COLLECTIONSTRING = Motion.COLLECTIONSTRING;
protected _motion: Motion; protected _category?: ViewCategory;
protected _category: ViewCategory; protected _submitters?: ViewUser[];
protected _submitters: ViewUser[]; protected _supporters?: ViewUser[];
protected _supporters: ViewUser[]; protected _workflow?: ViewWorkflow;
protected _workflow: ViewWorkflow; protected _state?: WorkflowState;
protected _state: WorkflowState; protected _block?: ViewMotionBlock;
protected _item: ViewItem; protected _attachments?: ViewMediafile[];
protected _block: ViewMotionBlock; protected _tags?: ViewTag[];
protected _attachments: ViewMediafile[]; protected _parent?: ViewMotion;
protected _tags: ViewTag[]; protected _amendments?: ViewMotion[];
protected _parent: ViewMotion; protected _changeRecommendations?: ViewMotionChangeRecommendation[];
protected _amendments: ViewMotion[];
protected _changeRecommendations: ViewMotionChangeRecommendation[];
public personalNote?: PersonalNoteContent; public personalNote?: PersonalNoteContent;
public get motion(): Motion { public get motion(): Motion {
return this._motion; return this._model;
} }
public get id(): number { public get category(): ViewCategory | null {
return this.motion.id; return this._category;
}
public get submitters(): ViewUser[] {
return this._submitters || [];
}
public get supporters(): ViewUser[] {
return this._supporters || [];
}
/**
* TODO: Where is this needed. Try to avoid this..
*/
public set supporters(users: ViewUser[]) {
this._supporters = users;
this._model.supporters_id = users.map(user => user.id);
}
public get motion_block(): ViewMotionBlock | null {
return this._block;
}
public get attachments(): ViewMediafile[] {
return this._attachments || [];
}
public get tags(): ViewTag[] {
return this._tags || [];
}
public get parent(): ViewMotion | null {
return this._parent;
}
public get amendments(): ViewMotion[] {
return this._amendments || [];
} }
public get identifier(): string { public get identifier(): string {
@ -111,43 +153,22 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
return this.motion.sort_parent_id; return this.motion.sort_parent_id;
} }
public get agenda_item_id(): number {
return this.motion.agenda_item_id;
}
public get category_id(): number { public get category_id(): number {
return this.motion.category_id; return this.motion.category_id;
} }
public get category(): ViewCategory {
return this._category;
}
public get category_weight(): number { public get category_weight(): number {
return this.motion.category_weight; return this.motion.category_weight;
} }
public get submitters(): ViewUser[] {
return this._submitters;
}
public get sorted_submitters_id(): number[] { public get sorted_submitters_id(): number[] {
return this.motion.sorted_submitters_id; return this.motion.sorted_submitters_id;
} }
public get supporters(): ViewUser[] {
return this._supporters;
}
public get supporters_id(): number[] { public get supporters_id(): number[] {
return this.motion.supporters_id; return this.motion.supporters_id;
} }
public set supporters(users: ViewUser[]) {
this._supporters = users;
this._motion.supporters_id = users.map(user => user.id);
}
public get workflow(): ViewWorkflow { public get workflow(): ViewWorkflow {
return this._workflow; return this._workflow;
} }
@ -207,24 +228,16 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
return this.state && this.workflow ? this.state.getPreviousStates(this.workflow.workflow) : []; return this.state && this.workflow ? this.state.getPreviousStates(this.workflow.workflow) : [];
} }
public get item(): ViewItem {
return this._item;
}
public get agenda_type(): number { public get agenda_type(): number {
return this.item ? this.item.type : null; return this.agendaItem ? this.agendaItem.type : null;
} }
public get motion_block_id(): number { public get motion_block_id(): number {
return this.motion.motion_block_id; return this.motion.motion_block_id;
} }
public get motion_block(): ViewMotionBlock { public get speakerAmount(): number {
return this._block; return this.listOfSpeakers ? this.listOfSpeakers.waitingSpeakerAmount : null;
}
public get agendaSpeakerAmount(): number {
return this.item ? this.item.waitingSpeakerAmount : null;
} }
public get parent_id(): number { public get parent_id(): number {
@ -243,22 +256,6 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
return this.motion.attachments_id; return this.motion.attachments_id;
} }
public get attachments(): ViewMediafile[] {
return this._attachments;
}
public get tags(): ViewTag[] {
return this._tags;
}
public get parent(): ViewMotion {
return this._parent;
}
public get amendments(): ViewMotion[] {
return this._amendments;
}
/** /**
* @returns the creation date as Date object * @returns the creation date as Date object
*/ */
@ -347,31 +344,9 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
return StateCssClassMapping[this.state.css_class] || ''; return StateCssClassMapping[this.state.css_class] || '';
} }
/** // This is set by the repository
* This is set by the repository
*/
public getTitle: () => string;
/**
* This is set by the repository
*/
public getIdentifierOrTitle: () => string; public getIdentifierOrTitle: () => string;
/**
* This is set by the repository
*/
public getAgendaTitle: () => string;
/**
* This is set by the repository
*/
public getAgendaTitleWithType: () => string;
/**
* This is set by the repository
*/
public getVerboseName: () => string;
public constructor( public constructor(
motion: Motion, motion: Motion,
category?: ViewCategory, category?: ViewCategory,
@ -380,6 +355,7 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
workflow?: ViewWorkflow, workflow?: ViewWorkflow,
state?: WorkflowState, state?: WorkflowState,
item?: ViewItem, item?: ViewItem,
listOfSpeakers?: ViewListOfSpeakers,
block?: ViewMotionBlock, block?: ViewMotionBlock,
attachments?: ViewMediafile[], attachments?: ViewMediafile[],
tags?: ViewTag[], tags?: ViewTag[],
@ -388,14 +364,12 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
amendments?: ViewMotion[], amendments?: ViewMotion[],
personalNote?: PersonalNoteContent personalNote?: PersonalNoteContent
) { ) {
super(Motion.COLLECTIONSTRING); super(Motion.COLLECTIONSTRING, motion, item, listOfSpeakers);
this._motion = motion;
this._category = category; this._category = category;
this._submitters = submitters; this._submitters = submitters;
this._supporters = supporters; this._supporters = supporters;
this._workflow = workflow; this._workflow = workflow;
this._state = state; this._state = state;
this._item = item;
this._block = block; this._block = block;
this._attachments = attachments; this._attachments = attachments;
this._tags = tags; this._tags = tags;
@ -405,14 +379,6 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
this.personalNote = personalNote; this.personalNote = personalNote;
} }
public getAgendaItem(): ViewItem {
return this.item;
}
public getModel(): Motion {
return this.motion;
}
/** /**
* Formats the category for search * Formats the category for search
* *
@ -461,12 +427,11 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
* @param update * @param update
*/ */
public updateDependencies(update: BaseViewModel): void { public updateDependencies(update: BaseViewModel): void {
super.updateDependencies(update);
if (update instanceof ViewWorkflow) { if (update instanceof ViewWorkflow) {
this.updateWorkflow(update); this.updateWorkflow(update);
} else if (update instanceof ViewCategory) { } else if (update instanceof ViewCategory) {
this.updateCategory(update); this.updateCategory(update);
} else if (update instanceof ViewItem) {
this.updateItem(update);
} else if (update instanceof ViewMotionBlock) { } else if (update instanceof ViewMotionBlock) {
this.updateMotionBlock(update); this.updateMotionBlock(update);
} else if (update instanceof ViewUser) { } else if (update instanceof ViewUser) {
@ -507,17 +472,6 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
} }
} }
/**
* Update routine for the agenda Item
*
* @param item potentially the changed agenda Item. Needs manual verification
*/
private updateItem(item: ViewItem): void {
if (item.id === this.motion.agenda_item_id) {
this._item = item;
}
}
/** /**
* Update routine for the motion block * Update routine for the motion block
* *
@ -532,7 +486,7 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
/** /**
* Update routine for supporters and submitters * Update routine for supporters and submitters
* *
* @param update potentially the changed agenda Item. Needs manual verification * @param update potentially the changed user. Needs manual verification
*/ */
private updateUser(update: ViewUser): void { private updateUser(update: ViewUser): void {
if (this.motion.submitters && this.motion.submitters.find(user => user.user_id === update.id)) { if (this.motion.submitters && this.motion.submitters.find(user => user.user_id === update.id)) {
@ -665,7 +619,7 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
}), }),
slideOptions: slideOptions, slideOptions: slideOptions,
projectionDefaultName: 'motions', projectionDefaultName: 'motions',
getDialogTitle: this.getAgendaTitle getDialogTitle: this.getAgendaSlideTitle
}; };
} }
@ -674,13 +628,14 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
*/ */
public copy(): ViewMotion { public copy(): ViewMotion {
return new ViewMotion( return new ViewMotion(
this._motion, this._model,
this._category, this._category,
this._submitters, this._submitters,
this._supporters, this._supporters,
this._workflow, this._workflow,
this._state, this._state,
this._item, this._item,
this._listOfSpeakers,
this._block, this._block,
this._attachments, this._attachments,
this._tags, this._tags,

View File

@ -3,6 +3,10 @@ import { StatuteParagraph } from 'app/shared/models/motions/statute-paragraph';
import { Searchable } from 'app/site/base/searchable'; import { Searchable } from 'app/site/base/searchable';
import { SearchRepresentation } from 'app/core/ui-services/search.service'; import { SearchRepresentation } from 'app/core/ui-services/search.service';
export interface StatuteParagraphTitleInformation {
title: string;
}
/** /**
* State paragrpah class for the View * State paragrpah class for the View
* *
@ -10,17 +14,12 @@ import { SearchRepresentation } from 'app/core/ui-services/search.service';
* Provides "safe" access to variables and functions in {@link StatuteParagraph} * Provides "safe" access to variables and functions in {@link StatuteParagraph}
* @ignore * @ignore
*/ */
export class ViewStatuteParagraph extends BaseViewModel implements Searchable { export class ViewStatuteParagraph extends BaseViewModel<StatuteParagraph>
implements StatuteParagraphTitleInformation, Searchable {
public static COLLECTIONSTRING = StatuteParagraph.COLLECTIONSTRING; public static COLLECTIONSTRING = StatuteParagraph.COLLECTIONSTRING;
private _paragraph: StatuteParagraph;
public get statuteParagraph(): StatuteParagraph { public get statuteParagraph(): StatuteParagraph {
return this._paragraph; return this._model;
}
public get id(): number {
return this.statuteParagraph.id;
} }
public get title(): string { public get title(): string {
@ -35,22 +34,8 @@ export class ViewStatuteParagraph extends BaseViewModel implements Searchable {
return this.statuteParagraph.weight; return this.statuteParagraph.weight;
} }
/** public constructor(statuteParagraph: StatuteParagraph) {
* This is set by the repository super(StatuteParagraph.COLLECTIONSTRING, statuteParagraph);
*/
public getVerboseName;
public constructor(paragraph: StatuteParagraph) {
super(StatuteParagraph.COLLECTIONSTRING);
this._paragraph = paragraph;
}
public getTitle = () => {
return this.title;
};
public getModel(): StatuteParagraph {
return this.statuteParagraph;
} }
public formatForSearch(): SearchRepresentation { public formatForSearch(): SearchRepresentation {

View File

@ -10,21 +10,19 @@ export const StateCssClassMapping = {
warning: 'yellow' warning: 'yellow'
}; };
export interface WorkflowTitleInformation {
name: string;
}
/** /**
* class for the ViewWorkflow. * class for the ViewWorkflow.
* @ignore * @ignore
*/ */
export class ViewWorkflow extends BaseViewModel { export class ViewWorkflow extends BaseViewModel<Workflow> implements WorkflowTitleInformation {
public static COLLECTIONSTRING = Workflow.COLLECTIONSTRING; public static COLLECTIONSTRING = Workflow.COLLECTIONSTRING;
private _workflow: Workflow;
public get workflow(): Workflow { public get workflow(): Workflow {
return this._workflow; return this._model;
}
public get id(): number {
return this.workflow.id;
} }
public get name(): string { public get name(): string {
@ -43,28 +41,14 @@ export class ViewWorkflow extends BaseViewModel {
return this.getStateById(this.first_state_id); return this.getStateById(this.first_state_id);
} }
/**
* This is set by the repository
*/
public getVerboseName;
public constructor(workflow: Workflow) { public constructor(workflow: Workflow) {
super(Workflow.COLLECTIONSTRING); super(Workflow.COLLECTIONSTRING, workflow);
this._workflow = workflow;
} }
public getTitle = () => {
return this.name;
};
public sortStates(): void { public sortStates(): void {
this.workflow.sortStates(); this.workflow.sortStates();
} }
public getModel(): Workflow {
return this.workflow;
}
/** /**
* Updates the local objects if required * Updates the local objects if required
* *

View File

@ -101,7 +101,7 @@
<!-- The menu content --> <!-- The menu content -->
<mat-menu #motionBlockMenu="matMenu"> <mat-menu #motionBlockMenu="matMenu">
<button mat-menu-item [routerLink]="getSpeakerLink()"> <button *ngIf="block" mat-menu-item [routerLink]="block.listOfSpeakersUrl">
<mat-icon>mic</mat-icon> <mat-icon>mic</mat-icon>
<span translate>List of speakers</span> <span translate>List of speakers</span>
</button> </button>

View File

@ -103,17 +103,6 @@ export class MotionBlockDetailComponent extends ListViewBaseComponent<ViewMotion
); );
} }
/**
* Get link to the list of speakers of the corresponding agenda item
*
* @returns the link to the list of speakers as string
*/
public getSpeakerLink(): string {
if (this.block) {
return `/agenda/${this.block.agenda_item_id}/speakers`;
}
}
/** /**
* Returns the columns that should be shown in the table * Returns the columns that should be shown in the table
* *

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