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 {
collectionString: string;
repository: Type<BaseRepository<any, any>>;
repository: Type<BaseRepository<any, any, any>>;
model: ModelConstructor<BaseModel>;
}

View File

@ -69,7 +69,7 @@ export class AppLoadService {
appConfigs.forEach((config: AppConfig) => {
if (config.models) {
config.models.forEach(entry => {
let repository: BaseRepository<any, any> = null;
let repository: BaseRepository<any, any, any> = null;
repository = this.injector.get(entry.repository);
repositories.push(repository);
this.modelMapper.registerCollectionElement(

View File

@ -123,11 +123,7 @@ export class AutoupdateService {
// Add the objects to the DataStore.
for (const collection of Object.keys(autoupdate.changed)) {
if (this.modelMapper.isCollectionRegistered(collection)) {
await this.DS.add(this.mapObjectsToBaseModels(collection, autoupdate.changed[collection]));
} else {
console.error(`Unregistered collection "${collection}". Ignore it.`);
}
await this.DS.add(this.mapObjectsToBaseModels(collection, autoupdate.changed[collection]));
}
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 models All models that should be mapped to BaseModels
* @returns A list of basemodels constructed from the given models.
*/
private mapObjectsToBaseModels(collection: string, models: object[]): BaseModel[] {
const targetClass = this.modelMapper.getModelConstructor(collection);
return models.map(model => new targetClass(model));
if (this.modelMapper.isCollectionRegistered(collection)) {
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 { 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.
@ -15,12 +15,12 @@ interface UnifiedConstructors {
/**
* 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 = [
ModelConstructor<BaseModel>,
ViewModelConstructor<BaseViewModel>,
BaseRepository<BaseViewModel, BaseModel>
BaseRepository<BaseViewModel, BaseModel, TitleInformation>
];
/**
@ -50,7 +50,7 @@ export class CollectionStringMapperService {
collectionString: string,
model: ModelConstructor<M>,
viewModel: ViewModelConstructor<V>,
repository: BaseRepository<V, M>
repository: BaseRepository<V, M, TitleInformation>
): void {
this.collectionStringMapping[collectionString] = [model, viewModel, repository];
}
@ -98,18 +98,18 @@ export class CollectionStringMapperService {
* @param obj The object to get the repository from.
* @returns the repository
*/
public getRepository<V extends BaseViewModel, M extends BaseModel>(
public getRepository<V extends BaseViewModel, M extends BaseModel, T extends TitleInformation>(
obj: TypeIdentifier
): BaseRepository<V, M> | null {
): BaseRepository<V & T, M, T> | null {
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.
*/
public getAllRepositories(): BaseRepository<any, any>[] {
public getAllRepositories(): BaseRepository<any, any, any>[] {
return Object.values(this.collectionStringMapping).map((types: CollectionStringMappedTypes) => types[2]);
}
}

View File

@ -120,7 +120,12 @@ export class HttpService {
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.");
} else if (typeof e.error === 'object') {
if (e.error.detail) {

View File

@ -20,10 +20,10 @@ export class ViewModelStoreService {
*
* @param collectionType The collection string or constructor.
*/
private getRepository<T extends BaseViewModel>(
collectionType: ViewModelConstructor<T> | string
): BaseRepository<T, any> {
return this.mapperService.getRepository(collectionType) as BaseRepository<T, any>;
private getRepository<V extends BaseViewModel>(
collectionType: ViewModelConstructor<V> | string
): BaseRepository<V, any, 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 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);
}

View File

@ -4,33 +4,41 @@ import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { TranslateService } from '@ngx-translate/core';
import { BaseRepository } from '../base-repository';
import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
import { ConfigService } from 'app/core/ui-services/config.service';
import { DataSendService } from 'app/core/core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.service';
import { HttpService } from 'app/core/core-services/http.service';
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 { 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 { 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 { MotionBlock } from 'app/shared/models/motions/motion-block';
import { Topic } from 'app/shared/models/topics/topic';
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}
*/
@Injectable({
providedIn: 'root'
})
export class ItemRepositoryService extends BaseRepository<ViewItem, Item> {
export class ItemRepositoryService extends BaseHasContentObjectRepository<
ViewItem,
Item,
BaseViewModelWithAgendaItem,
ItemTitleInformation
> {
/**
* Contructor for agenda repository.
*
@ -57,21 +65,24 @@ export class ItemRepositoryService extends BaseRepository<ViewItem, Item> {
MotionBlock
]);
this.setSortFunction((a, b) => {
// 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;
}
});
this.setSortFunction((a, b) => a.weight - b.weight);
}
public getVerboseName = (plural: boolean = false) => {
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
*
@ -80,31 +91,16 @@ export class ItemRepositoryService extends BaseRepository<ViewItem, Item> {
*/
public createViewModel(item: Item): ViewItem {
const contentObject = this.getContentObject(item);
const viewItem = 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;
return new ViewItem(item, contentObject);
}
/**
* 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
* @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>(
agendaItem.content_object.collection,
agendaItem.content_object.id
@ -112,13 +108,13 @@ export class ItemRepositoryService extends BaseRepository<ViewItem, Item> {
if (!contentObject) {
return null;
}
if (contentObject instanceof BaseAgendaViewModel) {
return contentObject as BaseAgendaViewModel;
if (isBaseViewModelWithAgendaItem(contentObject)) {
return contentObject;
} else {
throw new Error(
`The content object (${agendaItem.content_object.collection}, ${
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 { 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 { DataSendService } from 'app/core/core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.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 { Tag } from 'app/shared/models/core/tag';
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 { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
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 { ViewMediafile } from 'app/site/mediafiles/models/view-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.
@ -32,7 +32,11 @@ import { Mediafile } from 'app/shared/models/mediafiles/mediafile';
@Injectable({
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 restPollPath = '/rest/assignments/poll/';
private readonly candidatureOtherPath = '/candidature_other/';
@ -58,15 +62,11 @@ export class AssignmentRepositoryService extends BaseAgendaContentObjectReposito
protected translate: TranslateService,
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>) => {
return assignment.title;
};
public getAgendaTitleWithType = (assignment: Partial<Assignment> | Partial<ViewAssignment>) => {
return assignment.title + ' (' + this.getVerboseName() + ')';
public getTitle = (titleInformation: AssignmentTitleInformation) => {
return titleInformation.title;
};
public getVerboseName = (plural: boolean = false) => {
@ -74,24 +74,22 @@ export class AssignmentRepositoryService extends BaseAgendaContentObjectReposito
};
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 attachments = this.viewModelStoreService.getMany(ViewMediafile, assignment.attachments_id);
const assignmentRelatedUsers = this.createViewAssignmentRelatedUsers(assignment.assignment_related_users);
const assignmentPolls = this.createViewAssignmentPolls(assignment.polls);
const viewAssignment = new ViewAssignment(
return new ViewAssignment(
assignment,
assignmentRelatedUsers,
assignmentPolls,
agendaItem,
item,
listOfSpeakers,
tags,
attachments
);
viewAssignment.getVerboseName = this.getVerboseName;
viewAssignment.getAgendaTitle = () => this.getAgendaTitle(viewAssignment);
viewAssignment.getAgendaTitleWithType = () => this.getAgendaTitleWithType(viewAssignment);
return viewAssignment;
}
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 { auditTime } from 'rxjs/operators';
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 { 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 { Identifiable } from '../../shared/models/base/identifiable';
import { auditTime } from 'rxjs/operators';
import { ViewModelStoreService } from '../core-services/view-model-store.service';
import { OnAfterAppsLoaded } from '../onAfterAppsLoaded';
import { Collection } from 'app/shared/models/base/collection';
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 {
/**
* 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 getTitle: (titleInformation: T) => string;
/**
* Construction routine for the base repository
@ -114,7 +115,7 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
protected loadInitialFromDS(): void {
// Populate the local viewModelStore with ViewModel Objects.
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
@ -145,7 +146,7 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
*/
public changedModels(ids: number[]): void {
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.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
* 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;
/**
* 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.
*/

View File

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

View File

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

View File

@ -1,7 +1,9 @@
import { Injectable } from '@angular/core';
import { HttpHeaders } from '@angular/common/http';
import { BaseRepository } from '../base-repository';
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
import { TranslateService } from '@ngx-translate/core';
import { ViewMediafile, MediafileTitleInformation } from 'app/site/mediafiles/models/view-mediafile';
import { Mediafile } from 'app/shared/models/mediafiles/mediafile';
import { User } from 'app/shared/models/users/user';
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 { DataSendService } from 'app/core/core-services/data-send.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 { 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
@ -20,7 +22,11 @@ import { TranslateService } from '@ngx-translate/core';
@Injectable({
providedIn: 'root'
})
export class MediafileRepositoryService extends BaseRepository<ViewMediafile, Mediafile> {
export class MediafileRepositoryService extends BaseIsListOfSpeakersContentObjectRepository<
ViewMediafile,
Mediafile,
MediafileTitleInformation
> {
/**
* Constructor for the mediafile repository
* @param DS Data store
@ -40,6 +46,10 @@ export class MediafileRepositoryService extends BaseRepository<ViewMediafile, Me
this.initSorting();
}
public getTitle = (titleInformation: MediafileTitleInformation) => {
return titleInformation.title;
};
public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Files' : 'File');
};
@ -51,10 +61,9 @@ export class MediafileRepositoryService extends BaseRepository<ViewMediafile, Me
* @returns a new mediafile ViewModel
*/
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 viewMediafile = new ViewMediafile(file, uploader);
viewMediafile.getVerboseName = this.getVerboseName;
return viewMediafile;
return new ViewMediafile(file, listOfSpeakers, uploader);
}
/**

View File

@ -9,7 +9,7 @@ import { ConfigService } from 'app/core/ui-services/config.service';
import { DataSendService } from '../../core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.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';
type SortProperty = 'prefix' | 'name';
@ -27,7 +27,7 @@ type SortProperty = 'prefix' | 'name';
@Injectable({
providedIn: 'root'
})
export class CategoryRepositoryService extends BaseRepository<ViewCategory, Category> {
export class CategoryRepositoryService extends BaseRepository<ViewCategory, Category, CategoryTitleInformation> {
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) => {
return this.translate.instant(plural ? 'Categories' : 'Category');
};
protected createViewModel(category: Category): ViewCategory {
const viewCategory = new ViewCategory(category);
viewCategory.getVerboseName = this.getVerboseName;
return viewCategory;
return new ViewCategory(category);
}
/**

View File

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

View File

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

View File

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

View File

@ -6,14 +6,13 @@ import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
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 { ConfigService } from 'app/core/ui-services/config.service';
import { DataSendService } from '../../core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.service';
import { DiffLinesInParagraph, DiffService, LineRange, ModificationType } from '../../ui-services/diff.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 { Mediafile } from 'app/shared/models/mediafiles/mediafile';
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 { TreeIdNode } from 'app/core/ui-services/tree.service';
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 { ViewUnifiedChange } from 'app/shared/models/motions/view-unified-change';
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 { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
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 { OperatorService } from 'app/core/core-services/operator.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';
@ -88,7 +88,11 @@ export interface ParagraphToChoose {
@Injectable({
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
*/
@ -127,7 +131,6 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository<V
Category,
User,
Workflow,
Item,
MotionBlock,
Mediafile,
Tag,
@ -141,37 +144,39 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository<V
});
}
public getTitle = (motion: Partial<Motion> | Partial<ViewMotion>) => {
if (motion.identifier) {
return motion.identifier + ': ' + motion.title;
public getTitle = (titleInformation: MotionTitleInformation) => {
if (titleInformation.identifier) {
return titleInformation.identifier + ': ' + titleInformation.title;
} else {
return motion.title;
return titleInformation.title;
}
};
public getIdentifierOrTitle = (motion: Partial<Motion> | Partial<ViewMotion>) => {
if (motion.identifier) {
return motion.identifier;
public getIdentifierOrTitle = (titleInformation: MotionTitleInformation) => {
if (titleInformation.identifier) {
return titleInformation.identifier;
} 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 (motion.identifier) {
return this.translate.instant('Motion') + ' ' + motion.identifier;
if (titleInformation.identifier) {
return numberPrefix + this.translate.instant('Motion') + ' ' + titleInformation.identifier;
} 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.
if (motion.identifier) {
return this.translate.instant('Motion') + ' ' + motion.identifier;
if (titleInformation.identifier) {
return numberPrefix + this.translate.instant('Motion') + ' ' + titleInformation.identifier;
} 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 workflow = this.viewModelStoreService.get(ViewWorkflow, motion.workflow_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 attachments = this.viewModelStoreService.getMany(ViewMediafile, motion.attachments_id);
const tags = this.viewModelStoreService.getMany(ViewTag, motion.tags_id);
@ -215,6 +221,7 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository<V
workflow,
state,
item,
listOfSpeakers,
block,
attachments,
tags,
@ -224,11 +231,7 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository<V
personalNote
);
viewMotion.getIdentifierOrTitle = () => this.getIdentifierOrTitle(viewMotion);
viewMotion.getTitle = () => this.getTitle(viewMotion);
viewMotion.getVerboseName = this.getVerboseName;
viewMotion.getAgendaTitle = () => this.getAgendaTitle(viewMotion);
viewMotion.getProjectorTitle = viewMotion.getAgendaTitle;
viewMotion.getAgendaTitleWithType = () => this.getAgendaTitleWithType(viewMotion);
viewMotion.getProjectorTitle = () => this.getAgendaSlideTitle(viewMotion);
return viewMotion;
}

View File

@ -1,13 +1,14 @@
import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { DataSendService } from '../../core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.service';
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 { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { TranslateService } from '@ngx-translate/core';
/**
* Repository Services for statute paragraphs
@ -19,7 +20,11 @@ import { TranslateService } from '@ngx-translate/core';
@Injectable({
providedIn: 'root'
})
export class StatuteParagraphRepositoryService extends BaseRepository<ViewStatuteParagraph, StatuteParagraph> {
export class StatuteParagraphRepositoryService extends BaseRepository<
ViewStatuteParagraph,
StatuteParagraph,
StatuteParagraphTitleInformation
> {
/**
* Creates a StatuteParagraphRepository
* 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);
}
public getTitle = (titleInformation: StatuteParagraphTitleInformation) => {
return titleInformation.title;
};
public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Statute paragraphs' : 'Statute paragraph');
};
protected createViewModel(statuteParagraph: StatuteParagraph): ViewStatuteParagraph {
const viewStatuteParagraph = new ViewStatuteParagraph(statuteParagraph);
viewStatuteParagraph.getVerboseName = this.getVerboseName;
return viewStatuteParagraph;
return new ViewStatuteParagraph(statuteParagraph);
}
}

View File

@ -1,7 +1,9 @@
import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
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 { DataStoreService } from '../../core-services/data-store.service';
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 { HttpService } from 'app/core/core-services/http.service';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { TranslateService } from '@ngx-translate/core';
/**
* Repository Services for Categories
@ -25,7 +26,7 @@ import { TranslateService } from '@ngx-translate/core';
@Injectable({
providedIn: 'root'
})
export class WorkflowRepositoryService extends BaseRepository<ViewWorkflow, Workflow> {
export class WorkflowRepositoryService extends BaseRepository<ViewWorkflow, Workflow, WorkflowTitleInformation> {
/**
* 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) => {
return this.translate.instant(plural ? 'Workflows' : 'Workflow');
};
@ -81,9 +86,7 @@ export class WorkflowRepositoryService extends BaseRepository<ViewWorkflow, Work
* @param workflow the Workflow to convert
*/
protected createViewModel(workflow: Workflow): ViewWorkflow {
const viewWorkflow = new ViewWorkflow(workflow);
viewWorkflow.getVerboseName = this.getVerboseName;
return viewWorkflow;
return new ViewWorkflow(workflow);
}
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,17 +2,17 @@ import { Injectable } from '@angular/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 { DataStoreService } from 'app/core/core-services/data-store.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 { 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 { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
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
@ -20,7 +20,11 @@ import { ViewItem } from 'app/site/agenda/models/view-item';
@Injectable({
providedIn: 'root'
})
export class TopicRepositoryService extends BaseAgendaContentObjectRepository<ViewTopic, Topic> {
export class TopicRepositoryService extends BaseIsAgendaItemAndListOfSpeakersContentObjectRepository<
ViewTopic,
Topic,
TopicTitleInformation
> {
/**
* Constructor calls the parent constructor
*
@ -35,16 +39,25 @@ export class TopicRepositoryService extends BaseAgendaContentObjectRepository<Vi
viewModelStoreService: ViewModelStoreService,
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>) => {
return topic.title;
public getTitle = (titleInformation: TopicTitleInformation) => {
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.
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) => {
@ -60,11 +73,8 @@ export class TopicRepositoryService extends BaseAgendaContentObjectRepository<Vi
public createViewModel(topic: Topic): ViewTopic {
const attachments = this.viewModelStoreService.getMany(ViewMediafile, topic.attachments_id);
const item = this.viewModelStoreService.get(ViewItem, topic.agenda_item_id);
const viewTopic = new ViewTopic(topic, attachments, item);
viewTopic.getVerboseName = this.getVerboseName;
viewTopic.getAgendaTitle = () => this.getAgendaTitle(viewTopic);
viewTopic.getAgendaTitleWithType = () => this.getAgendaTitle(viewTopic);
return viewTopic;
const listOfSpeakers = this.viewModelStoreService.get(ViewListOfSpeakers, topic.list_of_speakers_id);
return new ViewTopic(topic, attachments, item, listOfSpeakers);
}
/**

View File

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

View File

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

View File

@ -12,7 +12,7 @@ import { Group } from 'app/shared/models/users/group';
import { HttpService } from 'app/core/core-services/http.service';
import { NewEntry } from 'app/core/ui-services/base-import.service';
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 { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
@ -32,7 +32,7 @@ type SortProperty = 'first_name' | 'last_name' | 'number';
@Injectable({
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
*/
@ -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) => {
return this.translate.instant(plural ? 'Participants' : 'Participant');
};
@ -72,10 +121,8 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {
public createViewModel(user: User): ViewUser {
const groups = this.viewModelStoreService.getMany(ViewGroup, user.groups_id);
const viewUser = new ViewUser(user, groups);
viewUser.getVerboseName = this.getVerboseName;
viewUser.getNumberForName = (nr: number) => {
return `${this.translate.instant('No.')} ${nr}`;
};
viewUser.getFullName = () => this.getFullName(viewUser);
viewUser.getShortName = () => this.getShortName(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 { 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';
/**
@ -208,7 +208,7 @@ export abstract class BaseFilterListService<V extends BaseViewModel> {
* @param exexcludeIds Set if certain ID's should be excluded from filtering
*/
protected updateFilterForRepo(
repo: BaseRepository<BaseViewModel, BaseModel>,
repo: BaseRepository<BaseViewModel, BaseModel, TitleInformation>,
filter: OsFilter,
noneOptionLabel?: string,
excludeIds?: number[]

View File

@ -92,7 +92,7 @@ export class SearchService {
*/
public registerModel(
collectionString: string,
repo: BaseRepository<any, any>,
repo: BaseRepository<any, any, any>,
displayOrder: number,
openInNewTab: boolean = false
): 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 { BaseModel } from '../base/base-model';
/**
* 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;
}
import { ContentObject } from '../base/content-object';
import { BaseModelWithContentObject } from '../base/base-model-with-content-object';
/**
* Determine visibility states for agenda items
@ -24,19 +15,36 @@ export const itemVisibilityChoices = [
* Representations of agenda Item
* @ignore
*/
export class Item extends BaseModel<Item> {
export class Item extends BaseModelWithContentObject<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 item_number: string;
public title_information: object;
public get item_number(): string {
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 closed: boolean;
public type: number;
public is_hidden: boolean;
public duration: number; // minutes
public speakers: Speaker[];
public speaker_list_closed: boolean;
public content_object: ContentObject;
public weight: number;
public parent_id: number;
@ -45,14 +53,4 @@ export class Item extends BaseModel<Item> {
public constructor(input?: any) {
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.
*
@ -16,49 +5,23 @@ export enum SpeakerState {
* Part of the 'speakers' list.
* @ignore
*/
export class Speaker extends Deserializer {
public static COLLECTIONSTRING = 'agenda/item/speakers';
public id: number;
public user_id: number;
export interface Speaker {
id: number;
user_id: number;
/**
* ISO datetime string to indicate the begin time of the speech. Empty if
* 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
* speech has not ended
*/
public end_time: string;
end_time: string;
public weight: number;
public marked: boolean;
public 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;
}
}
weight: number;
marked: boolean;
item_id: number;
}

View File

@ -1,12 +1,12 @@
import { BaseModel } from '../base/base-model';
import { AssignmentRelatedUser } from './assignment-related-user';
import { AssignmentPoll } from './assignment-poll';
import { BaseModelWithAgendaItemAndListOfSpeakers } from '../base/base-model-with-agenda-item-and-list-of-speakers';
/**
* Representation of an assignment.
* @ignore
*/
export class Assignment extends BaseModel<Assignment> {
export class Assignment extends BaseModelWithAgendaItemAndListOfSpeakers<Assignment> {
public static COLLECTIONSTRING = 'assignments/assignment';
public id: number;
@ -17,7 +17,6 @@ export class Assignment extends BaseModel<Assignment> {
public assignment_related_users: AssignmentRelatedUser[];
public poll_description_default: number;
public polls: AssignmentPoll[];
public agenda_item_id: number;
public tags_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 { BaseModel } from '../base/base-model';
import { BaseModelWithListOfSpeakers } from '../base/base-model-with-list-of-speakers';
interface FileMetadata {
name: string;
type: string;
// Only for PDFs
pages: number;
encrypted?: boolean;
}
/**
* Representation of MediaFile. Has the nested property "File"
* @ignore
*/
export class Mediafile extends BaseModel<Mediafile> {
export class Mediafile extends BaseModelWithListOfSpeakers<Mediafile> {
public static COLLECTIONSTRING = 'mediafiles/mediafile';
public id: number;
public title: string;
public mediafile: File;
public mediafile: FileMetadata;
public media_url_prefix: string;
public uploader_id: number;
public filesize: string;
@ -20,11 +28,6 @@ export class Mediafile extends BaseModel<Mediafile> {
super(Mediafile.COLLECTIONSTRING, input);
}
public deserialize(input: any): void {
Object.assign(this, input);
this.mediafile = new File(input.mediafile);
}
/**
* 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.
* @ignore
*/
export class MotionBlock extends BaseModel {
export class MotionBlock extends BaseModelWithAgendaItemAndListOfSpeakers<MotionBlock> {
public static COLLECTIONSTRING = 'motions/motion-block';
public id: number;
public title: string;
public agenda_item_id: number;
public constructor(input?: any) {
super(MotionBlock.COLLECTIONSTRING, input);

View File

@ -1,6 +1,6 @@
import { MotionSubmitter } from './motion-submitter';
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 {
id: number;
@ -12,11 +12,11 @@ export interface MotionComment {
/**
* Representation of Motion.
*
* Slightly Defined cause heavy maintaince on server side.
* Slightly defined cause heavy maintenance on server side.
*
* @ignore
*/
export class Motion extends BaseModel {
export class Motion extends BaseModelWithAgendaItemAndListOfSpeakers<Motion> {
public static COLLECTIONSTRING = 'motions/motion';
public id: number;
@ -44,7 +44,6 @@ export class Motion extends BaseModel {
public tags_id: number[];
public attachments_id: number[];
public polls: MotionPoll[];
public agenda_item_id: number;
public weight: number;
public sort_parent_id: number;
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.
* @ignore
*/
export class Topic extends BaseModel<Topic> {
export class Topic extends BaseModelWithAgendaItemAndListOfSpeakers<Topic> {
public static COLLECTIONSTRING = 'topics/topic';
public id: number;
public title: string;
public text: string;
public attachments_id: number[];
public agenda_item_id: number;
public constructor(input?: any) {
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 { MediaUploadContentComponent } from './components/media-upload-content/media-upload-content.component';
import { PrecisionPipe } from './pipes/precision.pipe';
import { SpeakerButtonComponent } from './components/speaker-button/speaker-button.component';
/**
* Share Module for all "dumb" components and pipes.
@ -206,7 +207,8 @@ import { PrecisionPipe } from './pipes/precision.pipe';
CountdownTimeComponent,
MediaUploadContentComponent,
PrecisionPipe,
ScrollingModule
ScrollingModule,
SpeakerButtonComponent
],
declarations: [
PermsDirective,
@ -234,7 +236,8 @@ import { PrecisionPipe } from './pipes/precision.pipe';
SlideContainerComponent,
CountdownTimeComponent,
MediaUploadContentComponent,
PrecisionPipe
PrecisionPipe,
SpeakerButtonComponent
],
providers: [
{ 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 { DurationService } from 'app/core/ui-services/duration.service';
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';
@Injectable({

View File

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

View File

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

View File

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

View File

@ -21,6 +21,8 @@ import { ViewItem } from '../../models/view-item';
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
import { _ } from 'app/core/translate/translation-marker';
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.
@ -35,12 +37,12 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem, Item, I
/**
* 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
*/
public displayedColumnsMobile: string[] = ['title', 'speakers'];
public displayedColumnsMobile: string[] = ['title'];
public isNumberingAllowed: boolean;
@ -105,7 +107,8 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem, Item, I
private csvExport: AgendaCsvExportService,
public filterService: AgendaFilterListService,
private agendaPdfService: AgendaPdfService,
private pdfService: PdfDocumentService
private pdfService: PdfDocumentService,
private listOfSpeakersRepo: ListOfSpeakersRepositoryService
) {
super(titleService, translate, matSnackBar, repo, route, storage, filterService);
@ -126,6 +129,16 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem, Item, I
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.
*
@ -183,16 +196,6 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem, Item, I
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.
* Comes from the HeadBar Component
@ -258,6 +261,9 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem, Item, I
*/
public getColumnDefinition(): string[] {
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')) {
columns = columns.concat(['menu']);
}

View File

@ -2,8 +2,8 @@
<!-- Title -->
<div class="title-slot">
<h2>
<span *ngIf="!currentListOfSpeakers" translate>List of speakers</span>
<span *ngIf="currentListOfSpeakers" translate>Current list of speakers</span>
<span *ngIf="!isCurrentListOfSpeakers" translate>List of speakers</span>
<span *ngIf="isCurrentListOfSpeakers" translate>Current list of speakers</span>
</h2>
</div>
<div class="menu-slot" *osPerms="['agenda.can_manage_list_of_speakers', 'core.can_manage_projector']">
@ -11,9 +11,9 @@
</div>
</os-head-bar>
<mat-card class="os-card speaker-card" *ngIf="viewItem">
<mat-card class="os-card speaker-card" *ngIf="viewListOfSpeakers">
<!-- 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 -->
<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">
<div class="finished-speaker-grid">
<div class="number">{{ number + 1 }}.</div>
<div class="name">{{ speaker }}</div>
<div class="name">{{ speaker.getTitle() }}</div>
<div class="time">
{{ durationString(speaker) }} ({{ 'Start time' | translate }}: {{ startTimeToString(speaker) }})
</div>
@ -53,7 +53,7 @@
<mat-icon>mic</mat-icon>
</span>
<span class="name">{{ activeSpeaker }}</span>
<span class="name">{{ activeSpeaker.getTitle() }}</span>
<span class="suffix">
<!-- Stop speaker button -->
@ -77,29 +77,29 @@
[enable]="opCanManage()"
(sortEvent)="onSortingChange($event)"
>
<!-- implicit item references into the component using ng-template slot -->
<ng-template let-item>
<!-- implicit speaker references into the component using ng-template slot -->
<ng-template let-speaker>
<span *osPerms="'agenda.can_manage_list_of_speakers'">
<span *ngIf="hasSpokenCount(item)" class="red-warning-text speaker-warning">
{{ hasSpokenCount(item) + 1 }}. <span translate>contribution</span>
<span *ngIf="hasSpokenCount(speaker)" class="red-warning-text speaker-warning">
{{ hasSpokenCount(speaker) + 1 }}. <span translate>contribution</span>
</span>
<span *ngIf="item.gender">({{ item.gender | translate }})</span>
<span *ngIf="speaker.gender">({{ speaker.gender | translate }})</span>
</span>
<!-- Start, start and delete buttons -->
<span *osPerms="'agenda.can_manage_list_of_speakers'">
<!-- 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>
</button>
<!-- star button -->
<button mat-icon-button matTooltip="{{ 'Mark speaker' | translate }}" (click)="onMarkButton(item)">
<mat-icon>{{ item.marked ? 'star' : 'star_border' }}</mat-icon>
<button mat-icon-button matTooltip="{{ 'Mark speaker' | translate }}" (click)="onMarkButton(speaker)">
<mat-icon>{{ speaker.marked ? 'star' : 'star_border' }}</mat-icon>
</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>
</button>
</span>
@ -139,39 +139,39 @@
<mat-menu #speakerMenu="matMenu">
<os-projector-button
*ngIf="viewItem && projectors && projectors.length > 1"
*ngIf="viewListOfSpeakers && projectors && projectors.length > 1"
[object]="getClosSlide()"
[menuItem]="true"
text="Current list of speakers (as slide)"
></os-projector-button>
<os-projector-button
*ngIf="viewItem"
[object]="viewItem.listOfSpeakersSlide"
*ngIf="viewListOfSpeakers"
[object]="viewListOfSpeakers"
[menuItem]="true"
text="List of speakers"
></os-projector-button>
<os-projector-button
*ngIf="viewItem"
[object]="viewItem.contentObject"
*ngIf="viewListOfSpeakers"
[object]="viewListOfSpeakers.contentObject"
[menuItem]="true"
[text]="getContentObjectProjectorButtonText()"
></os-projector-button>
<button mat-menu-item *ngIf="closedList" (click)="openSpeakerList()">
<button mat-menu-item *ngIf="isListOfSpeakersClosed" (click)="openSpeakerList()">
<mat-icon>mic</mat-icon>
<span translate>Open list of speakers</span>
</button>
<button mat-menu-item *ngIf="!closedList" (click)="closeSpeakerList()">
<button mat-menu-item *ngIf="!isListOfSpeakersClosed" (click)="closeSpeakerList()">
<mat-icon>mic_off</mat-icon>
<span translate>Close list of speakers</span>
</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>
<span translate>Remove all speakers</span>
</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 { ProjectorRepositoryService } from 'app/core/repositories/projector/projector-repository.service';
import { PromptService } from 'app/core/ui-services/prompt.service';
import { SpeakerState } from 'app/shared/models/agenda/speaker';
import { SpeakerRepositoryService } from 'app/core/repositories/agenda/speaker-repository.service';
import { ViewItem } from '../../models/view-item';
import { ViewSpeaker } from '../../models/view-speaker';
import { ViewSpeaker, SpeakerState } from '../../models/view-speaker';
import { ViewProjector } from 'app/site/projector/models/view-projector';
import { ViewUser } from 'app/site/users/models/view-user';
import { UserRepositoryService } from 'app/core/repositories/users/user-repository.service';
import { DurationService } from 'app/core/ui-services/duration.service';
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 { 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 { 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.
@ -37,12 +35,12 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
/**
* 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
*/
public viewItem: ViewItem;
public viewListOfSpeakers: ViewListOfSpeakers;
/**
* Holds the speakers
@ -80,19 +78,19 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
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 {
return this.viewItem && this.viewItem.item.speaker_list_closed;
public get isListOfSpeakersClosed(): boolean {
return this.viewListOfSpeakers && this.viewListOfSpeakers.closed;
}
public get emptyList(): boolean {
public get isListOfSpeakersEmpty(): boolean {
if (this.speakers && this.speakers.length) {
return false;
} else if (this.finishedSpeakers && this.finishedSpeakers.length) {
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 closItemSubscription: Subscription | null;
private closSubscription: Subscription | null;
/**
* Constructor for speaker list component. Generates the forms and subscribes
* to the {@link currentListOfSpeakers}
* Constructor for speaker list component. Generates the forms.
*
* @param title
* @param translate
@ -112,43 +109,29 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
* @param projectorRepo
* @param route Angulars ActivatedRoute
* @param DS the DataStore
* @param repo Repository for speakers
* @param itemRepo Repository for agendaItems
* @param op the current operator
* @param listOfSpeakersRepo Repository for list of speakers
* @param operator the current operator
* @param promptService
* @param currentAgendaItemService
* @param currentListOfSpeakersService
* @param durationService helper for speech duration display
*/
public constructor(
title: Title,
protected translate: TranslateService, // protected required for ng-translate-extract
snackBar: MatSnackBar,
projectorRepo: ProjectorRepositoryService,
private projectorRepo: ProjectorRepositoryService,
private route: ActivatedRoute,
private repo: SpeakerRepositoryService,
private itemRepo: ItemRepositoryService,
private op: OperatorService,
private listOfSpeakersRepo: ListOfSpeakersRepositoryService,
private operator: OperatorService,
private promptService: PromptService,
private currentAgendaItemService: CurrentAgendaItemService,
private currentListOfSpeakersService: CurrentListOfSpeakersService,
private durationService: DurationService,
private userRepository: UserRepositoryService,
private collectionStringMapper: CollectionStringMapperService,
private currentListOfSpeakersSlideService: CurrentListOfSpeakersSlideService
) {
super(title, translate, snackBar);
this.isCurrentListOfSpeakers();
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
*/
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
this.users = this.userRepository.getViewModelListBehaviorSubject();
@ -171,16 +172,7 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
}
public opCanManage(): boolean {
return this.op.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';
}
return this.operator.hasPerms('agenda.can_manage_list_of_speakers');
}
/**
@ -198,14 +190,14 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
if (this.projectorSubscription) {
this.projectorSubscription.unsubscribe();
this.viewItem = null;
this.viewListOfSpeakers = null;
}
this.projectorSubscription = this.currentAgendaItemService
.getAgendaItemObservable(referenceProjector)
.subscribe(item => {
if (item) {
this.setSpeakerList(item.id);
this.projectorSubscription = this.currentListOfSpeakersService
.getListOfSpeakersObservable(referenceProjector)
.subscribe(listOfSpeakers => {
if (listOfSpeakers) {
this.setListOfSpeakersId(listOfSpeakers.id);
}
});
}
@ -218,31 +210,20 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
}
/**
* Extract the ID from the url
* 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
* Sets the current list of speakers id to show
*
* @param item the item to use as List of Speakers
* @param id the list of speakers id
*/
private setSpeakerList(id: number): void {
if (this.closItemSubscription) {
this.closItemSubscription.unsubscribe();
private setListOfSpeakersId(id: number): void {
if (this.closSubscription) {
this.closSubscription.unsubscribe();
}
this.closItemSubscription = this.itemRepo.getViewModelObservable(id).subscribe(newAgendaItem => {
if (newAgendaItem) {
this.viewItem = newAgendaItem;
const allSpeakers = this.repo.createSpeakerList(newAgendaItem.item);
this.closSubscription = this.listOfSpeakersRepo.getViewModelObservable(id).subscribe(listOfSpeakers => {
if (listOfSpeakers) {
this.viewListOfSpeakers = listOfSpeakers;
const allSpeakers = this.viewListOfSpeakers.speakers.sort((a, b) => a.weight - b.weight);
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);
// 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.
* 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 {
const verboseName = this.collectionStringMapper
.getRepository(this.viewItem.item.content_object.collection)
.getRepository(this.viewListOfSpeakers.listOfSpeakers.content_object.collection)
.getVerboseName();
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.
*/
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 {
// extract the ids from the ViewSpeaker array
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
*
* @param item the speaker marked in the list
* @param speaker the speaker marked in the list
*/
public onStartButton(item: ViewSpeaker): void {
this.repo.startSpeaker(item.id, this.viewItem).then(null, this.raiseError);
public onStartButton(speaker: ViewSpeaker): void {
this.listOfSpeakersRepo.startSpeaker(this.viewListOfSpeakers, speaker).then(null, this.raiseError);
}
/**
* Click on the mic-cross button
*/
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 {
this.repo.markSpeaker(item.user.id, !item.marked, this.viewItem).then(null, this.raiseError);
public onMarkButton(speaker: ViewSpeaker): void {
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
*/
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
*/
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> {
if (!this.viewItem.item.speaker_list_closed) {
return this.itemRepo.update({ speaker_list_closed: true }, this.viewItem);
if (!this.viewListOfSpeakers.closed) {
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> {
if (this.viewItem.item.speaker_list_closed) {
return this.itemRepo.update({ speaker_list_closed: false }, this.viewItem);
if (this.viewListOfSpeakers.closed) {
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?'
);
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-menu #topicExtraMenu="matMenu">
<button mat-menu-item *ngIf="topic" [routerLink]="getSpeakerLink()">
<mat-icon>mic</mat-icon>
<span translate>List of speakers</span>
</button>
<os-speaker-button [object]="topic" [menuItem]="true"></os-speaker-button>
<div *osPerms="'agenda.can_manage'">
<mat-divider></mat-divider>
<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 { 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 { OperatorService } from 'app/core/core-services/operator.service';
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
*/

View File

@ -1,6 +1,5 @@
import { CreateTopic } from './create-topic';
import { ViewTopic } from './view-topic';
import { Topic } from 'app/shared/models/topics/topic';
/**
* View model for Topic('Agenda item') creation.
@ -8,7 +7,7 @@ import { Topic } from 'app/shared/models/topics/topic';
*/
export class ViewCreateTopic extends ViewTopic {
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);
}
public getModel(): Topic {
return super.getModel();
}
public getVerboseName = () => {
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 { Speaker, SpeakerState } from 'app/shared/models/agenda/speaker';
import { BaseAgendaViewModel, isAgendaBaseModel } from 'app/site/base/base-agenda-view-model';
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
import {
BaseViewModelWithAgendaItem,
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;
private _item: Item;
private _contentObject: BaseAgendaViewModel;
public get item(): Item {
return this._item;
}
public get contentObject(): BaseAgendaViewModel {
return this._contentObject;
}
public get id(): number {
return this.item.id;
return this._model;
}
public get itemNumber(): string {
@ -34,13 +32,6 @@ export class ViewItem extends BaseViewModel {
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 {
return this.item.type;
}
@ -81,13 +72,6 @@ export class ViewItem extends BaseViewModel {
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
* it's own hierarchy level (items sharing a parent)
@ -103,46 +87,7 @@ export class ViewItem extends BaseViewModel {
return this.item.parent_id;
}
/**
* This is set by the repository
*/
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;
public constructor(item: Item, contentObject?: BaseViewModelWithAgendaItem) {
super(Item.COLLECTIONSTRING, item, isBaseViewModelWithAgendaItem, 'BaseViewModelWithAgendaItem', contentObject);
}
}

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 { 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 { 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
*/
export class ViewSpeaker extends BaseViewModel {
export class ViewSpeaker implements Updateable, Identifiable {
private _speaker: Speaker;
private _user: ViewUser | null;
private _user?: ViewUser;
public get speaker(): Speaker {
return this._speaker;
@ -22,6 +32,10 @@ export class ViewSpeaker extends BaseViewModel {
return this.speaker.id;
}
public get userId(): number {
return this.speaker.user_id;
}
public get weight(): number {
return this.speaker.weight;
}
@ -44,8 +58,20 @@ export class ViewSpeaker extends BaseViewModel {
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 {
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 {
@ -56,13 +82,7 @@ export class ViewSpeaker extends BaseViewModel {
return this.user ? this.user.gender : '';
}
/**
* This is set by the repository
*/
public getVerboseName;
public constructor(speaker: Speaker, user?: ViewUser) {
super('TODO');
this._speaker = speaker;
this._user = user;
}
@ -71,13 +91,11 @@ export class ViewSpeaker extends BaseViewModel {
return this.name;
};
public getModel(): User {
return this.user.user;
public updateDependencies(update: BaseViewModel): boolean {
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 { BaseAgendaViewModel } from 'app/site/base/base-agenda-view-model';
import { SearchRepresentation } from 'app/core/ui-services/search.service';
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
import { ViewItem } from './view-item';
import { BaseViewModel } from 'app/site/base/base-view-model';
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
* @ignore
*/
export class ViewTopic extends BaseAgendaViewModel {
export class ViewTopic extends BaseViewModelWithAgendaItemAndListOfSpeakers implements TopicTitleInformation {
public static COLLECTIONSTRING = Topic.COLLECTIONSTRING;
protected _topic: Topic;
private _attachments: ViewMediafile[];
private _agendaItem: ViewItem;
private _attachments?: ViewMediafile[];
public get topic(): Topic {
return this._topic;
return this._model;
}
public get attachments(): ViewMediafile[] {
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;
return this._attachments || [];
}
public get attachments_id(): number[] {
@ -49,34 +42,14 @@ export class ViewTopic extends BaseAgendaViewModel {
return this.topic.text;
}
/**
* This is set by the repository
*/
public getVerboseName;
public getAgendaTitle;
public getAgendaTitleWithType;
public constructor(topic: Topic, attachments?: ViewMediafile[], item?: ViewItem) {
super(Topic.COLLECTIONSTRING);
this._topic = topic;
public constructor(
topic: Topic,
attachments?: ViewMediafile[],
item?: ViewItem,
listOfSpeakers?: ViewListOfSpeakers
) {
super(Topic.COLLECTIONSTRING, topic, item, listOfSpeakers);
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 {
super.updateDependencies(update);
if (update instanceof ViewMediafile && this.attachments_id.includes(update.id)) {
const attachmentIndex = this.attachments.findIndex(mediafile => mediafile.id === update.id);
if (attachmentIndex < 0) {
@ -126,8 +100,5 @@ export class ViewTopic extends BaseAgendaViewModel {
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>
</button>
<!-- List of speakers -->
<div *ngIf="assignment.agendaItem">
<button mat-menu-item [routerLink]="getSpeakerLink()" *osPerms="'agenda.can_see'">
<mat-icon>mic</mat-icon>
<span translate>List of speakers</span>
</button>
</div>
<os-speaker-button [object]="assignment" [menuItem]="true"></os-speaker-button>
</div>
<!-- Project -->
<os-projector-button [object]="assignment" [menuItem]="true"></os-projector-button>
@ -62,7 +57,9 @@
<ng-template #metaInfoTemplate>
<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.assignment.description" [innerHTML]="assignment.assignment.description"></div>
</div>
@ -227,7 +224,7 @@
matInput
placeholder="{{ 'Title' | translate }}"
formControlName="title"
[value]="assignmentCopy.getTitle() || ''"
[value]="assignmentCopy.title || ''"
/>
</mat-form-field>
</div>

View File

@ -501,11 +501,4 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
.sortCandidates(listInNewOrder.map(relatedUser => relatedUser.id), this.assignment)
.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();
}
public getProjectorTitle(): string {
return this.getTitle();
}
/**
* Creates a copy with deep-copy on all changing numerical values,
* but intact uncopied references to the users

View File

@ -1,5 +1,4 @@
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 { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
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 { ViewAssignmentPoll } from './view-assignment-poll';
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
@ -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;
private _assignment: Assignment;
private _assignmentRelatedUsers: ViewAssignmentRelatedUser[];
private _assignmentPolls: ViewAssignmentPoll[];
private _agendaItem?: ViewItem;
private _tags?: ViewTag[];
private _attachments?: ViewMediafile[];
public get id(): number {
return this._assignment ? this._assignment.id : null;
}
public get assignment(): Assignment {
return this._assignment;
return this._model;
}
public get polls(): ViewAssignmentPoll[] {
@ -75,10 +76,6 @@ export class ViewAssignment extends BaseAgendaViewModel {
return this._assignmentRelatedUsers;
}
public get agendaItem(): ViewItem | null {
return this._agendaItem;
}
public get tags(): ViewTag[] {
return this._tags || [];
}
@ -123,35 +120,26 @@ export class ViewAssignment extends BaseAgendaViewModel {
return this._assignmentRelatedUsers ? this._assignmentRelatedUsers.length : 0;
}
/**
* Constructor. Is set by the repository
*/
public getVerboseName;
public getAgendaTitle;
public getAgendaTitleWithType;
public constructor(
assignment: Assignment,
assignmentRelatedUsers: ViewAssignmentRelatedUser[],
assignmentPolls: ViewAssignmentPoll[],
agendaItem?: ViewItem,
item?: ViewItem,
listOfSpeakers?: ViewListOfSpeakers,
tags?: ViewTag[],
attachments?: ViewMediafile[]
) {
super(Assignment.COLLECTIONSTRING);
super(Assignment.COLLECTIONSTRING, assignment, item, listOfSpeakers);
this._assignment = assignment;
this._assignmentRelatedUsers = assignmentRelatedUsers;
this._assignmentPolls = assignmentPolls;
this._agendaItem = agendaItem;
this._tags = tags;
this._attachments = attachments;
}
public updateDependencies(update: BaseViewModel): void {
if (update instanceof ViewItem && update.id === this.assignment.agenda_item_id) {
this._agendaItem = update;
} else if (update instanceof ViewTag && this.assignment.tags_id.includes(update.id)) {
super.updateDependencies(update);
if (update instanceof ViewTag && this.assignment.tags_id.includes(update.id)) {
const tagIndex = this._tags.findIndex(_tag => _tag.id === update.id);
if (tagIndex < 0) {
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 {
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 { BaseViewModel } from './base-view-model';
import { ConfigService } from 'app/core/ui-services/config.service';
import { BaseModel } from 'app/shared/models/base/base-model';
/**
* 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;
/**

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 { Updateable } from './updateable';
export type TitleInformation = object;
export interface ViewModelConstructor<T extends BaseViewModel> {
COLLECTIONSTRING: string;
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.
*/
export abstract class BaseViewModel implements Displayable, Identifiable, Collection, Updateable {
/**
* Force children to have an id.
*/
public abstract id: number;
export abstract class BaseViewModel<M extends BaseModel = any>
implements Displayable, Identifiable, Collection, Updateable {
protected _model: M;
public get id(): number {
return this._model.id;
}
/**
* force children of BaseModel to have a collectionString.
@ -34,7 +38,8 @@ export abstract class BaseViewModel implements Displayable, Identifiable, Collec
return this._collectionString;
}
public abstract getTitle: () => string;
public getTitle: () => string;
public getListTitle: () => string;
/**
* Returns the verbose name.
@ -42,24 +47,23 @@ export abstract class BaseViewModel implements Displayable, Identifiable, Collec
* @param plural If the name should be plural
* @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 model
*/
public constructor(collectionString: string) {
public constructor(collectionString: string, model: M) {
this._collectionString = collectionString;
this._model = model;
}
public getListTitle: () => string = () => {
return this.getTitle();
};
/** return the main model of a view model */
public abstract getModel(): BaseModel;
/**
* @returns the main underlying model of the view model
*/
public getModel(): M {
return this._model;
}
public abstract updateDependencies(update: BaseViewModel): void;

View File

@ -1,22 +1,23 @@
import { MatTableDataSource, MatTable, MatSort, MatPaginator, MatSnackBar, PageEvent } from '@angular/material';
import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { ViewChild, Type, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs';
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 { BaseFilterListService } from 'app/core/ui-services/base-filter-list.service';
import { BaseModel } from 'app/shared/models/base/base-model';
import { StorageService } from 'app/core/core-services/storage.service';
import { BaseRepository } from 'app/core/repositories/base-repository';
import { Observable } from 'rxjs';
export abstract class ListViewBaseComponent<
V extends BaseViewModel,
M extends BaseModel,
R extends BaseRepository<V, M>
R extends BaseRepository<V, M, TitleInformation>
> extends BaseViewComponent implements OnDestroy {
/**
* 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 {
if (obj) {
return (<Projectable>obj).getSlide !== undefined;
} else {
return false;
}
return !!obj && obj.getSlide !== undefined && obj.getProjectorTitle !== undefined;
}
/**
* Interface for every model, that should be projectable.
*/
export interface Projectable extends Displayable {
getProjectorTitle: () => string;
getSlide(configSerice?: ConfigService): ProjectorElementBuildDeskriptor;
}

View File

@ -32,17 +32,16 @@ interface ConfigConstant {
choices?: ConfigChoice[];
}
export interface ConfigTitleInformation {
key: string;
}
/**
* The view model for configs.
*/
export class ViewConfig extends BaseViewModel {
export class ViewConfig extends BaseViewModel<Config> implements ConfigTitleInformation {
public static COLLECTIONSTRING = Config.COLLECTIONSTRING;
/**
* The underlying config.
*/
private _config: Config;
/* This private members are set by setConstantsInfo. */
private _helpText: string;
private _inputType: ConfigInputType;
@ -60,11 +59,7 @@ export class ViewConfig extends BaseViewModel {
}
public get config(): Config {
return this._config;
}
public get id(): number {
return this.config.id;
return this._model;
}
public get key(): string {
@ -95,26 +90,12 @@ export class ViewConfig extends BaseViewModel {
return this._defaultValue;
}
/**
* This is set by the repository
*/
public getVerboseName;
public constructor(config: Config) {
super(Config.COLLECTIONSTRING);
this._config = config;
super(Config.COLLECTIONSTRING, config);
}
public getTitle = () => {
return this.label;
};
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.
* 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 interface HistoryTitleInformation {
element_id: string;
}
/**
* View model for history objects
*/
export class ViewHistory extends BaseViewModel {
export class ViewHistory extends BaseViewModel<ProxyHistory> implements HistoryTitleInformation {
public static COLLECTIONSTRING = History.COLLECTIONSTRING;
/**
* Private BaseModel of the history
*/
private _history: ProxyHistory;
/**
* Read the history property
*/
public get history(): ProxyHistory {
return this._history;
return this._model;
}
/**
@ -73,11 +72,6 @@ export class ViewHistory extends BaseViewModel {
return this.history.now;
}
/**
* This is set by the repository
*/
public getVerboseName;
/**
* Construction of a ViewHistory
*
@ -85,8 +79,7 @@ export class ViewHistory extends BaseViewModel {
* @param user the real user BaseModel
*/
public constructor(history: ProxyHistory) {
super(History.COLLECTIONSTRING);
this._history = history;
super(History.COLLECTIONSTRING, history);
}
/**
@ -105,19 +98,5 @@ export class ViewHistory extends BaseViewModel {
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 {}
}

View File

@ -169,6 +169,8 @@
</div>
</div>
<os-speaker-button [object]="file" [menuItem]="true"></os-speaker-button>
<!-- Edit and delete for all images -->
<mat-divider></mat-divider>
<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 { SearchRepresentation } from 'app/core/ui-services/search.service';
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 { 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;
private _mediafile: Mediafile;
private _uploader: ViewUser;
public get mediafile(): Mediafile {
return this._mediafile;
return this._model;
}
public get uploader(): ViewUser {
return this._uploader;
}
public get id(): number {
return this.mediafile.id;
}
public get uploader_id(): number {
return this.mediafile.uploader_id;
}
@ -65,25 +66,11 @@ export class ViewMediafile extends BaseProjectableViewModel implements Searchabl
return this.mediafile.hidden;
}
/**
* This is set by the repository
*/
public getVerboseName;
public constructor(mediafile: Mediafile, uploader?: ViewUser) {
super(Mediafile.COLLECTIONSTRING);
this._mediafile = mediafile;
public constructor(mediafile: Mediafile, listOfSpeakers?: ViewListOfSpeakers, uploader?: ViewUser) {
super(Mediafile.COLLECTIONSTRING, mediafile, listOfSpeakers);
this._uploader = uploader;
}
public getTitle = () => {
return this.title;
};
public getModel(): Mediafile {
return this.mediafile;
}
public formatForSearch(): SearchRepresentation {
const searchValues = [this.title];
if (this.uploader) {
@ -167,6 +154,7 @@ export class ViewMediafile extends BaseProjectableViewModel implements Searchabl
}
public updateDependencies(update: BaseViewModel): void {
super.updateDependencies(update);
if (update instanceof ViewUser && this.uploader_id === update.id) {
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 { Searchable } from 'app/site/base/searchable';
export interface CategoryTitleInformation {
prefix: string;
name: string;
}
/**
* 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}
* @ignore
*/
export class ViewCategory extends BaseViewModel implements Searchable {
export class ViewCategory extends BaseViewModel<Category> implements CategoryTitleInformation, Searchable {
public static COLLECTIONSTRING = Category.COLLECTIONSTRING;
private _category: Category;
public get category(): Category {
return this._category;
}
public get id(): number {
return this.category.id;
return this._model;
}
public get name(): string {
@ -31,32 +30,28 @@ export class ViewCategory extends BaseViewModel implements Searchable {
return this.category.prefix;
}
/**
* TODO: Where is this used? Try to avoid this.
*/
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) {
this._category.name = name;
this._model.name = name;
}
public get prefixedName(): string {
return this.prefix ? this.prefix + ' - ' + this.name : this.name;
}
/**
* This is set by the repository
*/
public getVerboseName;
public constructor(category: Category) {
super(Category.COLLECTIONSTRING);
this._category = category;
super(Category.COLLECTIONSTRING, category);
}
public getTitle = () => {
return this.prefixedName;
};
public formatForSearch(): SearchRepresentation {
return [this.name, this.prefix];
}
@ -65,10 +60,6 @@ export class ViewCategory extends BaseViewModel implements Searchable {
return '/motions/category';
}
public getModel(): Category {
return this.category;
}
/**
* Updates the local objects if required
* @param update

View File

@ -6,6 +6,11 @@ import { ViewMotionBlock } from './view-motion-block';
import { ViewItem } from 'app/site/agenda/models/view-item';
import { ViewCategory } from './view-category';
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
@ -14,10 +19,10 @@ import { ViewWorkflow } from './view-workflow';
* @ignore
*/
export class ViewCreateMotion extends ViewMotion {
protected _motion: CreateMotion;
protected _model: CreateMotion;
public get motion(): CreateMotion {
return this._motion;
return this._model;
}
public get submitters(): ViewUser[] {
@ -30,20 +35,43 @@ export class ViewCreateMotion extends ViewMotion {
public set submitters(users: ViewUser[]) {
this._submitters = users;
this._motion.submitters_id = users.map(user => user.id);
this._model.submitters_id = users.map(user => user.id);
}
public constructor(
motion?: CreateMotion,
motion: CreateMotion,
category?: ViewCategory,
submitters?: ViewUser[],
supporters?: ViewUser[],
workflow?: ViewWorkflow,
state?: WorkflowState,
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 = () => {
@ -55,7 +83,7 @@ export class ViewCreateMotion extends ViewMotion {
*/
public copy(): ViewCreateMotion {
return new ViewCreateMotion(
this._motion,
this._model,
this._category,
this._submitters,
this._supporters,

View File

@ -1,52 +1,34 @@
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 { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
import { Searchable } from 'app/site/base/searchable';
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.
* @ignore
*/
export class ViewMotionBlock extends BaseAgendaViewModel implements Searchable {
export class ViewMotionBlock extends BaseViewModelWithAgendaItemAndListOfSpeakers
implements MotionBlockTitleInformation, Searchable {
public static COLLECTIONSTRING = MotionBlock.COLLECTIONSTRING;
private _motionBlock: MotionBlock;
private _agendaItem: ViewItem;
public get motionBlock(): MotionBlock {
return this._motionBlock;
}
public get agendaItem(): ViewItem {
return this._agendaItem;
}
public get id(): number {
return this.motionBlock.id;
return this._model;
}
public get title(): string {
return this.motionBlock.title;
}
public get agenda_item_id(): number {
return this.motionBlock.agenda_item_id;
}
/**
* 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;
public constructor(motionBlock: MotionBlock, agendaItem?: ViewItem, listOfSpeakers?: ViewListOfSpeakers) {
super(MotionBlock.COLLECTIONSTRING, motionBlock, agendaItem, listOfSpeakers);
}
/**
@ -58,10 +40,6 @@ export class ViewMotionBlock extends BaseAgendaViewModel implements Searchable {
return [this.title];
}
public getAgendaItem(): ViewItem {
return this.agendaItem;
}
/**
* Get the URL to the motion block
*
@ -71,20 +49,6 @@ export class ViewMotionBlock extends BaseAgendaViewModel implements Searchable {
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 {
return {
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 { ViewUnifiedChange, ViewUnifiedChangeType } from '../../../shared/models/motions/view-unified-change';
export type MotionChangeRecommendationTitleInformation = object;
/**
* 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}
* @ignore
*/
export class ViewMotionChangeRecommendation extends BaseViewModel implements ViewUnifiedChange {
export class ViewMotionChangeRecommendation extends BaseViewModel<MotionChangeRecommendation>
implements MotionChangeRecommendationTitleInformation, ViewUnifiedChange {
public static COLLECTIONSTRING = MotionChangeRecommendation.COLLECTIONSTRING;
private _changeRecommendation: MotionChangeRecommendation;
public get id(): number {
return this._changeRecommendation.id;
}
public get changeRecommendation(): MotionChangeRecommendation {
return this._changeRecommendation;
return this._model;
}
/**
* This is set by the repository
*/
public getVerboseName;
public constructor(changeReco: MotionChangeRecommendation) {
super(MotionChangeRecommendation.COLLECTIONSTRING);
this._changeRecommendation = changeReco;
public constructor(motionChangeRecommendation: MotionChangeRecommendation) {
super(MotionChangeRecommendation.COLLECTIONSTRING, motionChangeRecommendation);
}
public getTitle = () => {
return 'Change recommendation';
};
public updateDependencies(update: BaseViewModel): void {}
public getModel(): MotionChangeRecommendation {
return this.changeRecommendation;
}
public updateChangeReco(type: number, text: string, internal: boolean): void {
// @TODO HTML sanitazion
this._changeRecommendation.type = type;
this._changeRecommendation.text = text;
this._changeRecommendation.internal = internal;
this.changeRecommendation.type = type;
this.changeRecommendation.text = text;
this.changeRecommendation.internal = internal;
}
public get rejected(): boolean {
return this._changeRecommendation.rejected;
return this.changeRecommendation.rejected;
}
public get internal(): boolean {
return this._changeRecommendation.internal;
return this.changeRecommendation.internal;
}
public get type(): number {
return this._changeRecommendation.type || ModificationType.TYPE_REPLACEMENT;
return this.changeRecommendation.type || ModificationType.TYPE_REPLACEMENT;
}
public get other_description(): string {
return this._changeRecommendation.other_description;
return this.changeRecommendation.other_description;
}
public get line_from(): number {
return this._changeRecommendation.line_from;
return this.changeRecommendation.line_from;
}
public get line_to(): number {
return this._changeRecommendation.line_to;
return this.changeRecommendation.line_to;
}
public get text(): string {
return this._changeRecommendation.text;
return this.changeRecommendation.text;
}
public get motion_id(): number {
return this._changeRecommendation.motion_id;
return this.changeRecommendation.motion_id;
}
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 { ViewGroup } from 'app/site/users/models/view-group';
export interface MotionCommentSectionTitleInformation {
name: string;
}
/**
* 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}
* @ignore
*/
export class ViewMotionCommentSection extends BaseViewModel {
export class ViewMotionCommentSection extends BaseViewModel<MotionCommentSection>
implements MotionCommentSectionTitleInformation {
public static COLLECTIONSTRING = MotionCommentSection.COLLECTIONSTRING;
private _section: MotionCommentSection;
private _readGroups: ViewGroup[];
private _writeGroups: ViewGroup[];
public get section(): MotionCommentSection {
return this._section;
return this._model;
}
public get id(): number {
@ -45,30 +48,19 @@ export class ViewMotionCommentSection extends BaseViewModel {
return this._writeGroups;
}
/**
* TODO: Where is this needed? Try to avoid this.
*/
public set name(name: string) {
this._section.name = name;
this._model.name = name;
}
/**
* This is set by the repository
*/
public getVerboseName;
public constructor(section: MotionCommentSection, readGroups: ViewGroup[], writeGroups: ViewGroup[]) {
super(MotionCommentSection.COLLECTIONSTRING);
this._section = section;
public constructor(motionCommentSection: MotionCommentSection, readGroups: ViewGroup[], writeGroups: ViewGroup[]) {
super(MotionCommentSection.COLLECTIONSTRING, motionCommentSection);
this._readGroups = readGroups;
this._writeGroups = writeGroups;
}
public getTitle = () => {
return this.name;
};
public getModel(): MotionCommentSection {
return this.section;
}
/**
* Updates the local objects if required
* @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 { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
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 { ViewUser } from 'app/site/users/models/view-user';
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 { BaseViewModel } from 'app/site/base/base-view-model';
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 { ViewMotionChangeRecommendation } from './view-motion-change-recommendation';
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.
@ -40,6 +42,11 @@ export enum ChangeRecoMode {
ModifiedFinal = 'modified_final_version'
}
export interface MotionTitleInformation extends TitleInformationWithAgendaItem {
title: string;
identifier?: string;
}
/**
* Motion class for the View
*
@ -47,30 +54,65 @@ export enum ChangeRecoMode {
* Provides "safe" access to variables and functions in {@link Motion}
* @ignore
*/
export class ViewMotion extends BaseAgendaViewModel implements Searchable {
export class ViewMotion extends BaseViewModelWithAgendaItemAndListOfSpeakers<Motion>
implements MotionTitleInformation, Searchable {
public static COLLECTIONSTRING = Motion.COLLECTIONSTRING;
protected _motion: Motion;
protected _category: ViewCategory;
protected _submitters: ViewUser[];
protected _supporters: ViewUser[];
protected _workflow: ViewWorkflow;
protected _state: WorkflowState;
protected _item: ViewItem;
protected _block: ViewMotionBlock;
protected _attachments: ViewMediafile[];
protected _tags: ViewTag[];
protected _parent: ViewMotion;
protected _amendments: ViewMotion[];
protected _changeRecommendations: ViewMotionChangeRecommendation[];
protected _category?: ViewCategory;
protected _submitters?: ViewUser[];
protected _supporters?: ViewUser[];
protected _workflow?: ViewWorkflow;
protected _state?: WorkflowState;
protected _block?: ViewMotionBlock;
protected _attachments?: ViewMediafile[];
protected _tags?: ViewTag[];
protected _parent?: ViewMotion;
protected _amendments?: ViewMotion[];
protected _changeRecommendations?: ViewMotionChangeRecommendation[];
public personalNote?: PersonalNoteContent;
public get motion(): Motion {
return this._motion;
return this._model;
}
public get id(): number {
return this.motion.id;
public get category(): ViewCategory | null {
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 {
@ -111,43 +153,22 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
return this.motion.sort_parent_id;
}
public get agenda_item_id(): number {
return this.motion.agenda_item_id;
}
public get category_id(): number {
return this.motion.category_id;
}
public get category(): ViewCategory {
return this._category;
}
public get category_weight(): number {
return this.motion.category_weight;
}
public get submitters(): ViewUser[] {
return this._submitters;
}
public get sorted_submitters_id(): number[] {
return this.motion.sorted_submitters_id;
}
public get supporters(): ViewUser[] {
return this._supporters;
}
public get supporters_id(): number[] {
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 {
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) : [];
}
public get item(): ViewItem {
return this._item;
}
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 {
return this.motion.motion_block_id;
}
public get motion_block(): ViewMotionBlock {
return this._block;
}
public get agendaSpeakerAmount(): number {
return this.item ? this.item.waitingSpeakerAmount : null;
public get speakerAmount(): number {
return this.listOfSpeakers ? this.listOfSpeakers.waitingSpeakerAmount : null;
}
public get parent_id(): number {
@ -243,22 +256,6 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
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
*/
@ -347,31 +344,9 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
return StateCssClassMapping[this.state.css_class] || '';
}
/**
* This is set by the repository
*/
public getTitle: () => string;
/**
* This is set by the repository
*/
// This is set by the repository
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(
motion: Motion,
category?: ViewCategory,
@ -380,6 +355,7 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
workflow?: ViewWorkflow,
state?: WorkflowState,
item?: ViewItem,
listOfSpeakers?: ViewListOfSpeakers,
block?: ViewMotionBlock,
attachments?: ViewMediafile[],
tags?: ViewTag[],
@ -388,14 +364,12 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
amendments?: ViewMotion[],
personalNote?: PersonalNoteContent
) {
super(Motion.COLLECTIONSTRING);
this._motion = motion;
super(Motion.COLLECTIONSTRING, motion, item, listOfSpeakers);
this._category = category;
this._submitters = submitters;
this._supporters = supporters;
this._workflow = workflow;
this._state = state;
this._item = item;
this._block = block;
this._attachments = attachments;
this._tags = tags;
@ -405,14 +379,6 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
this.personalNote = personalNote;
}
public getAgendaItem(): ViewItem {
return this.item;
}
public getModel(): Motion {
return this.motion;
}
/**
* Formats the category for search
*
@ -461,12 +427,11 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
* @param update
*/
public updateDependencies(update: BaseViewModel): void {
super.updateDependencies(update);
if (update instanceof ViewWorkflow) {
this.updateWorkflow(update);
} else if (update instanceof ViewCategory) {
this.updateCategory(update);
} else if (update instanceof ViewItem) {
this.updateItem(update);
} else if (update instanceof ViewMotionBlock) {
this.updateMotionBlock(update);
} 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
*
@ -532,7 +486,7 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
/**
* 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 {
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,
projectionDefaultName: 'motions',
getDialogTitle: this.getAgendaTitle
getDialogTitle: this.getAgendaSlideTitle
};
}
@ -674,13 +628,14 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
*/
public copy(): ViewMotion {
return new ViewMotion(
this._motion,
this._model,
this._category,
this._submitters,
this._supporters,
this._workflow,
this._state,
this._item,
this._listOfSpeakers,
this._block,
this._attachments,
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 { SearchRepresentation } from 'app/core/ui-services/search.service';
export interface StatuteParagraphTitleInformation {
title: string;
}
/**
* 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}
* @ignore
*/
export class ViewStatuteParagraph extends BaseViewModel implements Searchable {
export class ViewStatuteParagraph extends BaseViewModel<StatuteParagraph>
implements StatuteParagraphTitleInformation, Searchable {
public static COLLECTIONSTRING = StatuteParagraph.COLLECTIONSTRING;
private _paragraph: StatuteParagraph;
public get statuteParagraph(): StatuteParagraph {
return this._paragraph;
}
public get id(): number {
return this.statuteParagraph.id;
return this._model;
}
public get title(): string {
@ -35,22 +34,8 @@ export class ViewStatuteParagraph extends BaseViewModel implements Searchable {
return this.statuteParagraph.weight;
}
/**
* This is set by the repository
*/
public getVerboseName;
public constructor(paragraph: StatuteParagraph) {
super(StatuteParagraph.COLLECTIONSTRING);
this._paragraph = paragraph;
}
public getTitle = () => {
return this.title;
};
public getModel(): StatuteParagraph {
return this.statuteParagraph;
public constructor(statuteParagraph: StatuteParagraph) {
super(StatuteParagraph.COLLECTIONSTRING, statuteParagraph);
}
public formatForSearch(): SearchRepresentation {

View File

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

View File

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

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