Merge pull request #4866 from FinnStutzenstein/RepoUpdate
Generic relations for the repos
This commit is contained in:
commit
1b26c03ef9
@ -20,7 +20,7 @@ type TypeIdentifier = UnifiedConstructors | BaseRepository<any, any, any> | stri
|
||||
type CollectionStringMappedTypes = [
|
||||
ModelConstructor<BaseModel>,
|
||||
ViewModelConstructor<BaseViewModel>,
|
||||
BaseRepository<BaseViewModel, BaseModel, TitleInformation>
|
||||
BaseRepository<BaseViewModel<any>, BaseModel<any>, TitleInformation>
|
||||
];
|
||||
|
||||
/**
|
||||
@ -46,7 +46,7 @@ export class CollectionStringMapperService {
|
||||
* @param collectionString
|
||||
* @param model
|
||||
*/
|
||||
public registerCollectionElement<V extends BaseViewModel, M extends BaseModel>(
|
||||
public registerCollectionElement<V extends BaseViewModel<M>, M extends BaseModel>(
|
||||
collectionString: string,
|
||||
model: ModelConstructor<M>,
|
||||
viewModel: ViewModelConstructor<V>,
|
||||
|
@ -16,14 +16,23 @@ import {
|
||||
IBaseViewModelWithAgendaItem
|
||||
} 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 { 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';
|
||||
import { BaseHasContentObjectRepository, GenericRelationDefinition } from '../base-has-content-object-repository';
|
||||
import { Identifiable } from 'app/shared/models/base/identifiable';
|
||||
import { RelationDefinition } from '../base-repository';
|
||||
import { ViewMotion } from 'app/site/motions/models/view-motion';
|
||||
import { ViewMotionBlock } from 'app/site/motions/models/view-motion-block';
|
||||
import { ViewTopic } from 'app/site/topics/models/view-topic';
|
||||
import { ViewAssignment } from 'app/site/assignments/models/view-assignment';
|
||||
|
||||
const ItemRelations: (RelationDefinition | GenericRelationDefinition)[] = [
|
||||
{
|
||||
type: 'generic',
|
||||
possibleModels: [ViewMotion, ViewMotionBlock, ViewTopic, ViewAssignment],
|
||||
isVForeign: isBaseViewModelWithAgendaItem,
|
||||
VForeignVerbose: 'BaseViewModelWithAgendaItem'
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Repository service for items
|
||||
@ -58,12 +67,7 @@ export class ItemRepositoryService extends BaseHasContentObjectRepository<
|
||||
private httpService: HttpService,
|
||||
private config: ConfigService
|
||||
) {
|
||||
super(DS, dataSend, mapperService, viewModelStoreService, translate, Item, [
|
||||
Topic,
|
||||
Assignment,
|
||||
Motion,
|
||||
MotionBlock
|
||||
]);
|
||||
super(DS, dataSend, mapperService, viewModelStoreService, translate, Item, ItemRelations);
|
||||
|
||||
this.setSortFunction((a, b) => a.weight - b.weight);
|
||||
}
|
||||
@ -83,34 +87,6 @@ export class ItemRepositoryService extends BaseHasContentObjectRepository<
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the viewItem out of a given item
|
||||
*
|
||||
* @param item the item that should be converted to view item
|
||||
* @returns a new view item
|
||||
*/
|
||||
public createViewModel(item: Item): ViewItem {
|
||||
const contentObject = this.getContentObject(item);
|
||||
return new ViewItem(item, contentObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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): BaseViewModelWithAgendaItem {
|
||||
const contentObject = this.viewModelStoreService.get<BaseViewModel>(
|
||||
agendaItem.content_object.collection,
|
||||
agendaItem.content_object.id
|
||||
);
|
||||
if (!contentObject || !isBaseViewModelWithAgendaItem(contentObject)) {
|
||||
return null;
|
||||
}
|
||||
return contentObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger the automatic numbering sequence on the server
|
||||
*/
|
||||
|
@ -12,20 +12,42 @@ 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 { BaseHasContentObjectRepository, GenericRelationDefinition } from '../base-has-content-object-repository';
|
||||
import { ItemRepositoryService } from './item-repository.service';
|
||||
import { User } from 'app/shared/models/users/user';
|
||||
import { ViewMotion } from 'app/site/motions/models/view-motion';
|
||||
import { RelationDefinition } from '../base-repository';
|
||||
import { ViewMotionBlock } from 'app/site/motions/models/view-motion-block';
|
||||
import { ViewTopic } from 'app/site/topics/models/view-topic';
|
||||
import { ViewAssignment } from 'app/site/assignments/models/view-assignment';
|
||||
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
|
||||
import { ViewUser } from 'app/site/users/models/view-user';
|
||||
|
||||
const ListOfSpeakersRelations: (RelationDefinition | GenericRelationDefinition)[] = [
|
||||
{
|
||||
type: 'generic',
|
||||
possibleModels: [ViewMotion, ViewMotionBlock, ViewTopic, ViewAssignment, ViewMediafile],
|
||||
isVForeign: isBaseViewModelWithListOfSpeakers,
|
||||
VForeignVerbose: 'BaseViewModelWithListOfSpeakers'
|
||||
},
|
||||
{
|
||||
type: 'nested',
|
||||
ownKey: 'speakers',
|
||||
foreignModel: ViewSpeaker,
|
||||
order: 'weight',
|
||||
relationDefinition: [
|
||||
{
|
||||
type: 'O2M',
|
||||
ownIdKey: 'user_id',
|
||||
ownKey: 'user',
|
||||
foreignModel: ViewUser
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Repository service for lists of speakers
|
||||
@ -60,14 +82,7 @@ export class ListOfSpeakersRepositoryService extends BaseHasContentObjectReposit
|
||||
private httpService: HttpService,
|
||||
private itemRepo: ItemRepositoryService
|
||||
) {
|
||||
super(DS, dataSend, mapperService, viewModelStoreService, translate, ListOfSpeakers, [
|
||||
Topic,
|
||||
Assignment,
|
||||
Motion,
|
||||
MotionBlock,
|
||||
Mediafile,
|
||||
User
|
||||
]);
|
||||
super(DS, dataSend, mapperService, viewModelStoreService, translate, ListOfSpeakers, ListOfSpeakersRelations);
|
||||
}
|
||||
|
||||
public getVerboseName = (plural: boolean = false) => {
|
||||
@ -93,48 +108,6 @@ export class ListOfSpeakersRepositoryService extends BaseHasContentObjectReposit
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -3,26 +3,71 @@ import { Injectable } from '@angular/core';
|
||||
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 { 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 { 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, 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';
|
||||
import { ViewUser } from 'app/site/users/models/view-user';
|
||||
import { ViewAssignmentRelatedUser } from 'app/site/assignments/models/view-assignment-related-user';
|
||||
import { ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-poll';
|
||||
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';
|
||||
import { RelationDefinition } from '../base-repository';
|
||||
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
|
||||
import { ViewAssignmentPollOption } from 'app/site/assignments/models/view-assignment-poll-option';
|
||||
|
||||
const AssignmentRelations: RelationDefinition[] = [
|
||||
{
|
||||
type: 'M2M',
|
||||
ownIdKey: 'tags_id',
|
||||
ownKey: 'tags',
|
||||
foreignModel: ViewTag
|
||||
},
|
||||
{
|
||||
type: 'M2M',
|
||||
ownIdKey: 'attachments_id',
|
||||
ownKey: 'attachments',
|
||||
foreignModel: ViewMediafile
|
||||
},
|
||||
{
|
||||
type: 'nested',
|
||||
ownKey: 'assignment_related_users',
|
||||
foreignModel: ViewAssignmentRelatedUser,
|
||||
order: 'weight',
|
||||
relationDefinition: [
|
||||
{
|
||||
type: 'O2M',
|
||||
ownIdKey: 'user_id',
|
||||
ownKey: 'user',
|
||||
foreignModel: ViewUser
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'nested',
|
||||
ownKey: 'polls',
|
||||
foreignModel: ViewAssignmentPoll,
|
||||
relationDefinition: [
|
||||
{
|
||||
type: 'nested',
|
||||
ownKey: 'options',
|
||||
foreignModel: ViewAssignmentPollOption,
|
||||
order: 'weight',
|
||||
relationDefinition: [
|
||||
{
|
||||
type: 'O2M',
|
||||
ownIdKey: 'user_id',
|
||||
ownKey: 'user',
|
||||
foreignModel: ViewUser
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Repository Service for Assignments.
|
||||
@ -62,7 +107,7 @@ export class AssignmentRepositoryService extends BaseIsAgendaItemAndListOfSpeake
|
||||
protected translate: TranslateService,
|
||||
private httpService: HttpService
|
||||
) {
|
||||
super(DS, dataSend, mapperService, viewModelStoreService, translate, Assignment, [User, Tag, Mediafile]);
|
||||
super(DS, dataSend, mapperService, viewModelStoreService, translate, Assignment, AssignmentRelations);
|
||||
}
|
||||
|
||||
public getTitle = (titleInformation: AssignmentTitleInformation) => {
|
||||
@ -73,48 +118,6 @@ export class AssignmentRepositoryService extends BaseIsAgendaItemAndListOfSpeake
|
||||
return this.translate.instant(plural ? 'Elections' : 'Election');
|
||||
};
|
||||
|
||||
public createViewModel(assignment: Assignment): ViewAssignment {
|
||||
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);
|
||||
|
||||
return new ViewAssignment(
|
||||
assignment,
|
||||
assignmentRelatedUsers,
|
||||
assignmentPolls,
|
||||
item,
|
||||
listOfSpeakers,
|
||||
tags,
|
||||
attachments
|
||||
);
|
||||
}
|
||||
|
||||
private createViewAssignmentRelatedUsers(
|
||||
assignmentRelatedUsers: AssignmentRelatedUser[]
|
||||
): ViewAssignmentRelatedUser[] {
|
||||
return assignmentRelatedUsers
|
||||
.map(aru => {
|
||||
const user = this.viewModelStoreService.get(ViewUser, aru.user_id);
|
||||
return new ViewAssignmentRelatedUser(aru, user);
|
||||
})
|
||||
.sort((a, b) => a.weight - b.weight);
|
||||
}
|
||||
|
||||
private createViewAssignmentPolls(assignmentPolls: AssignmentPoll[]): ViewAssignmentPoll[] {
|
||||
return assignmentPolls.map(poll => {
|
||||
const options = poll.options
|
||||
.map(option => {
|
||||
const user = this.viewModelStoreService.get(ViewUser, option.candidate_id);
|
||||
return new ViewAssignmentPollOption(option, user);
|
||||
})
|
||||
.sort((a, b) => a.weight - b.weight);
|
||||
return new ViewAssignmentPoll(poll, options);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds/removes another user to/from the candidates list of an assignment
|
||||
*
|
||||
|
@ -1,8 +1,24 @@
|
||||
import { BaseRepository } from './base-repository';
|
||||
import { BaseRepository, RelationDefinition } 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';
|
||||
import { BaseViewModel, TitleInformation, ViewModelConstructor } from 'app/site/base/base-view-model';
|
||||
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 { TranslateService } from '@ngx-translate/core';
|
||||
import { ModelConstructor } from 'app/shared/models/base/base-model';
|
||||
|
||||
/**
|
||||
* A generic relation for models with a content_object
|
||||
*/
|
||||
export interface GenericRelationDefinition<VForeign extends BaseViewModel = BaseViewModel> {
|
||||
type: 'generic';
|
||||
possibleModels: ViewModelConstructor<BaseViewModel>[];
|
||||
isVForeign: (obj: any) => obj is VForeign;
|
||||
VForeignVerbose: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A base repository for objects that *have* content objects, e.g. items and lists of speakers.
|
||||
@ -23,6 +39,98 @@ export abstract class BaseHasContentObjectRepository<
|
||||
};
|
||||
} = {};
|
||||
|
||||
public constructor(
|
||||
DS: DataStoreService,
|
||||
dataSend: DataSendService,
|
||||
collectionStringMapperService: CollectionStringMapperService,
|
||||
viewModelStoreService: ViewModelStoreService,
|
||||
translate: TranslateService,
|
||||
baseModelCtor: ModelConstructor<M>,
|
||||
relationDefinitions: (RelationDefinition | GenericRelationDefinition)[] = []
|
||||
) {
|
||||
super(DS, dataSend, collectionStringMapperService, viewModelStoreService, translate, baseModelCtor, <
|
||||
RelationDefinition[]
|
||||
>relationDefinitions); // This cast "hides" the new generic relation from the base repository. Typescript can't handle this...
|
||||
}
|
||||
|
||||
protected _groupRelationsByCollections(
|
||||
relation: RelationDefinition | GenericRelationDefinition,
|
||||
baseRelation: RelationDefinition
|
||||
): void {
|
||||
if (relation.type === 'generic') {
|
||||
relation.possibleModels.forEach(ctor => {
|
||||
const collection = ctor.COLLECTIONSTRING;
|
||||
if (!this.relationsByCollection[collection]) {
|
||||
this.relationsByCollection[collection] = [];
|
||||
}
|
||||
// The cast to any is needed to convince Typescript, that a GenericRelationDefinition can also
|
||||
// be used as a RelationDefinition
|
||||
this.relationsByCollection[collection].push(<any>baseRelation);
|
||||
});
|
||||
} else {
|
||||
super._groupRelationsByCollections(relation, baseRelation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the generic relation.
|
||||
*/
|
||||
protected updateSingleDependency(
|
||||
ownViewModel: V,
|
||||
relation: RelationDefinition | GenericRelationDefinition,
|
||||
collection: string,
|
||||
changedId: number
|
||||
): boolean {
|
||||
if (relation.type === 'generic') {
|
||||
const foreignModel = <any>this.viewModelStoreService.get(collection, changedId);
|
||||
if (
|
||||
foreignModel &&
|
||||
foreignModel.collectionString === ownViewModel.contentObjectData.collection &&
|
||||
foreignModel.id === ownViewModel.contentObjectData.id
|
||||
) {
|
||||
if (relation.isVForeign(foreignModel)) {
|
||||
(<any>ownViewModel)._contentObject = foreignModel;
|
||||
return true;
|
||||
} else {
|
||||
throw new Error(`The object is not an ${relation.VForeignVerbose}:` + foreignModel);
|
||||
}
|
||||
|
||||
// TODO: set reverse
|
||||
}
|
||||
} else {
|
||||
super.updateSingleDependency(ownViewModel, relation, collection, changedId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the generic relation.
|
||||
*/
|
||||
protected setRelationsInViewModel<K extends BaseViewModel = V>(
|
||||
model: M,
|
||||
viewModel: K,
|
||||
relation: RelationDefinition | GenericRelationDefinition
|
||||
): void {
|
||||
if (relation.type === 'generic') {
|
||||
(<any>viewModel)._contentObject = this.getContentObject(model, relation);
|
||||
} else {
|
||||
super.setRelationsInViewModel(model, viewModel, relation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to get the content object (as a view model) from the given model and relation.
|
||||
*/
|
||||
protected getContentObject(model: M, relation: GenericRelationDefinition): BaseViewModel {
|
||||
const contentObject = this.viewModelStoreService.get<BaseViewModel>(
|
||||
model.content_object.collection,
|
||||
model.content_object.id
|
||||
);
|
||||
if (!contentObject || !relation.isVForeign(contentObject)) {
|
||||
return null;
|
||||
}
|
||||
return contentObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object with has the given content object as the content object.
|
||||
*
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { BaseModel, ModelConstructor } from '../../shared/models/base/base-model';
|
||||
import { BaseRepository } from './base-repository';
|
||||
import { BaseRepository, RelationDefinition } from './base-repository';
|
||||
import {
|
||||
isBaseIsAgendaItemContentObjectRepository,
|
||||
IBaseIsAgendaItemContentObjectRepository
|
||||
@ -13,8 +13,6 @@ import {
|
||||
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,
|
||||
@ -22,6 +20,8 @@ import {
|
||||
} 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';
|
||||
import { ViewItem } from 'app/site/agenda/models/view-item';
|
||||
import { ViewListOfSpeakers } from 'app/site/agenda/models/view-list-of-speakers';
|
||||
|
||||
export function isBaseIsAgendaItemAndListOfSpeakersContentObjectRepository(
|
||||
obj: any
|
||||
@ -50,7 +50,7 @@ export abstract class BaseIsAgendaItemAndListOfSpeakersContentObjectRepository<
|
||||
viewModelStoreService: ViewModelStoreService,
|
||||
translate: TranslateService,
|
||||
baseModelCtor: ModelConstructor<M>,
|
||||
depsModelCtors?: ModelConstructor<BaseModel>[]
|
||||
relationDefinitions?: RelationDefinition[]
|
||||
) {
|
||||
super(
|
||||
DS,
|
||||
@ -59,13 +59,24 @@ export abstract class BaseIsAgendaItemAndListOfSpeakersContentObjectRepository<
|
||||
viewModelStoreService,
|
||||
translate,
|
||||
baseModelCtor,
|
||||
depsModelCtors
|
||||
relationDefinitions
|
||||
);
|
||||
if (!this.depsModelCtors) {
|
||||
this.depsModelCtors = [];
|
||||
}
|
||||
this.depsModelCtors.push(Item);
|
||||
this.depsModelCtors.push(ListOfSpeakers);
|
||||
}
|
||||
|
||||
protected groupRelationsByCollections(): void {
|
||||
this.relationDefinitions.push({
|
||||
type: 'O2M',
|
||||
ownIdKey: 'agenda_item_id',
|
||||
ownKey: 'item',
|
||||
foreignModel: ViewItem
|
||||
});
|
||||
this.relationDefinitions.push({
|
||||
type: 'O2M',
|
||||
ownIdKey: 'list_of_speakers_id',
|
||||
ownKey: 'list_of_speakers',
|
||||
foreignModel: ViewListOfSpeakers
|
||||
});
|
||||
super.groupRelationsByCollections();
|
||||
}
|
||||
|
||||
public getAgendaListTitle(titleInformation: T): string {
|
||||
|
@ -3,14 +3,14 @@ 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 { BaseRepository, RelationDefinition } from './base-repository';
|
||||
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';
|
||||
import { ViewItem } from 'app/site/agenda/models/view-item';
|
||||
|
||||
export function isBaseIsAgendaItemContentObjectRepository(
|
||||
obj: any
|
||||
@ -46,7 +46,7 @@ export abstract class BaseIsAgendaItemContentObjectRepository<
|
||||
viewModelStoreService: ViewModelStoreService,
|
||||
translate: TranslateService,
|
||||
baseModelCtor: ModelConstructor<M>,
|
||||
depsModelCtors?: ModelConstructor<BaseModel>[]
|
||||
relationDefinitions?: RelationDefinition[]
|
||||
) {
|
||||
super(
|
||||
DS,
|
||||
@ -55,12 +55,18 @@ export abstract class BaseIsAgendaItemContentObjectRepository<
|
||||
viewModelStoreService,
|
||||
translate,
|
||||
baseModelCtor,
|
||||
depsModelCtors
|
||||
relationDefinitions
|
||||
);
|
||||
if (!this.depsModelCtors) {
|
||||
this.depsModelCtors = [];
|
||||
}
|
||||
this.depsModelCtors.push(Item);
|
||||
}
|
||||
|
||||
protected groupRelationsByCollections(): void {
|
||||
this.relationDefinitions.push({
|
||||
type: 'O2M',
|
||||
ownIdKey: 'agenda_item_id',
|
||||
ownKey: 'item',
|
||||
foreignModel: ViewItem
|
||||
});
|
||||
super.groupRelationsByCollections();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,13 +2,13 @@ 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 { BaseRepository, RelationDefinition } 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';
|
||||
import { ViewListOfSpeakers } from 'app/site/agenda/models/view-list-of-speakers';
|
||||
|
||||
export function isBaseIsListOfSpeakersContentObjectRepository(
|
||||
obj: any
|
||||
@ -44,7 +44,7 @@ export abstract class BaseIsListOfSpeakersContentObjectRepository<
|
||||
viewModelStoreService: ViewModelStoreService,
|
||||
translate: TranslateService,
|
||||
baseModelCtor: ModelConstructor<M>,
|
||||
depsModelCtors?: ModelConstructor<BaseModel>[]
|
||||
relationDefinitions?: RelationDefinition[]
|
||||
) {
|
||||
super(
|
||||
DS,
|
||||
@ -53,12 +53,18 @@ export abstract class BaseIsListOfSpeakersContentObjectRepository<
|
||||
viewModelStoreService,
|
||||
translate,
|
||||
baseModelCtor,
|
||||
depsModelCtors
|
||||
relationDefinitions
|
||||
);
|
||||
if (!this.depsModelCtors) {
|
||||
this.depsModelCtors = [];
|
||||
}
|
||||
this.depsModelCtors.push(ListOfSpeakers);
|
||||
}
|
||||
|
||||
protected groupRelationsByCollections(): void {
|
||||
this.relationDefinitions.push({
|
||||
type: 'O2M',
|
||||
ownIdKey: 'list_of_speakers_id',
|
||||
ownKey: 'list_of_speakers',
|
||||
foreignModel: ViewListOfSpeakers
|
||||
});
|
||||
super.groupRelationsByCollections();
|
||||
}
|
||||
|
||||
public getListOfSpeakersTitle(titleInformation: T): string {
|
||||
|
@ -2,7 +2,7 @@ import { BehaviorSubject, Observable, Subject } from 'rxjs';
|
||||
import { auditTime } from 'rxjs/operators';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { BaseViewModel, TitleInformation } from '../../site/base/base-view-model';
|
||||
import { BaseViewModel, TitleInformation, ViewModelConstructor } 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';
|
||||
@ -12,6 +12,89 @@ import { ViewModelStoreService } from '../core-services/view-model-store.service
|
||||
import { OnAfterAppsLoaded } from '../onAfterAppsLoaded';
|
||||
import { Collection } from 'app/shared/models/base/collection';
|
||||
|
||||
// All "standard" relations.
|
||||
export type RelationDefinition<VForeign extends BaseViewModel = BaseViewModel> =
|
||||
| NormalRelationDefinition<VForeign>
|
||||
| NestedRelationDefinition<VForeign>
|
||||
| CustomRelationDefinition<VForeign>;
|
||||
|
||||
/**
|
||||
* Normal relations.
|
||||
*/
|
||||
interface NormalRelationDefinition<VForeign extends BaseViewModel> {
|
||||
/**
|
||||
* - O2M: From this model to another one, where this model is the One-side.
|
||||
* E.g. motions<->categories: One motions has One category; One category has
|
||||
* Many motions
|
||||
* - M2M: M2M relation from this to another model.
|
||||
*/
|
||||
type: 'M2M' | 'O2M';
|
||||
|
||||
/**
|
||||
* The key where the id(s) are given. Must be present in the model and view model. E.g. `category_id`.
|
||||
*/
|
||||
ownIdKey: string;
|
||||
|
||||
/**
|
||||
* The name of the property, where the foreign view model should be accessable.
|
||||
* Note, that this must be a getter to a private variable `_<ownKey`!
|
||||
*
|
||||
* E.g. `category`. (private variable `_category`)
|
||||
*/
|
||||
ownKey: string;
|
||||
|
||||
/**
|
||||
* The model on the other side of the relation.
|
||||
*/
|
||||
foreignModel: ViewModelConstructor<VForeign>;
|
||||
|
||||
/**
|
||||
* TODO: reverse relations.
|
||||
*/
|
||||
foreignKey?: keyof VForeign;
|
||||
}
|
||||
|
||||
/**
|
||||
* Nested relations in the REST-API. For the most values see
|
||||
* `NormalRelationDefinition`.
|
||||
*/
|
||||
interface NestedRelationDefinition<VForeign extends BaseViewModel> {
|
||||
type: 'nested';
|
||||
ownKey: string;
|
||||
foreignModel: ViewModelConstructor<VForeign>;
|
||||
foreignKey?: keyof VForeign;
|
||||
|
||||
/**
|
||||
* The nested relations.
|
||||
*/
|
||||
relationDefinition?: RelationDefinition[];
|
||||
|
||||
/**
|
||||
* Provide an extra key (holding a number) to order by.
|
||||
* If the value is equal or no order key is given, the models
|
||||
* will be sorted by id.
|
||||
*/
|
||||
order?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom relation with callbacks with things todo.
|
||||
*/
|
||||
interface CustomRelationDefinition<VForeign extends BaseViewModel> {
|
||||
type: 'custom';
|
||||
foreignModel: ViewModelConstructor<VForeign>;
|
||||
|
||||
/**
|
||||
* Called, when the view model is created from the model.
|
||||
*/
|
||||
setRelations: (model: BaseModel, viewModel: BaseViewModel) => void;
|
||||
|
||||
/**
|
||||
* Called, when the dependency was updated.
|
||||
*/
|
||||
updateDependency: (ownViewModel: BaseViewModel, foreignViewModel: VForeign) => boolean;
|
||||
}
|
||||
|
||||
export abstract class BaseRepository<V extends BaseViewModel & T, M extends BaseModel, T extends TitleInformation>
|
||||
implements OnAfterAppsLoaded, Collection {
|
||||
/**
|
||||
@ -71,6 +154,20 @@ export abstract class BaseRepository<V extends BaseViewModel & T, M extends Base
|
||||
public abstract getVerboseName: (plural?: boolean) => string;
|
||||
public abstract getTitle: (titleInformation: T) => string;
|
||||
|
||||
/**
|
||||
* Maps the given relations (`relationDefinitions`) to their affected collections. This means,
|
||||
* if a model of the collection updates, the relation needs to be updated.
|
||||
*
|
||||
* Attention: Some inherited repos might put other relations than RelationDefinition in here, so
|
||||
* *always* check the type of the relation.
|
||||
*/
|
||||
protected relationsByCollection: { [collection: string]: RelationDefinition<BaseViewModel>[] } = {};
|
||||
|
||||
/**
|
||||
* The view model ctor of the encapsulated view model.
|
||||
*/
|
||||
protected baseViewModelCtor: ViewModelConstructor<V>;
|
||||
|
||||
/**
|
||||
* Construction routine for the base repository
|
||||
*
|
||||
@ -87,10 +184,12 @@ export abstract class BaseRepository<V extends BaseViewModel & T, M extends Base
|
||||
protected viewModelStoreService: ViewModelStoreService,
|
||||
protected translate: TranslateService,
|
||||
protected baseModelCtor: ModelConstructor<M>,
|
||||
protected depsModelCtors?: ModelConstructor<BaseModel>[]
|
||||
protected relationDefinitions: RelationDefinition<BaseViewModel>[] = []
|
||||
) {
|
||||
this._collectionString = baseModelCtor.COLLECTIONSTRING;
|
||||
|
||||
this.groupRelationsByCollections();
|
||||
|
||||
// All data is piped through an auditTime of 1ms. This is to prevent massive
|
||||
// updates, if e.g. an autoupdate with a lot motions come in. The result is just one
|
||||
// update of the new list instead of many unnecessary updates.
|
||||
@ -103,7 +202,34 @@ export abstract class BaseRepository<V extends BaseViewModel & T, M extends Base
|
||||
this.languageCollator = new Intl.Collator(this.translate.currentLang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorders the relations to provide faster access.
|
||||
*/
|
||||
protected groupRelationsByCollections(): void {
|
||||
this.relationDefinitions.forEach(relation => {
|
||||
this._groupRelationsByCollections(relation, relation);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive function for reorderung the relations.
|
||||
*/
|
||||
protected _groupRelationsByCollections(relation: RelationDefinition, baseRelation: RelationDefinition): void {
|
||||
if (relation.type === 'nested') {
|
||||
(relation.relationDefinition || []).forEach(nestedRelation => {
|
||||
this._groupRelationsByCollections(nestedRelation, baseRelation);
|
||||
});
|
||||
} else if (relation.type === 'O2M' || relation.type === 'M2M' || relation.type === 'custom') {
|
||||
const collection = relation.foreignModel.COLLECTIONSTRING;
|
||||
if (!this.relationsByCollection[collection]) {
|
||||
this.relationsByCollection[collection] = [];
|
||||
}
|
||||
this.relationsByCollection[collection].push(baseRelation);
|
||||
}
|
||||
}
|
||||
|
||||
public onAfterAppsLoaded(): void {
|
||||
this.baseViewModelCtor = this.collectionStringMapperService.getViewModelConstructor(this.collectionString);
|
||||
this.DS.clearObservable.subscribe(() => this.clear());
|
||||
this.translate.onLangChange.subscribe(change => {
|
||||
this.languageCollator = new Intl.Collator(change.lang);
|
||||
@ -113,6 +239,10 @@ export abstract class BaseRepository<V extends BaseViewModel & T, M extends Base
|
||||
});
|
||||
}
|
||||
|
||||
public getListTitle: (titleInformation: T) => string = (titleInformation: T) => {
|
||||
return this.getTitle(titleInformation);
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes all models from the repository (internally, no requests). Changes need
|
||||
* to be committed via `commitUpdate()`.
|
||||
@ -139,6 +269,73 @@ export abstract class BaseRepository<V extends BaseViewModel & T, M extends Base
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, this.baseViewModelCtor, this.relationDefinitions);
|
||||
viewModel.getTitle = () => this.getTitle(viewModel);
|
||||
viewModel.getListTitle = () => this.getListTitle(viewModel);
|
||||
viewModel.getVerboseName = this.getVerboseName;
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a view model from the given model and model ctor. All dependencies will be
|
||||
* set accorting to relations.
|
||||
*/
|
||||
protected createViewModel<K extends BaseViewModel = V>(
|
||||
model: M,
|
||||
modelCtor: ViewModelConstructor<K>,
|
||||
relations: RelationDefinition[]
|
||||
): K {
|
||||
const viewModel = new modelCtor(model) as K;
|
||||
|
||||
// no reverse setting needed
|
||||
relations.forEach(relation => {
|
||||
this.setRelationsInViewModel(model, viewModel, relation);
|
||||
});
|
||||
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets one foreign view model in the view model according to the relation and the information
|
||||
* from the model.
|
||||
*/
|
||||
protected setRelationsInViewModel<K extends BaseViewModel = V>(
|
||||
model: M,
|
||||
viewModel: K,
|
||||
relation: RelationDefinition
|
||||
): void {
|
||||
if (relation.type === 'M2M' && model[relation.ownIdKey] instanceof Array) {
|
||||
const foreignViewModels = this.viewModelStoreService.getMany(
|
||||
relation.foreignModel,
|
||||
model[relation.ownIdKey]
|
||||
);
|
||||
viewModel['_' + relation.ownKey] = foreignViewModels;
|
||||
} else if (relation.type === 'O2M') {
|
||||
const foreignViewModel = this.viewModelStoreService.get(relation.foreignModel, model[relation.ownIdKey]);
|
||||
viewModel['_' + relation.ownKey] = foreignViewModel;
|
||||
} else if (relation.type === 'nested') {
|
||||
const foreignViewModels: BaseViewModel[] = model[relation.ownKey].map(foreignModel =>
|
||||
this.createViewModel(foreignModel, relation.foreignModel, relation.relationDefinition || [])
|
||||
);
|
||||
foreignViewModels.sort((a: BaseViewModel, b: BaseViewModel) => {
|
||||
const order = relation.order;
|
||||
if (!relation.order || a[order] === b[order]) {
|
||||
return a.id - b.id;
|
||||
} else {
|
||||
return a[order] - b[order];
|
||||
}
|
||||
});
|
||||
viewModel['_' + relation.ownKey] = foreignViewModels;
|
||||
} else if (relation.type === 'custom') {
|
||||
relation.setRelations(model, viewModel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all models in this repository with all changed models.
|
||||
*
|
||||
@ -146,7 +343,7 @@ export abstract class BaseRepository<V extends BaseViewModel & T, M extends Base
|
||||
* @returns if at least one model was affected.
|
||||
*/
|
||||
public updateDependencies(changedModels: CollectionIds): boolean {
|
||||
if (!this.depsModelCtors || this.depsModelCtors.length === 0) {
|
||||
if (!this.relationDefinitions.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -154,20 +351,25 @@ export abstract class BaseRepository<V extends BaseViewModel & T, M extends Base
|
||||
const viewModels = this.getViewModelList();
|
||||
let somethingUpdated = false;
|
||||
Object.keys(changedModels).forEach(collection => {
|
||||
const dependencyChanged: boolean = this.depsModelCtors.some(ctor => {
|
||||
return ctor.COLLECTIONSTRING === collection;
|
||||
});
|
||||
const dependencyChanged: boolean = Object.keys(this.relationsByCollection).includes(collection);
|
||||
if (!dependencyChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ok, we are affected by this collection. Update all viewModels from this repo.
|
||||
viewModels.forEach(ownViewModel => {
|
||||
changedModels[collection].forEach(id => {
|
||||
ownViewModel.updateDependencies(this.viewModelStoreService.get(collection, id));
|
||||
const relations = this.relationsByCollection[collection];
|
||||
if (!relations || !relations.length) {
|
||||
return;
|
||||
}
|
||||
relations.forEach(relation => {
|
||||
changedModels[collection].forEach(id => {
|
||||
if (this.updateSingleDependency(ownViewModel, relation, collection, id)) {
|
||||
somethingUpdated = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
somethingUpdated = true;
|
||||
});
|
||||
if (somethingUpdated) {
|
||||
viewModels.forEach(ownViewModel => {
|
||||
@ -177,9 +379,64 @@ export abstract class BaseRepository<V extends BaseViewModel & T, M extends Base
|
||||
return somethingUpdated;
|
||||
}
|
||||
|
||||
public getListTitle: (titleInformation: T) => string = (titleInformation: T) => {
|
||||
return this.getTitle(titleInformation);
|
||||
};
|
||||
/**
|
||||
* Updates an own view model with an implicit given model by the collection and changedId.
|
||||
*
|
||||
* @return true, if something was updated.
|
||||
*/
|
||||
protected updateSingleDependency(
|
||||
ownViewModel: BaseViewModel,
|
||||
relation: RelationDefinition,
|
||||
collection: string,
|
||||
changedId: number
|
||||
): boolean {
|
||||
if (relation.type === 'M2M') {
|
||||
if (
|
||||
ownViewModel[relation.ownIdKey] &&
|
||||
ownViewModel[relation.ownIdKey] instanceof Array &&
|
||||
ownViewModel[relation.ownIdKey].includes(changedId)
|
||||
) {
|
||||
const foreignViewModel = <any>this.viewModelStoreService.get(collection, changedId);
|
||||
let ownModelArray = <any>ownViewModel['_' + relation.ownKey];
|
||||
if (!ownModelArray) {
|
||||
ownViewModel['_' + relation.ownKey] = [];
|
||||
ownModelArray = <any>ownViewModel['_' + relation.ownKey];
|
||||
}
|
||||
const index = ownModelArray.findIndex(user => user.id === changedId);
|
||||
if (index < 0) {
|
||||
ownModelArray.push(foreignViewModel);
|
||||
} else {
|
||||
ownModelArray[index] = foreignViewModel;
|
||||
}
|
||||
// TODO: set reverse
|
||||
|
||||
return true;
|
||||
}
|
||||
} else if (relation.type === 'O2M') {
|
||||
if (ownViewModel[relation.ownIdKey] === <any>changedId) {
|
||||
ownViewModel['_' + relation.ownKey] = <any>this.viewModelStoreService.get(collection, changedId);
|
||||
// TODO: set reverse
|
||||
|
||||
return true;
|
||||
}
|
||||
} else if (relation.type === 'nested') {
|
||||
let updated = false;
|
||||
(relation.relationDefinition || []).forEach(nestedRelation => {
|
||||
const nestedViewModels = ownViewModel[relation.ownKey] as BaseViewModel[];
|
||||
nestedViewModels.forEach(nestedViewModel => {
|
||||
if (this.updateSingleDependency(nestedViewModel, nestedRelation, collection, changedId)) {
|
||||
updated = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
return updated;
|
||||
} else if (relation.type === 'custom') {
|
||||
const foreignViewModel = <any>this.viewModelStoreService.get(collection, changedId);
|
||||
return relation.updateDependency(ownViewModel, foreignViewModel);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the (full) update to an existing model. So called "update"-function
|
||||
@ -242,27 +499,6 @@ export abstract class BaseRepository<V extends BaseViewModel & T, M extends Base
|
||||
return await this.dataSend.createModel(sendModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a view model out of a base model.
|
||||
*
|
||||
* Should read all necessary objects from the datastore
|
||||
* that the viewmodel needs
|
||||
* @param model
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
|
@ -134,14 +134,6 @@ export class ConfigRepositoryService extends BaseRepository<ViewConfig, Config,
|
||||
return titleInformation.key;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new ViewConfig of a given Config object
|
||||
* @param config
|
||||
*/
|
||||
public createViewModel(config: Config): ViewConfig {
|
||||
return new ViewConfig(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites the default delete procedure
|
||||
*
|
||||
@ -160,14 +152,6 @@ export class ConfigRepositoryService extends BaseRepository<ViewConfig, Config,
|
||||
throw new Error('Config variables cannot be created');
|
||||
}
|
||||
|
||||
protected loadInitialFromDS(): void {
|
||||
this.DS.getAll(Config).forEach((config: Config) => {
|
||||
this.viewModelStore[config.id] = this.createViewModel(config);
|
||||
this.updateConfigStructure(false, this.viewModelStore[config.id]);
|
||||
});
|
||||
this.updateConfigListObservable();
|
||||
}
|
||||
|
||||
public changedModels(ids: number[]): void {
|
||||
super.changedModels(ids);
|
||||
|
||||
|
@ -14,9 +14,29 @@ import { DataSendService } from 'app/core/core-services/data-send.service';
|
||||
import { HttpService } from 'app/core/core-services/http.service';
|
||||
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
||||
import { BaseIsListOfSpeakersContentObjectRepository } from '../base-is-list-of-speakers-content-object-repository';
|
||||
import { ViewListOfSpeakers } from 'app/site/agenda/models/view-list-of-speakers';
|
||||
import { ViewGroup } from 'app/site/users/models/view-group';
|
||||
import { Group } from 'app/shared/models/users/group';
|
||||
import { RelationDefinition } from '../base-repository';
|
||||
|
||||
const MediafileRelations: RelationDefinition[] = [
|
||||
{
|
||||
type: 'O2M',
|
||||
ownIdKey: 'parent_id',
|
||||
ownKey: 'parent',
|
||||
foreignModel: ViewMediafile
|
||||
},
|
||||
{
|
||||
type: 'M2M',
|
||||
ownIdKey: 'access_groups_id',
|
||||
ownKey: 'access_groups',
|
||||
foreignModel: ViewGroup
|
||||
},
|
||||
{
|
||||
type: 'M2M',
|
||||
ownIdKey: 'inherited_access_groups_id',
|
||||
ownKey: 'inherited_access_groups',
|
||||
foreignModel: ViewGroup
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Repository for MediaFiles
|
||||
@ -46,7 +66,7 @@ export class MediafileRepositoryService extends BaseIsListOfSpeakersContentObjec
|
||||
dataSend: DataSendService,
|
||||
private httpService: HttpService
|
||||
) {
|
||||
super(DS, dataSend, mapperService, viewModelStoreService, translate, Mediafile, [Mediafile, Group]);
|
||||
super(DS, dataSend, mapperService, viewModelStoreService, translate, Mediafile, MediafileRelations);
|
||||
this.directoryBehaviorSubject = new BehaviorSubject([]);
|
||||
this.getViewModelListObservable().subscribe(mediafiles => {
|
||||
if (mediafiles) {
|
||||
@ -67,25 +87,6 @@ export class MediafileRepositoryService extends BaseIsListOfSpeakersContentObjec
|
||||
return this.translate.instant(plural ? 'Files' : 'File');
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates mediafile ViewModels out of given mediafile objects
|
||||
*
|
||||
* @param file mediafile to convert
|
||||
* @returns a new mediafile ViewModel
|
||||
*/
|
||||
public createViewModel(file: Mediafile): ViewMediafile {
|
||||
const listOfSpeakers = this.viewModelStoreService.get(ViewListOfSpeakers, file.list_of_speakers_id);
|
||||
const parent = this.viewModelStoreService.get(ViewMediafile, file.parent_id);
|
||||
const accessGroups = this.viewModelStoreService.getMany(ViewGroup, file.access_groups_id);
|
||||
let inheritedAccessGroups;
|
||||
if (file.has_inherited_access_groups) {
|
||||
inheritedAccessGroups = this.viewModelStoreService.getMany(ViewGroup, <number[]>(
|
||||
file.inherited_access_groups_id
|
||||
));
|
||||
}
|
||||
return new ViewMediafile(file, listOfSpeakers, parent, accessGroups, inheritedAccessGroups);
|
||||
}
|
||||
|
||||
public async getDirectoryIdByPath(pathSegments: string[]): Promise<number | null> {
|
||||
let parentId = null;
|
||||
|
||||
|
@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { BaseRepository } from '../base-repository';
|
||||
import { BaseRepository, RelationDefinition } from '../base-repository';
|
||||
import { Category } from 'app/shared/models/motions/category';
|
||||
import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
|
||||
import { DataSendService } from '../../core-services/data-send.service';
|
||||
@ -13,6 +13,15 @@ import { ViewModelStoreService } from 'app/core/core-services/view-model-store.s
|
||||
import { Motion } from 'app/shared/models/motions/motion';
|
||||
import { TreeIdNode } from 'app/core/ui-services/tree.service';
|
||||
|
||||
const CategoryRelations: RelationDefinition[] = [
|
||||
{
|
||||
type: 'O2M',
|
||||
ownIdKey: 'parent_id',
|
||||
ownKey: 'parent',
|
||||
foreignModel: ViewCategory
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Repository Services for Categories
|
||||
*
|
||||
@ -47,7 +56,7 @@ export class CategoryRepositoryService extends BaseRepository<ViewCategory, Cate
|
||||
translate: TranslateService,
|
||||
private httpService: HttpService
|
||||
) {
|
||||
super(DS, dataSend, mapperService, viewModelStoreService, translate, Category, [Category]);
|
||||
super(DS, dataSend, mapperService, viewModelStoreService, translate, Category, CategoryRelations);
|
||||
|
||||
this.setSortFunction((a, b) => a.weight - b.weight);
|
||||
}
|
||||
@ -62,11 +71,6 @@ export class CategoryRepositoryService extends BaseRepository<ViewCategory, Cate
|
||||
return this.translate.instant(plural ? 'Categories' : 'Category');
|
||||
};
|
||||
|
||||
protected createViewModel(category: Category): ViewCategory {
|
||||
const parent = this.viewModelStoreService.get(ViewCategory, category.parent_id);
|
||||
return new ViewCategory(category, parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a categories numbering.
|
||||
*
|
||||
|
@ -5,9 +5,6 @@ import { map } from 'rxjs/operators';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { DataSendService } from 'app/core/core-services/data-send.service';
|
||||
import { User } from 'app/shared/models/users/user';
|
||||
import { Category } from 'app/shared/models/motions/category';
|
||||
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';
|
||||
@ -61,11 +58,7 @@ export class ChangeRecommendationRepositoryService extends BaseRepository<
|
||||
translate: TranslateService,
|
||||
private diffService: DiffService
|
||||
) {
|
||||
super(DS, dataSend, mapperService, viewModelStoreService, translate, MotionChangeRecommendation, [
|
||||
Category,
|
||||
User,
|
||||
Workflow
|
||||
]);
|
||||
super(DS, dataSend, mapperService, viewModelStoreService, translate, MotionChangeRecommendation);
|
||||
}
|
||||
|
||||
public getTitle = (titleInformation: MotionChangeRecommendationTitleInformation) => {
|
||||
@ -76,15 +69,6 @@ export class ChangeRecommendationRepositoryService extends BaseRepository<
|
||||
return this.translate.instant(plural ? 'Change recommendations' : 'Change recommendation');
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates this view wrapper based on an actual Change Recommendation model
|
||||
*
|
||||
* @param {MotionChangeRecommendation} model
|
||||
*/
|
||||
protected createViewModel(model: MotionChangeRecommendation): ViewMotionChangeRecommendation {
|
||||
return new ViewMotionChangeRecommendation(model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a change recommendation view object, a entry in the backend is created.
|
||||
* @param view
|
||||
|
@ -14,8 +14,6 @@ import { MotionRepositoryService } from './motion-repository.service';
|
||||
import { ViewMotion } from 'app/site/motions/models/view-motion';
|
||||
import { ViewMotionBlock, MotionBlockTitleInformation } from 'app/site/motions/models/view-motion-block';
|
||||
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
||||
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';
|
||||
|
||||
/**
|
||||
@ -59,18 +57,6 @@ export class MotionBlockRepositoryService extends BaseIsAgendaItemAndListOfSpeak
|
||||
return this.translate.instant(plural ? 'Motion blocks' : 'Motion block');
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a given motion block into a ViewModel
|
||||
*
|
||||
* @param block a motion block
|
||||
* @returns a new ViewMotionBlock
|
||||
*/
|
||||
protected createViewModel(block: MotionBlock): ViewMotionBlock {
|
||||
const item = this.viewModelStoreService.get(ViewItem, block.agenda_item_id);
|
||||
const listOfSpeakers = this.viewModelStoreService.get(ViewListOfSpeakers, block.list_of_speakers_id);
|
||||
return new ViewMotionBlock(block, item, listOfSpeakers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the motion block id from the given motion
|
||||
*
|
||||
|
@ -4,19 +4,33 @@ 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 { BaseRepository, RelationDefinition } from '../base-repository';
|
||||
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 { ViewMotion } from 'app/site/motions/models/view-motion';
|
||||
|
||||
const MotionCommentSectionRelations: RelationDefinition[] = [
|
||||
{
|
||||
type: 'M2M',
|
||||
ownIdKey: 'read_groups_id',
|
||||
ownKey: 'read_groups',
|
||||
foreignModel: ViewGroup
|
||||
},
|
||||
{
|
||||
type: 'M2M',
|
||||
ownIdKey: 'write_groups_id',
|
||||
ownKey: 'write_groups',
|
||||
foreignModel: ViewGroup
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Repository Services for Categories
|
||||
*
|
||||
@ -53,7 +67,15 @@ export class MotionCommentSectionRepositoryService extends BaseRepository<
|
||||
translate: TranslateService,
|
||||
private http: HttpService
|
||||
) {
|
||||
super(DS, dataSend, mapperService, viewModelStoreService, translate, MotionCommentSection, [Group]);
|
||||
super(
|
||||
DS,
|
||||
dataSend,
|
||||
mapperService,
|
||||
viewModelStoreService,
|
||||
translate,
|
||||
MotionCommentSection,
|
||||
MotionCommentSectionRelations
|
||||
);
|
||||
|
||||
this.viewModelSortFn = (a: ViewMotionCommentSection, b: ViewMotionCommentSection) => {
|
||||
if (a.weight === b.weight) {
|
||||
@ -72,18 +94,6 @@ export class MotionCommentSectionRepositoryService extends BaseRepository<
|
||||
return this.translate.instant(plural ? 'Comment sections' : 'Comment section');
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the ViewModel for the MotionComment Section
|
||||
*
|
||||
* @param section the MotionCommentSection the View Model should be created of
|
||||
* @returns the View Model representation of the MotionCommentSection
|
||||
*/
|
||||
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);
|
||||
return new ViewMotionCommentSection(section, readGroups, writeGroups);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a comment made at a MotionCommentSection. Does an update, if
|
||||
* there is a comment text. Deletes the comment, if the text is empty.
|
||||
|
@ -5,42 +5,35 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { Category } from 'app/shared/models/motions/category';
|
||||
import { ChangeRecoMode, MotionTitleInformation, ViewMotion } 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, CollectionIds } from 'app/core/core-services/data-store.service';
|
||||
import { DataStoreService } from 'app/core/core-services/data-store.service';
|
||||
import { DiffService, DiffLinesInParagraph } from 'app/core/ui-services/diff.service';
|
||||
import { HttpService } from 'app/core/core-services/http.service';
|
||||
import { LinenumberingService, LineNumberRange } from '../../ui-services/linenumbering.service';
|
||||
import { Mediafile } from 'app/shared/models/mediafiles/mediafile';
|
||||
import { Motion } from 'app/shared/models/motions/motion';
|
||||
import { MotionBlock } from 'app/shared/models/motions/motion-block';
|
||||
import { MotionChangeRecommendation } from 'app/shared/models/motions/motion-change-reco';
|
||||
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-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';
|
||||
import { Workflow } from 'app/shared/models/motions/workflow';
|
||||
import { WorkflowState } from 'app/shared/models/motions/workflow-state';
|
||||
import { Tag } from 'app/shared/models/core/tag';
|
||||
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
||||
import { ViewCategory } from 'app/site/motions/models/view-category';
|
||||
import { ViewUser } from 'app/site/users/models/view-user';
|
||||
import { ViewWorkflow } from 'app/site/motions/models/view-workflow';
|
||||
import { ViewItem } from 'app/site/agenda/models/view-item';
|
||||
import { ViewMotionBlock } from 'app/site/motions/models/view-motion-block';
|
||||
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
|
||||
import { ViewTag } from 'app/site/tags/models/view-tag';
|
||||
import { ViewPersonalNote } from 'app/site/users/models/view-personal-note';
|
||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||
import { PersonalNote, PersonalNoteContent } from 'app/shared/models/users/personal-note';
|
||||
import { ViewListOfSpeakers } from 'app/site/agenda/models/view-list-of-speakers';
|
||||
import { PersonalNoteContent } from 'app/shared/models/users/personal-note';
|
||||
import { BaseIsAgendaItemAndListOfSpeakersContentObjectRepository } from '../base-is-agenda-item-and-list-of-speakers-content-object-repository';
|
||||
import { RelationDefinition } from '../base-repository';
|
||||
import { ViewState } from 'app/site/motions/models/view-state';
|
||||
import { ViewSubmitter } from 'app/site/motions/models/view-submitter';
|
||||
|
||||
type SortProperty = 'weight' | 'identifier';
|
||||
|
||||
@ -74,6 +67,84 @@ export interface ParagraphToChoose {
|
||||
lineTo: number;
|
||||
}
|
||||
|
||||
const MotionRelations: RelationDefinition[] = [
|
||||
{
|
||||
type: 'O2M',
|
||||
ownIdKey: 'state_id',
|
||||
ownKey: 'state',
|
||||
foreignModel: ViewState
|
||||
},
|
||||
{
|
||||
type: 'O2M',
|
||||
ownIdKey: 'recommendation_id',
|
||||
ownKey: 'recommendation',
|
||||
foreignModel: ViewState
|
||||
},
|
||||
{
|
||||
type: 'O2M',
|
||||
ownIdKey: 'workflow_id',
|
||||
ownKey: 'workflow',
|
||||
foreignModel: ViewWorkflow
|
||||
},
|
||||
{
|
||||
type: 'O2M',
|
||||
ownIdKey: 'category_id',
|
||||
ownKey: 'category',
|
||||
foreignModel: ViewCategory
|
||||
},
|
||||
{
|
||||
type: 'O2M',
|
||||
ownIdKey: 'motion_block_id',
|
||||
ownKey: 'motion_block',
|
||||
foreignModel: ViewMotionBlock
|
||||
},
|
||||
{
|
||||
type: 'nested',
|
||||
ownKey: 'submitters',
|
||||
foreignModel: ViewSubmitter,
|
||||
order: 'weight',
|
||||
relationDefinition: [
|
||||
{
|
||||
type: 'O2M',
|
||||
ownIdKey: 'user_id',
|
||||
ownKey: 'user',
|
||||
foreignModel: ViewUser
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'M2M',
|
||||
ownIdKey: 'supporters_id',
|
||||
ownKey: 'supporters',
|
||||
foreignModel: ViewUser
|
||||
},
|
||||
{
|
||||
type: 'M2M',
|
||||
ownIdKey: 'attachments_id',
|
||||
ownKey: 'attachments',
|
||||
foreignModel: ViewMediafile
|
||||
},
|
||||
{
|
||||
type: 'M2M',
|
||||
ownIdKey: 'tags_id',
|
||||
ownKey: 'tags',
|
||||
foreignModel: ViewTag
|
||||
},
|
||||
{
|
||||
type: 'O2M',
|
||||
ownIdKey: 'parent_id',
|
||||
ownKey: 'parent',
|
||||
foreignModel: ViewMotion
|
||||
},
|
||||
{
|
||||
type: 'M2M',
|
||||
ownIdKey: 'change_recommendations_id',
|
||||
ownKey: 'changeRecommendations',
|
||||
foreignModel: ViewMotionChangeRecommendation
|
||||
}
|
||||
// Personal notes are dynamically added in the repo.
|
||||
];
|
||||
|
||||
/**
|
||||
* Repository Services for motions (and potentially categories)
|
||||
*
|
||||
@ -126,23 +197,36 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
||||
private readonly diff: DiffService,
|
||||
private operator: OperatorService
|
||||
) {
|
||||
super(DS, dataSend, mapperService, viewModelStoreService, translate, Motion, [
|
||||
Category,
|
||||
User,
|
||||
Workflow,
|
||||
MotionBlock,
|
||||
Mediafile,
|
||||
Tag,
|
||||
MotionChangeRecommendation,
|
||||
PersonalNote,
|
||||
Motion
|
||||
]);
|
||||
super(DS, dataSend, mapperService, viewModelStoreService, translate, Motion, MotionRelations);
|
||||
config.get<SortProperty>('motions_motions_sorting').subscribe(conf => {
|
||||
this.sortProperty = conf;
|
||||
this.setConfigSortFn();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the personal note custom relation to the relation definitions.
|
||||
*/
|
||||
protected groupRelationsByCollections(): void {
|
||||
this.relationDefinitions.push({
|
||||
type: 'custom',
|
||||
foreignModel: ViewPersonalNote,
|
||||
setRelations: (motion: Motion, viewMotion: ViewMotion) => {
|
||||
viewMotion.personalNote = this.getPersonalNoteForMotion(motion);
|
||||
},
|
||||
updateDependency: (viewMotion: ViewMotion, viewPersonalNote: ViewPersonalNote) => {
|
||||
const personalNoteContent = viewPersonalNote.getNoteContent(this.collectionString, viewMotion.id);
|
||||
if (!personalNoteContent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
viewMotion.personalNote = personalNoteContent;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
super.groupRelationsByCollections();
|
||||
}
|
||||
|
||||
public getTitle = (titleInformation: MotionTitleInformation) => {
|
||||
if (titleInformation.identifier) {
|
||||
return titleInformation.identifier + ': ' + titleInformation.title;
|
||||
@ -183,64 +267,20 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
||||
return this.translate.instant(plural ? 'Motions' : 'Motion');
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a motion to a ViewMotion and adds it to the store.
|
||||
*
|
||||
* Foreign references of the motion will be resolved (e.g submitters to users)
|
||||
* Expandable to all (server side) changes that might occur on the motion object.
|
||||
*
|
||||
* @param motion blank motion domain object
|
||||
*/
|
||||
protected createViewModel(motion: Motion): ViewMotion {
|
||||
const category = this.viewModelStoreService.get(ViewCategory, motion.category_id);
|
||||
const submitters = this.viewModelStoreService.getMany(ViewUser, motion.sorted_submitters_id);
|
||||
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);
|
||||
const parent = this.viewModelStoreService.get(ViewMotion, motion.parent_id);
|
||||
const amendments = this.viewModelStoreService.filter(ViewMotion, m => m.parent_id && m.parent_id === motion.id);
|
||||
const changeRecommendations = this.viewModelStoreService.filter(
|
||||
ViewMotionChangeRecommendation,
|
||||
cr => cr.motion_id === motion.id
|
||||
);
|
||||
let state: WorkflowState = null;
|
||||
if (workflow) {
|
||||
state = workflow.getStateById(motion.state_id);
|
||||
}
|
||||
const personalNote = this.getPersonalNoteForMotion(motion.id);
|
||||
const viewMotion = new ViewMotion(
|
||||
motion,
|
||||
category,
|
||||
submitters,
|
||||
supporters,
|
||||
workflow,
|
||||
state,
|
||||
item,
|
||||
listOfSpeakers,
|
||||
block,
|
||||
attachments,
|
||||
tags,
|
||||
parent,
|
||||
changeRecommendations,
|
||||
amendments,
|
||||
personalNote
|
||||
);
|
||||
viewMotion.getIdentifierOrTitle = () => this.getIdentifierOrTitle(viewMotion);
|
||||
viewMotion.getProjectorTitle = () => this.getAgendaSlideTitle(viewMotion);
|
||||
return viewMotion;
|
||||
protected createViewModelWithTitles(model: Motion): ViewMotion {
|
||||
const viewModel = super.createViewModelWithTitles(model);
|
||||
viewModel.getIdentifierOrTitle = () => this.getIdentifierOrTitle(viewModel);
|
||||
viewModel.getProjectorTitle = () => this.getAgendaSlideTitle(viewModel);
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the personal note content for one motion by their id
|
||||
*
|
||||
* @param motionId the id of the motion
|
||||
* @param motion the motion
|
||||
* @returns the personal note content for this motion or null
|
||||
*/
|
||||
private getPersonalNoteForMotion(motionId: number): PersonalNoteContent | null {
|
||||
private getPersonalNoteForMotion(motion: Motion): PersonalNoteContent | null {
|
||||
if (this.operator.isAnonymous) {
|
||||
return;
|
||||
}
|
||||
@ -248,64 +288,17 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
||||
const personalNote = this.viewModelStoreService.find(ViewPersonalNote, pn => {
|
||||
return pn.userId === this.operator.user.id;
|
||||
});
|
||||
|
||||
if (!personalNote) {
|
||||
return;
|
||||
}
|
||||
|
||||
const notes = personalNote.notes;
|
||||
const collection = Motion.COLLECTIONSTRING;
|
||||
if (notes && notes[collection] && notes[collection][motionId]) {
|
||||
return notes[collection][motionId];
|
||||
if (notes && notes[collection] && notes[collection][motion.id]) {
|
||||
return notes[collection][motion.id];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Special handling of updating personal notes.
|
||||
* @override
|
||||
*/
|
||||
public updateDependencies(changedModels: CollectionIds): boolean {
|
||||
if (!this.depsModelCtors || this.depsModelCtors.length === 0) {
|
||||
return;
|
||||
}
|
||||
const viewModels = this.getViewModelList();
|
||||
let somethingUpdated = false;
|
||||
Object.keys(changedModels).forEach(collection => {
|
||||
const dependencyChanged: boolean = this.depsModelCtors.some(ctor => {
|
||||
return ctor.COLLECTIONSTRING === collection;
|
||||
});
|
||||
if (!dependencyChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not update personal notes, if the operator is anonymous
|
||||
if (collection === PersonalNote.COLLECTIONSTRING && this.operator.isAnonymous) {
|
||||
return;
|
||||
}
|
||||
|
||||
viewModels.forEach(ownViewModel => {
|
||||
changedModels[collection].forEach(id => {
|
||||
const viewModel = this.viewModelStoreService.get(collection, id);
|
||||
// Only update the personal note, if the operator is the right user.
|
||||
if (
|
||||
collection === PersonalNote.COLLECTIONSTRING &&
|
||||
(<ViewPersonalNote>viewModel).userId !== this.operator.user.id
|
||||
) {
|
||||
return;
|
||||
}
|
||||
ownViewModel.updateDependencies(viewModel);
|
||||
});
|
||||
});
|
||||
somethingUpdated = true;
|
||||
});
|
||||
if (somethingUpdated) {
|
||||
viewModels.forEach(ownViewModel => {
|
||||
this.updateViewModelObservable(ownViewModel.id);
|
||||
});
|
||||
}
|
||||
return somethingUpdated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the state of a motion
|
||||
*
|
||||
@ -757,20 +750,6 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
||||
.filter((para: ViewMotionAmendedParagraph) => para !== null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns motion duplicates (sharing the identifier)
|
||||
*
|
||||
* @param viewMotion the ViewMotion to compare against the list of Motions
|
||||
* in the data
|
||||
* @returns An Array of ViewMotions with the same identifier of the input, or an empty array
|
||||
*/
|
||||
public getMotionDuplicates(motion: ViewMotion): ViewMotion[] {
|
||||
const duplicates = this.DS.filter(Motion, item => motion.identifier === item.identifier);
|
||||
const viewMotions: ViewMotion[] = [];
|
||||
duplicates.forEach(item => viewMotions.push(this.createViewModel(item)));
|
||||
return viewMotions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to the server, creating a new poll for the motion
|
||||
*/
|
||||
|
@ -0,0 +1,17 @@
|
||||
import { TestBed, inject } from '@angular/core/testing';
|
||||
|
||||
import { E2EImportsModule } from '../../../../e2e-imports.module';
|
||||
import { StateRepositoryService } from './state-repository.service';
|
||||
|
||||
describe('StateRepositoryService', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule],
|
||||
providers: [StateRepositoryService]
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created', inject([StateRepositoryService], (service: StateRepositoryService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
});
|
@ -0,0 +1,69 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { WorkflowTitleInformation, ViewWorkflow } 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, RelationDefinition } from '../base-repository';
|
||||
import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
|
||||
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
||||
import { State } from 'app/shared/models/motions/state';
|
||||
import { ViewState, StateTitleInformation } from 'app/site/motions/models/view-state';
|
||||
|
||||
const StateRelations: RelationDefinition[] = [
|
||||
{
|
||||
type: 'O2M',
|
||||
ownIdKey: 'workflow_id',
|
||||
ownKey: 'workflow',
|
||||
foreignModel: ViewWorkflow
|
||||
},
|
||||
{
|
||||
type: 'M2M',
|
||||
ownIdKey: 'next_states_id',
|
||||
ownKey: 'next_states',
|
||||
foreignModel: ViewState
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Repository Services for States
|
||||
*
|
||||
* The repository is meant to process domain objects (those found under
|
||||
* shared/models), so components can display them and interact with them.
|
||||
*
|
||||
* Rather than manipulating models directly, the repository is meant to
|
||||
* inform the {@link DataSendService} about changes which will send
|
||||
* them to the Server.
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class StateRepositoryService extends BaseRepository<ViewState, State, StateTitleInformation> {
|
||||
/**
|
||||
* Creates a WorkflowRepository
|
||||
* Converts existing and incoming workflow to ViewWorkflows
|
||||
*
|
||||
* @param DS Accessing the data store
|
||||
* @param mapperService mapping models
|
||||
* @param dataSend sending data to the server
|
||||
* @param httpService HttpService
|
||||
*/
|
||||
public constructor(
|
||||
DS: DataStoreService,
|
||||
dataSend: DataSendService,
|
||||
mapperService: CollectionStringMapperService,
|
||||
viewModelStoreService: ViewModelStoreService,
|
||||
translate: TranslateService
|
||||
) {
|
||||
super(DS, dataSend, mapperService, viewModelStoreService, translate, State, StateRelations);
|
||||
}
|
||||
|
||||
public getTitle = (titleInformation: WorkflowTitleInformation) => {
|
||||
return titleInformation.name;
|
||||
};
|
||||
|
||||
public getVerboseName = (plural: boolean = false) => {
|
||||
return this.translate.instant(plural ? 'Workflows' : 'Workflow');
|
||||
};
|
||||
}
|
@ -51,8 +51,4 @@ export class StatuteParagraphRepositoryService extends BaseRepository<
|
||||
public getVerboseName = (plural: boolean = false) => {
|
||||
return this.translate.instant(plural ? 'Statute paragraphs' : 'Statute paragraph');
|
||||
};
|
||||
|
||||
protected createViewModel(statuteParagraph: StatuteParagraph): ViewStatuteParagraph {
|
||||
return new ViewStatuteParagraph(statuteParagraph);
|
||||
}
|
||||
}
|
||||
|
@ -6,15 +6,29 @@ import { Workflow } from 'app/shared/models/motions/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';
|
||||
import { BaseRepository, RelationDefinition } from '../base-repository';
|
||||
import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
|
||||
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 { ViewState } from 'app/site/motions/models/view-state';
|
||||
|
||||
const WorkflowRelations: RelationDefinition[] = [
|
||||
{
|
||||
type: 'M2M',
|
||||
ownIdKey: 'states_id',
|
||||
ownKey: 'states',
|
||||
foreignModel: ViewState
|
||||
},
|
||||
{
|
||||
type: 'O2M',
|
||||
ownIdKey: 'first_state_id',
|
||||
ownKey: 'first_state',
|
||||
foreignModel: ViewState
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Repository Services for Categories
|
||||
* Repository Services for Workflows
|
||||
*
|
||||
* The repository is meant to process domain objects (those found under
|
||||
* shared/models), so components can display them and interact with them.
|
||||
@ -27,11 +41,6 @@ import { ViewModelStoreService } from 'app/core/core-services/view-model-store.s
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class WorkflowRepositoryService extends BaseRepository<ViewWorkflow, Workflow, WorkflowTitleInformation> {
|
||||
/**
|
||||
* The url to state on rest
|
||||
*/
|
||||
private restStateUrl = '/rest/motions/state/';
|
||||
|
||||
/**
|
||||
* Creates a WorkflowRepository
|
||||
* Converts existing and incoming workflow to ViewWorkflows
|
||||
@ -46,16 +55,9 @@ export class WorkflowRepositoryService extends BaseRepository<ViewWorkflow, Work
|
||||
dataSend: DataSendService,
|
||||
mapperService: CollectionStringMapperService,
|
||||
viewModelStoreService: ViewModelStoreService,
|
||||
translate: TranslateService,
|
||||
private httpService: HttpService
|
||||
translate: TranslateService
|
||||
) {
|
||||
super(DS, dataSend, mapperService, viewModelStoreService, translate, Workflow);
|
||||
|
||||
this.viewModelListSubject.subscribe(models => {
|
||||
if (models && models.length > 0) {
|
||||
this.initSorting(models);
|
||||
}
|
||||
});
|
||||
super(DS, dataSend, mapperService, viewModelStoreService, translate, Workflow, WorkflowRelations);
|
||||
}
|
||||
|
||||
public getTitle = (titleInformation: WorkflowTitleInformation) => {
|
||||
@ -66,86 +68,14 @@ export class WorkflowRepositoryService extends BaseRepository<ViewWorkflow, Work
|
||||
return this.translate.instant(plural ? 'Workflows' : 'Workflow');
|
||||
};
|
||||
|
||||
/**
|
||||
* Sort the states of custom workflows. Ignores simple and complex workflows.
|
||||
* Implying the default workflows always have the IDs 1 und 2
|
||||
*
|
||||
* TODO: Temp Solution. Should be replaced by general sorting over repositories after PR 4411
|
||||
* This is an abstract to prevent further collisions. Real sorting is then done in 4411
|
||||
* For now this "just" sorts the Workflow states of all custom workflows
|
||||
*/
|
||||
private initSorting(workflows: ViewWorkflow[]): void {
|
||||
for (const workflow of workflows) {
|
||||
workflow.sortStates();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ViewWorkflow from a given Workflow
|
||||
*
|
||||
* @param workflow the Workflow to convert
|
||||
*/
|
||||
protected createViewModel(workflow: Workflow): ViewWorkflow {
|
||||
return new ViewWorkflow(workflow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new state to the given workflow
|
||||
*
|
||||
* @param stateName The name of the new Workflow
|
||||
* @param viewWorkflow The workflow
|
||||
*/
|
||||
public async addState(stateName: string, viewWorkflow: ViewWorkflow): Promise<void> {
|
||||
const newStatePayload = {
|
||||
name: stateName,
|
||||
workflow_id: viewWorkflow.id
|
||||
};
|
||||
await this.httpService.post(this.restStateUrl, newStatePayload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates workflow state with a new value-object and sends it to the server
|
||||
*
|
||||
* @param newValue a key-value pair with the new state value
|
||||
* @param workflowState the workflow state to update
|
||||
*/
|
||||
public async updateState(newValue: object, workflowState: WorkflowState): Promise<void> {
|
||||
const stateUpdate = Object.assign(workflowState, newValue);
|
||||
await this.httpService.put(`${this.restStateUrl}${workflowState.id}/`, stateUpdate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the selected work
|
||||
*
|
||||
* @param workflowState the workflow state to delete
|
||||
*/
|
||||
public async deleteState(workflowState: WorkflowState): Promise<void> {
|
||||
await this.httpService.delete(`${this.restStateUrl}${workflowState.id}/`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects all existing states from all workflows
|
||||
*
|
||||
* @returns All currently existing workflow states
|
||||
*/
|
||||
public getAllWorkflowStates(): WorkflowState[] {
|
||||
let states: WorkflowState[] = [];
|
||||
this.getViewModelList().forEach(workflow => {
|
||||
if (workflow) {
|
||||
states = states.concat(workflow.states);
|
||||
}
|
||||
});
|
||||
return states;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all workflowStates that cover the list of viewMotions given
|
||||
*
|
||||
* @param motions The motions to get the workflows from
|
||||
* @returns The workflow states to the given motion
|
||||
*/
|
||||
public getWorkflowStatesForMotions(motions: ViewMotion[]): WorkflowState[] {
|
||||
let states: WorkflowState[] = [];
|
||||
public getWorkflowStatesForMotions(motions: ViewMotion[]): ViewState[] {
|
||||
let states: ViewState[] = [];
|
||||
const workflowIds = motions
|
||||
.map(motion => motion.workflow_id)
|
||||
.filter((value, index, self) => self.indexOf(value) === index);
|
||||
|
@ -36,10 +36,6 @@ export class CountdownRepositoryService extends BaseRepository<ViewCountdown, Co
|
||||
return this.translate.instant(plural ? 'Countdowns' : 'Countdown');
|
||||
};
|
||||
|
||||
protected createViewModel(countdown: Countdown): ViewCountdown {
|
||||
return new ViewCountdown(countdown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a countdown.
|
||||
*
|
||||
|
@ -52,10 +52,6 @@ export class ProjectionDefaultRepositoryService extends BaseRepository<
|
||||
return this.translate.instant(titleInformation.display_name);
|
||||
};
|
||||
|
||||
public createViewModel(projectionDefault: ProjectionDefault): ViewProjectionDefault {
|
||||
return new ViewProjectionDefault(projectionDefault);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creation of projection defaults is not supported.
|
||||
*/
|
||||
|
@ -38,8 +38,4 @@ export class ProjectorMessageRepositoryService extends BaseRepository<
|
||||
public getVerboseName = (plural: boolean = false) => {
|
||||
return this.translate.instant(plural ? 'Messages' : 'Message');
|
||||
};
|
||||
|
||||
protected createViewModel(message: ProjectorMessage): ViewProjectorMessage {
|
||||
return new ViewProjectorMessage(message);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { BaseRepository } from '../base-repository';
|
||||
import { BaseRepository, RelationDefinition } 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';
|
||||
@ -21,6 +21,15 @@ export enum ScrollScaleDirection {
|
||||
Reset = 'reset'
|
||||
}
|
||||
|
||||
const ProjectorRelations: RelationDefinition[] = [
|
||||
{
|
||||
type: 'O2M',
|
||||
ownIdKey: 'reference_projector_id',
|
||||
ownKey: 'referenceProjector',
|
||||
foreignModel: ViewProjector
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Manages all projector instances.
|
||||
*/
|
||||
@ -44,7 +53,7 @@ export class ProjectorRepositoryService extends BaseRepository<ViewProjector, Pr
|
||||
translate: TranslateService,
|
||||
private http: HttpService
|
||||
) {
|
||||
super(DS, dataSend, mapperService, viewModelStoreService, translate, Projector, [Projector]);
|
||||
super(DS, dataSend, mapperService, viewModelStoreService, translate, Projector, ProjectorRelations);
|
||||
}
|
||||
|
||||
public getTitle = (titleInformation: ProjectorTitleInformation) => {
|
||||
@ -55,10 +64,6 @@ export class ProjectorRepositoryService extends BaseRepository<ViewProjector, Pr
|
||||
return this.translate.instant(plural ? 'Projectors' : 'Projector');
|
||||
};
|
||||
|
||||
public createViewModel(projector: Projector): ViewProjector {
|
||||
return new ViewProjector(projector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new projector. Adds the clock as default, stable element
|
||||
*/
|
||||
|
@ -52,10 +52,6 @@ export class TagRepositoryService extends BaseRepository<ViewTag, Tag, TagTitleI
|
||||
return this.translate.instant(plural ? 'Tags' : 'Tag');
|
||||
};
|
||||
|
||||
protected createViewModel(tag: Tag): ViewTag {
|
||||
return new ViewTag(tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default sorting (e.g. in dropdowns and for new users) to 'name'
|
||||
*/
|
||||
|
@ -5,14 +5,21 @@ 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 { DataSendService } from 'app/core/core-services/data-send.service';
|
||||
import { Mediafile } from 'app/shared/models/mediafiles/mediafile';
|
||||
import { Topic } from 'app/shared/models/topics/topic';
|
||||
import { ViewTopic, TopicTitleInformation } from 'app/site/topics/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';
|
||||
import { RelationDefinition } from '../base-repository';
|
||||
|
||||
const TopicRelations: RelationDefinition[] = [
|
||||
{
|
||||
type: 'M2M',
|
||||
ownIdKey: 'attachments_id',
|
||||
ownKey: 'attachments',
|
||||
foreignModel: ViewMediafile
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Repository for topics
|
||||
@ -39,7 +46,7 @@ export class TopicRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCon
|
||||
viewModelStoreService: ViewModelStoreService,
|
||||
translate: TranslateService
|
||||
) {
|
||||
super(DS, dataSend, mapperService, viewModelStoreService, translate, Topic, [Mediafile]);
|
||||
super(DS, dataSend, mapperService, viewModelStoreService, translate, Topic, TopicRelations);
|
||||
}
|
||||
|
||||
public getTitle = (titleInformation: TopicTitleInformation) => {
|
||||
@ -63,29 +70,4 @@ export class TopicRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCon
|
||||
public getVerboseName = (plural: boolean = false) => {
|
||||
return this.translate.instant(plural ? 'Topics' : 'Topic');
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new viewModel out of the given model
|
||||
*
|
||||
* @param topic The topic that shall be converted into a view topic
|
||||
* @returns a new view topic
|
||||
*/
|
||||
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 listOfSpeakers = this.viewModelStoreService.get(ViewListOfSpeakers, topic.list_of_speakers_id);
|
||||
return new ViewTopic(topic, attachments, item, listOfSpeakers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all duplicates for a topic
|
||||
*
|
||||
* @param topic
|
||||
*/
|
||||
public getTopicDuplicates(topic: ViewTopic): ViewTopic[] {
|
||||
const duplicates = this.DS.filter(Topic, item => topic.title === item.title);
|
||||
const viewTopics: ViewTopic[] = [];
|
||||
duplicates.forEach(item => viewTopics.push(this.createViewModel(item)));
|
||||
return viewTopics;
|
||||
}
|
||||
}
|
||||
|
@ -70,10 +70,6 @@ export class GroupRepositoryService extends BaseRepository<ViewGroup, Group, Gro
|
||||
return this.translate.instant(plural ? 'Groups' : 'Group');
|
||||
};
|
||||
|
||||
public createViewModel(group: Group): ViewGroup {
|
||||
return new ViewGroup(group);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the given permisson.
|
||||
*
|
||||
|
@ -43,10 +43,6 @@ export class PersonalNoteRepositoryService extends BaseRepository<
|
||||
return this.translate.instant(plural ? 'Personal notes' : 'Personal note');
|
||||
};
|
||||
|
||||
protected createViewModel(personalNote: PersonalNote): ViewPersonalNote {
|
||||
return new ViewPersonalNote(personalNote);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrite the default procedure
|
||||
*
|
||||
|
@ -2,13 +2,12 @@ import { Injectable } from '@angular/core';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { BaseRepository } from '../base-repository';
|
||||
import { BaseRepository, RelationDefinition } from '../base-repository';
|
||||
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 { environment } from '../../../../environments/environment';
|
||||
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';
|
||||
@ -24,6 +23,15 @@ type StringNamingSchema = 'lastCommaFirst' | 'firstSpaceLast';
|
||||
|
||||
type SortProperty = 'first_name' | 'last_name' | 'number';
|
||||
|
||||
const UserRelations: RelationDefinition[] = [
|
||||
{
|
||||
type: 'M2M',
|
||||
ownIdKey: 'groups_id',
|
||||
ownKey: 'groups',
|
||||
foreignModel: ViewGroup
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Repository service for users
|
||||
*
|
||||
@ -57,7 +65,7 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User, UserTi
|
||||
private httpService: HttpService,
|
||||
private configService: ConfigService
|
||||
) {
|
||||
super(DS, dataSend, mapperService, viewModelStoreService, translate, User, [Group]);
|
||||
super(DS, dataSend, mapperService, viewModelStoreService, translate, User, UserRelations);
|
||||
this.sortProperty = this.configService.instant('users_sort_by');
|
||||
this.configService.get<SortProperty>('users_sort_by').subscribe(conf => {
|
||||
this.sortProperty = conf;
|
||||
@ -133,12 +141,14 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User, UserTi
|
||||
return pw;
|
||||
}
|
||||
|
||||
public createViewModel(user: User): ViewUser {
|
||||
const groups = this.viewModelStoreService.getMany(ViewGroup, user.groups_id);
|
||||
const viewUser = new ViewUser(user, groups);
|
||||
viewUser.getFullName = () => this.getFullName(viewUser);
|
||||
viewUser.getShortName = () => this.getShortName(viewUser);
|
||||
return viewUser;
|
||||
/**
|
||||
* Adds teh short and full name to the view user.
|
||||
*/
|
||||
protected createViewModelWithTitles(model: User): ViewUser {
|
||||
const viewModel = super.createViewModelWithTitles(model);
|
||||
viewModel.getFullName = () => this.getFullName(viewModel);
|
||||
viewModel.getShortName = () => this.getShortName(viewModel);
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -25,7 +25,7 @@ export interface NewEntry<V> {
|
||||
newEntry: V;
|
||||
status: CsvImportStatus;
|
||||
errors: string[];
|
||||
duplicates: V[];
|
||||
hasDuplicates: boolean;
|
||||
importTrackId?: number;
|
||||
}
|
||||
|
||||
@ -259,11 +259,11 @@ export abstract class BaseImportService<V extends BaseViewModel> {
|
||||
if (entry.status === 'done') {
|
||||
summary.done += 1;
|
||||
return;
|
||||
} else if (entry.status === 'error' && !entry.duplicates.length) {
|
||||
} else if (entry.status === 'error' && !entry.hasDuplicates) {
|
||||
// errors that are not due to duplicates
|
||||
summary.errors += 1;
|
||||
return;
|
||||
} else if (entry.duplicates.length) {
|
||||
} else if (entry.hasDuplicates) {
|
||||
summary.duplicates += 1;
|
||||
return;
|
||||
} else if (entry.status === 'new') {
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { BaseModel } from '../base/base-model';
|
||||
|
||||
/**
|
||||
* Representation of a speaker in an agenda item.
|
||||
*
|
||||
@ -5,23 +7,28 @@
|
||||
* Part of the 'speakers' list.
|
||||
* @ignore
|
||||
*/
|
||||
export interface Speaker {
|
||||
id: number;
|
||||
user_id: number;
|
||||
export class Speaker extends BaseModel<Speaker> {
|
||||
public static COLLECTIONSTRING = 'agenda/speaker';
|
||||
|
||||
public id: number;
|
||||
public user_id: number;
|
||||
public weight: number;
|
||||
public marked: boolean;
|
||||
public item_id: number;
|
||||
|
||||
/**
|
||||
* ISO datetime string to indicate the begin time of the speech. Empty if
|
||||
* the speaker has not started
|
||||
*/
|
||||
begin_time: string;
|
||||
public begin_time: string;
|
||||
|
||||
/**
|
||||
* ISO datetime string to indicate the end time of the speech. Empty if the
|
||||
* speech has not ended
|
||||
*/
|
||||
end_time: string;
|
||||
public end_time: string;
|
||||
|
||||
weight: number;
|
||||
marked: boolean;
|
||||
item_id: number;
|
||||
public constructor(input?: any) {
|
||||
super(Speaker.COLLECTIONSTRING, input);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Deserializer } from '../base/deserializer';
|
||||
import { PollVoteValue } from 'app/core/ui-services/poll.service';
|
||||
import { BaseModel } from '../base/base-model';
|
||||
|
||||
export interface AssignmentOptionVote {
|
||||
weight: number;
|
||||
@ -12,7 +12,9 @@ export interface AssignmentOptionVote {
|
||||
* part of the 'polls-options'-array in poll
|
||||
* @ignore
|
||||
*/
|
||||
export class AssignmentPollOption extends Deserializer {
|
||||
export class AssignmentPollOption extends BaseModel<AssignmentPollOption> {
|
||||
public static COLLECTIONSTRING = 'assignments/assignment-poll-option';
|
||||
|
||||
public id: number; // The AssignmentUser id of the candidate
|
||||
public candidate_id: number; // the User id of the candidate
|
||||
public is_elected: boolean;
|
||||
@ -21,8 +23,6 @@ export class AssignmentPollOption extends Deserializer {
|
||||
public weight: number; // weight to order the display
|
||||
|
||||
/**
|
||||
* Needs to be completely optional because poll has (yet) the optional parameter 'poll-options'
|
||||
*
|
||||
* @param input
|
||||
*/
|
||||
public constructor(input?: any) {
|
||||
@ -33,6 +33,6 @@ export class AssignmentPollOption extends Deserializer {
|
||||
}
|
||||
});
|
||||
}
|
||||
super(input);
|
||||
super(AssignmentPollOption.COLLECTIONSTRING, input);
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { AssignmentPollMethod } from 'app/site/assignments/services/assignment-poll.service';
|
||||
import { Deserializer } from '../base/deserializer';
|
||||
import { AssignmentPollOption } from './assignment-poll-option';
|
||||
import { BaseModel } from '../base/base-model';
|
||||
|
||||
/**
|
||||
* Content of the 'polls' property of assignments
|
||||
* @ignore
|
||||
*/
|
||||
export class AssignmentPoll extends Deserializer {
|
||||
export class AssignmentPoll extends BaseModel<AssignmentPoll> {
|
||||
public static COLLECTIONSTRING = 'assignments/assignment-poll';
|
||||
private static DECIMAL_FIELDS = ['votesvalid', 'votesinvalid', 'votescast', 'votesno', 'votesabstain'];
|
||||
|
||||
public id: number;
|
||||
@ -23,7 +24,6 @@ export class AssignmentPoll extends Deserializer {
|
||||
public assignment_id: number;
|
||||
|
||||
/**
|
||||
* Needs to be completely optional because assignment has (yet) the optional parameter 'polls'
|
||||
* @param input
|
||||
*/
|
||||
public constructor(input?: any) {
|
||||
@ -35,14 +35,6 @@ export class AssignmentPoll extends Deserializer {
|
||||
}
|
||||
});
|
||||
}
|
||||
super(input);
|
||||
}
|
||||
|
||||
public deserialize(input: any): void {
|
||||
Object.assign(this, input);
|
||||
this.options = [];
|
||||
if (input.options instanceof Array) {
|
||||
this.options = input.options.map(pollOptionData => new AssignmentPollOption(pollOptionData));
|
||||
}
|
||||
super(AssignmentPoll.COLLECTIONSTRING, input);
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +1,18 @@
|
||||
import { BaseModel } from '../base/base-model';
|
||||
|
||||
/**
|
||||
* Content of the 'assignment_related_users' property.
|
||||
*/
|
||||
export interface AssignmentRelatedUser {
|
||||
id: number;
|
||||
export class AssignmentRelatedUser extends BaseModel<AssignmentRelatedUser> {
|
||||
public static COLLECTIONSTRING = 'assignments/assignment-related-user';
|
||||
|
||||
/**
|
||||
* id of the user this assignment user relates to
|
||||
*/
|
||||
user_id: number;
|
||||
public id: number;
|
||||
public user_id: number;
|
||||
public elected: boolean;
|
||||
public assignment_id: number;
|
||||
public weight: number;
|
||||
|
||||
/**
|
||||
* The current 'elected' state
|
||||
*/
|
||||
elected: boolean;
|
||||
|
||||
/**
|
||||
* id of the related assignment
|
||||
*/
|
||||
assignment_id: number;
|
||||
|
||||
/**
|
||||
* A weight to determine the position in the list of candidates
|
||||
* (determined by the server)
|
||||
*/
|
||||
weight: number;
|
||||
public constructor(input?: any) {
|
||||
super(AssignmentRelatedUser.COLLECTIONSTRING, input);
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +0,0 @@
|
||||
import { Deserializer } from '../base/deserializer';
|
||||
import { User } from '../users/user';
|
||||
|
||||
/**
|
||||
* Representation of a Motion Submitter.
|
||||
*
|
||||
* @ignore
|
||||
*/
|
||||
export class MotionSubmitter extends Deserializer {
|
||||
public id: number;
|
||||
public user_id: number;
|
||||
public motion_id: number;
|
||||
public weight: number;
|
||||
|
||||
public constructor(input?: any, motion_id?: number, weight?: number) {
|
||||
super();
|
||||
this.id = input.id;
|
||||
if (input instanceof User) {
|
||||
const user_obj = input as User;
|
||||
this.user_id = user_obj.id;
|
||||
this.motion_id = motion_id;
|
||||
this.weight = weight;
|
||||
} else {
|
||||
this.deserialize(input);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { MotionSubmitter } from './motion-submitter';
|
||||
import { Submitter } from './submitter';
|
||||
import { MotionPoll } from './motion-poll';
|
||||
import { BaseModelWithAgendaItemAndListOfSpeakers } from '../base/base-model-with-agenda-item-and-list-of-speakers';
|
||||
|
||||
@ -31,7 +31,7 @@ export class Motion extends BaseModelWithAgendaItemAndListOfSpeakers<Motion> {
|
||||
public category_weight: number;
|
||||
public motion_block_id: number;
|
||||
public origin: string;
|
||||
public submitters: MotionSubmitter[];
|
||||
public submitters: Submitter[];
|
||||
public supporters_id: number[];
|
||||
public comments: MotionComment[];
|
||||
public workflow_id: number;
|
||||
@ -48,6 +48,7 @@ export class Motion extends BaseModelWithAgendaItemAndListOfSpeakers<Motion> {
|
||||
public sort_parent_id: number;
|
||||
public created: string;
|
||||
public last_modified: string;
|
||||
public change_recommendations_id: number[];
|
||||
|
||||
public constructor(input?: any) {
|
||||
super(Motion.COLLECTIONSTRING, input);
|
||||
@ -58,9 +59,9 @@ export class Motion extends BaseModelWithAgendaItemAndListOfSpeakers<Motion> {
|
||||
*/
|
||||
public get sorted_submitters_id(): number[] {
|
||||
return this.submitters
|
||||
.sort((a: MotionSubmitter, b: MotionSubmitter) => {
|
||||
.sort((a: Submitter, b: Submitter) => {
|
||||
return a.weight - b.weight;
|
||||
})
|
||||
.map((submitter: MotionSubmitter) => submitter.user_id);
|
||||
.map((submitter: Submitter) => submitter.user_id);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { Deserializer } from '../base/deserializer';
|
||||
import { Workflow } from './workflow';
|
||||
import { BaseModel } from '../base/base-model';
|
||||
|
||||
/**
|
||||
* Specifies if an amendment of this state/recommendation should be merged into the motion
|
||||
@ -16,7 +15,9 @@ export enum MergeAmendment {
|
||||
* Part of the 'states'-array in motion/workflow
|
||||
* @ignore
|
||||
*/
|
||||
export class WorkflowState extends Deserializer {
|
||||
export class State extends BaseModel<State> {
|
||||
public static COLLECTIONSTRING = 'motions/state';
|
||||
|
||||
public id: number;
|
||||
public name: string;
|
||||
public recommendation_label: string;
|
||||
@ -37,23 +38,7 @@ export class WorkflowState extends Deserializer {
|
||||
* @param input If given, it will be deserialized
|
||||
*/
|
||||
public constructor(input?: any) {
|
||||
super(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* return a list of the next possible states.
|
||||
* Also adds the current state.
|
||||
*/
|
||||
public getNextStates(workflow: Workflow): WorkflowState[] {
|
||||
return workflow.states.filter(state => {
|
||||
return this.next_states_id.includes(state.id);
|
||||
});
|
||||
}
|
||||
|
||||
public getPreviousStates(workflow: Workflow): WorkflowState[] {
|
||||
return workflow.states.filter(state => {
|
||||
return state.next_states_id.includes(this.id);
|
||||
});
|
||||
super(State.COLLECTIONSTRING, input);
|
||||
}
|
||||
|
||||
public toString = (): string => {
|
19
client/src/app/shared/models/motions/submitter.ts
Normal file
19
client/src/app/shared/models/motions/submitter.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { BaseModel } from '../base/base-model';
|
||||
|
||||
/**
|
||||
* Representation of a Motion Submitter.
|
||||
*
|
||||
* @ignore
|
||||
*/
|
||||
export class Submitter extends BaseModel {
|
||||
public static COLLECTIONSTRING = 'motions/submitter';
|
||||
|
||||
public id: number;
|
||||
public user_id: number;
|
||||
public motion_id: number;
|
||||
public weight: number;
|
||||
|
||||
public constructor(input?: any) {
|
||||
super(Submitter.COLLECTIONSTRING, input);
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
import { BaseModel } from '../base/base-model';
|
||||
import { WorkflowState } from './workflow-state';
|
||||
|
||||
/**
|
||||
* Representation of a motion workflow. Has the nested property 'states'
|
||||
@ -10,40 +9,10 @@ export class Workflow extends BaseModel<Workflow> {
|
||||
|
||||
public id: number;
|
||||
public name: string;
|
||||
public states: WorkflowState[];
|
||||
public states_id: number[];
|
||||
public first_state_id: number;
|
||||
|
||||
public constructor(input?: any) {
|
||||
super(Workflow.COLLECTIONSTRING, input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the containing @link{WorkflowState}s contain a given ID
|
||||
* @param id The State ID
|
||||
*/
|
||||
public isStateContained(obj: number | WorkflowState): boolean {
|
||||
let id: number;
|
||||
if (obj instanceof WorkflowState) {
|
||||
id = obj.id;
|
||||
} else {
|
||||
id = obj;
|
||||
}
|
||||
|
||||
return this.states.some(state => state.id === id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the states of this workflow
|
||||
*/
|
||||
public sortStates(): void {
|
||||
this.states.sort((a, b) => (a.id > b.id ? 1 : -1));
|
||||
}
|
||||
|
||||
public deserialize(input: any): void {
|
||||
Object.assign(this, input);
|
||||
|
||||
if (input.states instanceof Array) {
|
||||
this.states = input.states.map(workflowStateData => new WorkflowState(workflowStateData));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,5 @@
|
||||
import { Item, ItemVisibilityChoices } from 'app/shared/models/agenda/item';
|
||||
import {
|
||||
BaseViewModelWithAgendaItem,
|
||||
isBaseViewModelWithAgendaItem
|
||||
} from 'app/site/base/base-view-model-with-agenda-item';
|
||||
import { BaseViewModelWithAgendaItem } 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';
|
||||
|
||||
@ -87,7 +84,7 @@ export class ViewItem extends BaseViewModelWithContentObject<Item, BaseViewModel
|
||||
return this.item.parent_id;
|
||||
}
|
||||
|
||||
public constructor(item: Item, contentObject?: BaseViewModelWithAgendaItem) {
|
||||
super(Item.COLLECTIONSTRING, item, isBaseViewModelWithAgendaItem, 'BaseViewModelWithAgendaItem', contentObject);
|
||||
public constructor(item: Item) {
|
||||
super(Item.COLLECTIONSTRING, item);
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,8 @@
|
||||
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 { BaseViewModelWithListOfSpeakers } from 'app/site/base/base-view-model-with-list-of-speakers';
|
||||
import { BaseViewModelWithContentObject } from 'app/site/base/base-view-model-with-content-object';
|
||||
import { ContentObject } from 'app/shared/models/base/content-object';
|
||||
|
||||
@ -31,7 +26,7 @@ export class ViewListOfSpeakers extends BaseViewModelWithContentObject<ListOfSpe
|
||||
}
|
||||
|
||||
public get speakers(): ViewSpeaker[] {
|
||||
return this._speakers;
|
||||
return this._speakers || [];
|
||||
}
|
||||
|
||||
public get title_information(): object {
|
||||
@ -53,19 +48,8 @@ export class ViewListOfSpeakers extends BaseViewModelWithContentObject<ListOfSpe
|
||||
return `/agenda/speakers/${this.id}`;
|
||||
}
|
||||
|
||||
public constructor(
|
||||
listOfSpeakers: ListOfSpeakers,
|
||||
speakers: ViewSpeaker[],
|
||||
contentObject?: BaseViewModelWithListOfSpeakers
|
||||
) {
|
||||
super(
|
||||
Item.COLLECTIONSTRING,
|
||||
listOfSpeakers,
|
||||
isBaseViewModelWithListOfSpeakers,
|
||||
'BaseViewModelWithListOfSpeakers',
|
||||
contentObject
|
||||
);
|
||||
this._speakers = speakers;
|
||||
public constructor(listOfSpeakers: ListOfSpeakers) {
|
||||
super(Item.COLLECTIONSTRING, listOfSpeakers);
|
||||
}
|
||||
|
||||
public getProjectorTitle(): string {
|
||||
@ -84,12 +68,4 @@ export class ViewListOfSpeakers extends BaseViewModelWithContentObject<ListOfSpe
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { BaseViewModel } from 'app/site/base/base-view-model';
|
||||
import { Speaker } from 'app/shared/models/agenda/speaker';
|
||||
import { ViewUser } from 'app/site/users/models/view-user';
|
||||
import { Updateable } from 'app/site/base/updateable';
|
||||
import { Identifiable } from 'app/shared/models/base/identifiable';
|
||||
|
||||
/**
|
||||
* Determine the state of the speaker
|
||||
@ -16,15 +14,15 @@ export enum SpeakerState {
|
||||
/**
|
||||
* Provides "safe" access to a speaker with all it's components
|
||||
*/
|
||||
export class ViewSpeaker implements Updateable, Identifiable {
|
||||
private _speaker: Speaker;
|
||||
export class ViewSpeaker extends BaseViewModel<Speaker> {
|
||||
public static COLLECTIONSTRING = Speaker.COLLECTIONSTRING;
|
||||
private _user?: ViewUser;
|
||||
|
||||
public get speaker(): Speaker {
|
||||
return this._speaker;
|
||||
return this._model;
|
||||
}
|
||||
|
||||
public get user(): ViewUser {
|
||||
public get user(): ViewUser | null {
|
||||
return this._user;
|
||||
}
|
||||
|
||||
@ -82,20 +80,11 @@ export class ViewSpeaker implements Updateable, Identifiable {
|
||||
return this.user ? this.user.gender : '';
|
||||
}
|
||||
|
||||
public constructor(speaker: Speaker, user?: ViewUser) {
|
||||
this._speaker = speaker;
|
||||
this._user = user;
|
||||
public constructor(speaker: Speaker) {
|
||||
super(Speaker.COLLECTIONSTRING, speaker);
|
||||
}
|
||||
|
||||
public getTitle = () => {
|
||||
return this.name;
|
||||
};
|
||||
|
||||
public updateDependencies(update: BaseViewModel): boolean {
|
||||
if (update instanceof ViewUser && update.id === this.speaker.user_id) {
|
||||
this._user = update;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ export class AgendaImportService extends BaseImportService<ViewCreateTopic> {
|
||||
newEntry[this.expectedHeader[idx]] = line[idx];
|
||||
}
|
||||
}
|
||||
const updateModels = this.repo.getTopicDuplicates(newEntry) as ViewCreateTopic[];
|
||||
const hasDuplicates = this.repo.getViewModelList().some(topic => topic.title === newEntry.title);
|
||||
|
||||
// set type to 'public' if none is given in import
|
||||
if (!newEntry.type) {
|
||||
@ -108,12 +108,11 @@ export class AgendaImportService extends BaseImportService<ViewCreateTopic> {
|
||||
}
|
||||
const mappedEntry: NewEntry<ViewCreateTopic> = {
|
||||
newEntry: newEntry,
|
||||
duplicates: [],
|
||||
hasDuplicates: hasDuplicates,
|
||||
status: 'new',
|
||||
errors: []
|
||||
};
|
||||
if (updateModels.length) {
|
||||
mappedEntry.duplicates = updateModels;
|
||||
if (hasDuplicates) {
|
||||
this.setError(mappedEntry, 'Duplicates');
|
||||
}
|
||||
if (hasErrors) {
|
||||
@ -198,17 +197,14 @@ export class AgendaImportService extends BaseImportService<ViewCreateTopic> {
|
||||
agenda_type: 1 // set type to 'public item' by default
|
||||
})
|
||||
);
|
||||
const hasDuplicates = this.repo.getViewModelList().some(topic => topic.title === newTopic.title);
|
||||
const newEntry: NewEntry<ViewCreateTopic> = {
|
||||
newEntry: newTopic,
|
||||
duplicates: [],
|
||||
hasDuplicates: hasDuplicates,
|
||||
status: 'new',
|
||||
errors: []
|
||||
};
|
||||
const duplicates = this.repo.getTopicDuplicates(newTopic);
|
||||
if (duplicates.length) {
|
||||
// TODO duplicates are not really ViewCreateTopics, but ViewTopics.
|
||||
// TODO this should be fine as the duplicates will not be created
|
||||
newEntry.duplicates = duplicates as ViewCreateTopic[];
|
||||
if (hasDuplicates) {
|
||||
this.setError(newEntry, 'Duplicates');
|
||||
}
|
||||
newEntries.push(newEntry);
|
||||
|
@ -162,10 +162,10 @@
|
||||
<div>
|
||||
<div
|
||||
class="candidates-list"
|
||||
*ngIf="assignment && assignment.assignmentRelatedUsers && assignment.assignmentRelatedUsers.length > 0"
|
||||
*ngIf="assignment && assignment.assignment_related_users && assignment.assignment_related_users.length > 0"
|
||||
>
|
||||
<os-sorting-list
|
||||
[input]="assignment.assignmentRelatedUsers"
|
||||
[input]="assignment.assignment_related_users"
|
||||
[live]="true"
|
||||
[count]="true"
|
||||
[enable]="hasPerms('addOthers')"
|
||||
|
@ -388,7 +388,7 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
||||
super.setTitle('New election');
|
||||
this.newAssignment = true;
|
||||
// TODO set defaults?
|
||||
this.assignment = new ViewAssignment(new Assignment(), [], []);
|
||||
this.assignment = new ViewAssignment(new Assignment());
|
||||
this.patchForm(this.assignment);
|
||||
this.setEditMode(true);
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ export class AssignmentListComponent extends BaseListViewComponent<ViewAssignmen
|
||||
/**
|
||||
* Define extra filter properties
|
||||
*/
|
||||
public filterProps = ['title', 'candidates', 'assignmentRelatedUsers', 'tags', 'candidateAmount'];
|
||||
public filterProps = ['title', 'candidates', 'assignment_related_users', 'tags', 'candidateAmount'];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
|
@ -169,7 +169,8 @@
|
||||
<div *ngIf="!pollData">
|
||||
<h4 translate>Candidates</h4>
|
||||
<div *ngFor="let option of poll.options">
|
||||
<span class="accent"> {{ option.user.full_name }}</span>
|
||||
<span class="accent" *ngIf="option.user">{{ option.user.getFullName() }}</span>
|
||||
<span *ngIf="!option.user">No user {{ option.user_id }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -236,7 +236,7 @@ export class AssignmentPollComponent extends BaseViewComponent implements OnInit
|
||||
}
|
||||
|
||||
// TODO additional conditions: assignment not finished?
|
||||
const viewAssignmentRelatedUser = this.assignment.assignmentRelatedUsers.find(
|
||||
const viewAssignmentRelatedUser = this.assignment.assignment_related_users.find(
|
||||
user => user.user_id === option.user_id
|
||||
);
|
||||
if (viewAssignmentRelatedUser) {
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { AssignmentPollOption, AssignmentOptionVote } from 'app/shared/models/assignments/assignment-poll-option';
|
||||
import { BaseViewModel } from 'app/site/base/base-view-model';
|
||||
import { Identifiable } from 'app/shared/models/base/identifiable';
|
||||
import { PollVoteValue } from 'app/core/ui-services/poll.service';
|
||||
import { Updateable } from 'app/site/base/updateable';
|
||||
import { ViewUser } from 'app/site/users/models/view-user';
|
||||
|
||||
/**
|
||||
@ -10,12 +8,12 @@ import { ViewUser } from 'app/site/users/models/view-user';
|
||||
*/
|
||||
const votesOrder: PollVoteValue[] = ['Votes', 'Yes', 'No', 'Abstain'];
|
||||
|
||||
export class ViewAssignmentPollOption implements Identifiable, Updateable {
|
||||
private _assignmentPollOption: AssignmentPollOption;
|
||||
private _user: ViewUser; // This is the "candidate". We'll stay consistent wich user here...
|
||||
export class ViewAssignmentPollOption extends BaseViewModel<AssignmentPollOption> {
|
||||
public static COLLECTIONSTRING = AssignmentPollOption.COLLECTIONSTRING;
|
||||
private _user?: ViewUser; // This is the "candidate". We'll stay consistent wich user here...
|
||||
|
||||
public get option(): AssignmentPollOption {
|
||||
return this._assignmentPollOption;
|
||||
return this._model;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -52,14 +50,7 @@ export class ViewAssignmentPollOption implements Identifiable, Updateable {
|
||||
return this.option.weight;
|
||||
}
|
||||
|
||||
public constructor(assignmentPollOption: AssignmentPollOption, user?: ViewUser) {
|
||||
this._assignmentPollOption = assignmentPollOption;
|
||||
this._user = user;
|
||||
}
|
||||
|
||||
public updateDependencies(update: BaseViewModel): void {
|
||||
if (update instanceof ViewUser && update.id === this.user_id) {
|
||||
this._user = update;
|
||||
}
|
||||
public constructor(assignmentPollOption: AssignmentPollOption) {
|
||||
super(AssignmentPollOption.COLLECTIONSTRING, assignmentPollOption);
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,20 @@
|
||||
import { BaseViewModel } from 'app/site/base/base-view-model';
|
||||
import { Updateable } from 'app/site/base/updateable';
|
||||
import { Identifiable } from 'app/shared/models/base/identifiable';
|
||||
import { AssignmentPoll } from 'app/shared/models/assignments/assignment-poll';
|
||||
import { AssignmentPollMethod } from '../services/assignment-poll.service';
|
||||
import { ViewAssignmentPollOption } from './view-assignment-poll-option';
|
||||
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
|
||||
import { BaseProjectableViewModel } from 'app/site/base/base-projectable-view-model';
|
||||
import { AssignmentPollOption } from 'app/shared/models/assignments/assignment-poll-option';
|
||||
import { Projectable, ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
|
||||
|
||||
export class ViewAssignmentPoll implements Identifiable, Updateable, Projectable {
|
||||
private _assignmentPoll: AssignmentPoll;
|
||||
private _assignmentPollOptions: ViewAssignmentPollOption[];
|
||||
export class ViewAssignmentPoll extends BaseProjectableViewModel<AssignmentPoll> {
|
||||
public static COLLECTIONSTRING = AssignmentPoll.COLLECTIONSTRING;
|
||||
private _options: ViewAssignmentPollOption[];
|
||||
|
||||
public get poll(): AssignmentPoll {
|
||||
return this._assignmentPoll;
|
||||
return this._model;
|
||||
}
|
||||
|
||||
public get options(): ViewAssignmentPollOption[] {
|
||||
return this._assignmentPollOptions;
|
||||
return this._options;
|
||||
}
|
||||
|
||||
public get id(): number {
|
||||
@ -78,43 +76,38 @@ export class ViewAssignmentPoll implements Identifiable, Updateable, Projectable
|
||||
return this.poll.assignment_id;
|
||||
}
|
||||
|
||||
public constructor(assignmentPoll: AssignmentPoll, assignmentPollOptions: ViewAssignmentPollOption[]) {
|
||||
this._assignmentPoll = assignmentPoll;
|
||||
this._assignmentPollOptions = assignmentPollOptions;
|
||||
public constructor(assignmentPoll: AssignmentPoll) {
|
||||
super(AssignmentPoll.COLLECTIONSTRING, assignmentPoll);
|
||||
}
|
||||
|
||||
public updateDependencies(update: BaseViewModel): void {
|
||||
this.options.forEach(option => option.updateDependencies(update));
|
||||
}
|
||||
public getTitle = () => {
|
||||
return 'Poll';
|
||||
};
|
||||
|
||||
public getTitle(): string {
|
||||
return 'TODO';
|
||||
}
|
||||
|
||||
public getListTitle(): string {
|
||||
public getListTitle = () => {
|
||||
return this.getTitle();
|
||||
}
|
||||
};
|
||||
|
||||
public getProjectorTitle(): string {
|
||||
public getProjectorTitle = () => {
|
||||
return this.getTitle();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a copy with deep-copy on all changing numerical values,
|
||||
* but intact uncopied references to the users
|
||||
*
|
||||
* TODO check and review
|
||||
* TODO: This MUST NOT be done this way. Do not create ViewModels on your own...
|
||||
*/
|
||||
public copy(): ViewAssignmentPoll {
|
||||
return new ViewAssignmentPoll(
|
||||
new AssignmentPoll(JSON.parse(JSON.stringify(this._assignmentPoll))),
|
||||
this._assignmentPollOptions.map(option => {
|
||||
return new ViewAssignmentPollOption(
|
||||
new AssignmentPollOption(JSON.parse(JSON.stringify(option.option))),
|
||||
option.user
|
||||
);
|
||||
})
|
||||
);
|
||||
const poll = new ViewAssignmentPoll(new AssignmentPoll(JSON.parse(JSON.stringify(this.poll))));
|
||||
(<any>poll)._options = this.options.map(option => {
|
||||
const polloption = new ViewAssignmentPollOption(
|
||||
new AssignmentPollOption(JSON.parse(JSON.stringify(option.option)))
|
||||
);
|
||||
(<any>polloption)._user = option.user;
|
||||
return polloption;
|
||||
});
|
||||
return poll;
|
||||
}
|
||||
|
||||
public getSlide(): ProjectorElementBuildDeskriptor {
|
||||
|
@ -1,16 +1,14 @@
|
||||
import { AssignmentRelatedUser } from 'app/shared/models/assignments/assignment-related-user';
|
||||
import { BaseViewModel } from 'app/site/base/base-view-model';
|
||||
import { Displayable } from 'app/site/base/displayable';
|
||||
import { Identifiable } from 'app/shared/models/base/identifiable';
|
||||
import { Updateable } from 'app/site/base/updateable';
|
||||
import { ViewUser } from 'app/site/users/models/view-user';
|
||||
|
||||
export class ViewAssignmentRelatedUser implements Updateable, Identifiable, Displayable {
|
||||
private _assignmentRelatedUser: AssignmentRelatedUser;
|
||||
export class ViewAssignmentRelatedUser extends BaseViewModel<AssignmentRelatedUser> {
|
||||
public static COLLECTIONSTRING = AssignmentRelatedUser.COLLECTIONSTRING;
|
||||
|
||||
private _user?: ViewUser;
|
||||
|
||||
public get assignmentRelatedUser(): AssignmentRelatedUser {
|
||||
return this._assignmentRelatedUser;
|
||||
return this._model;
|
||||
}
|
||||
|
||||
public get user(): ViewUser {
|
||||
@ -37,22 +35,13 @@ export class ViewAssignmentRelatedUser implements Updateable, Identifiable, Disp
|
||||
return this.assignmentRelatedUser.weight;
|
||||
}
|
||||
|
||||
public constructor(assignmentRelatedUser: AssignmentRelatedUser, user?: ViewUser) {
|
||||
this._assignmentRelatedUser = assignmentRelatedUser;
|
||||
this._user = user;
|
||||
public getListTitle: () => string = this.getTitle;
|
||||
|
||||
public constructor(assignmentRelatedUser: AssignmentRelatedUser) {
|
||||
super(AssignmentRelatedUser.COLLECTIONSTRING, assignmentRelatedUser);
|
||||
}
|
||||
|
||||
public updateDependencies(update: BaseViewModel): void {
|
||||
if (update instanceof ViewUser && update.id === this.user_id) {
|
||||
this._user = update;
|
||||
}
|
||||
}
|
||||
|
||||
public getTitle(): string {
|
||||
return this.user ? this.user.getTitle() : '';
|
||||
}
|
||||
|
||||
public getListTitle(): string {
|
||||
return this.getTitle();
|
||||
}
|
||||
public getTitle: () => string = () => {
|
||||
return this.user ? this.user.getFullName() : '';
|
||||
};
|
||||
}
|
||||
|
@ -2,14 +2,11 @@ import { Assignment } from 'app/shared/models/assignments/assignment';
|
||||
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';
|
||||
import { ViewItem } from 'app/site/agenda/models/view-item';
|
||||
import { ViewTag } from 'app/site/tags/models/view-tag';
|
||||
import { BaseViewModel } from 'app/site/base/base-view-model';
|
||||
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 {
|
||||
@ -43,8 +40,8 @@ export class ViewAssignment extends BaseViewModelWithAgendaItemAndListOfSpeakers
|
||||
implements AssignmentTitleInformation {
|
||||
public static COLLECTIONSTRING = Assignment.COLLECTIONSTRING;
|
||||
|
||||
private _assignmentRelatedUsers: ViewAssignmentRelatedUser[];
|
||||
private _assignmentPolls: ViewAssignmentPoll[];
|
||||
private _assignment_related_users?: ViewAssignmentRelatedUser[];
|
||||
private _polls?: ViewAssignmentPoll[];
|
||||
private _tags?: ViewTag[];
|
||||
private _attachments?: ViewMediafile[];
|
||||
|
||||
@ -53,7 +50,7 @@ export class ViewAssignment extends BaseViewModelWithAgendaItemAndListOfSpeakers
|
||||
}
|
||||
|
||||
public get polls(): ViewAssignmentPoll[] {
|
||||
return this._assignmentPolls;
|
||||
return this._polls || [];
|
||||
}
|
||||
|
||||
public get title(): string {
|
||||
@ -69,21 +66,29 @@ export class ViewAssignment extends BaseViewModelWithAgendaItemAndListOfSpeakers
|
||||
}
|
||||
|
||||
public get candidates(): ViewUser[] {
|
||||
return this._assignmentRelatedUsers.map(aru => aru.user);
|
||||
return this.assignment_related_users.map(aru => aru.user).filter(x => !!x);
|
||||
}
|
||||
|
||||
public get assignmentRelatedUsers(): ViewAssignmentRelatedUser[] {
|
||||
return this._assignmentRelatedUsers;
|
||||
public get assignment_related_users(): ViewAssignmentRelatedUser[] {
|
||||
return this._assignment_related_users || [];
|
||||
}
|
||||
|
||||
public get tags(): ViewTag[] {
|
||||
return this._tags || [];
|
||||
}
|
||||
|
||||
public get tags_id(): number[] {
|
||||
return this.assignment.tags_id;
|
||||
}
|
||||
|
||||
public get attachments(): ViewMediafile[] {
|
||||
return this._attachments || [];
|
||||
}
|
||||
|
||||
public get attachments_id(): number[] {
|
||||
return this.assignment.attachments_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* unknown where the identifier to the phase is get
|
||||
*/
|
||||
@ -117,46 +122,11 @@ export class ViewAssignment extends BaseViewModelWithAgendaItemAndListOfSpeakers
|
||||
* @returns the amount of candidates in the assignment's candidate list
|
||||
*/
|
||||
public get candidateAmount(): number {
|
||||
return this._assignmentRelatedUsers ? this._assignmentRelatedUsers.length : 0;
|
||||
return this._assignment_related_users ? this._assignment_related_users.length : 0;
|
||||
}
|
||||
|
||||
public constructor(
|
||||
assignment: Assignment,
|
||||
assignmentRelatedUsers: ViewAssignmentRelatedUser[],
|
||||
assignmentPolls: ViewAssignmentPoll[],
|
||||
item?: ViewItem,
|
||||
listOfSpeakers?: ViewListOfSpeakers,
|
||||
tags?: ViewTag[],
|
||||
attachments?: ViewMediafile[]
|
||||
) {
|
||||
super(Assignment.COLLECTIONSTRING, assignment, item, listOfSpeakers);
|
||||
|
||||
this._assignmentRelatedUsers = assignmentRelatedUsers;
|
||||
this._assignmentPolls = assignmentPolls;
|
||||
this._tags = tags;
|
||||
this._attachments = attachments;
|
||||
}
|
||||
|
||||
public updateDependencies(update: BaseViewModel): void {
|
||||
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);
|
||||
} else {
|
||||
this._tags[tagIndex] = update;
|
||||
}
|
||||
} else if (update instanceof ViewUser) {
|
||||
this.assignmentRelatedUsers.forEach(aru => aru.updateDependencies(update));
|
||||
this.polls.forEach(poll => poll.updateDependencies(update));
|
||||
} else if (update instanceof ViewMediafile && this.assignment.attachments_id.includes(update.id)) {
|
||||
const mediafileIndex = this._attachments.findIndex(_mediafile => _mediafile.id === update.id);
|
||||
if (mediafileIndex < 0) {
|
||||
this._attachments.push(update);
|
||||
} else {
|
||||
this._attachments[mediafileIndex] = update;
|
||||
}
|
||||
}
|
||||
public constructor(assignment: Assignment) {
|
||||
super(Assignment.COLLECTIONSTRING, assignment);
|
||||
}
|
||||
|
||||
public formatForSearch(): SearchRepresentation {
|
||||
|
@ -127,7 +127,7 @@ export class AssignmentPdfService {
|
||||
*/
|
||||
private createCandidateList(assignment: ViewAssignment): object {
|
||||
if (assignment.phase !== 2) {
|
||||
const candidates = assignment.assignmentRelatedUsers.sort((a, b) => a.weight - b.weight);
|
||||
const candidates = assignment.assignment_related_users.sort((a, b) => a.weight - b.weight);
|
||||
|
||||
const candidatesText = `${this.translate.instant('Candidates')}: `;
|
||||
const userList = candidates.map(candidate => {
|
||||
|
@ -191,10 +191,7 @@ export abstract class BaseImportListComponent<V extends BaseViewModel> extends B
|
||||
};
|
||||
} else if (this.shown === 'error') {
|
||||
this.dataSource.filterPredicate = (data, filter) => {
|
||||
if (data.errors.length || data.duplicates.length) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return !!data.errors.length || data.hasDuplicates;
|
||||
};
|
||||
}
|
||||
this.dataSource.filter = 'X'; // TODO: This is just a bogus non-null string to trigger the filter
|
||||
|
@ -8,7 +8,6 @@ import {
|
||||
isBaseViewModelWithListOfSpeakers,
|
||||
IBaseViewModelWithListOfSpeakers
|
||||
} from './base-view-model-with-list-of-speakers';
|
||||
import { BaseViewModel } from './base-view-model';
|
||||
|
||||
export function isBaseViewModelWithAgendaItemAndListOfSpeakers(
|
||||
obj: any
|
||||
@ -23,7 +22,7 @@ export abstract class BaseViewModelWithAgendaItemAndListOfSpeakers<
|
||||
M extends BaseModelWithAgendaItemAndListOfSpeakers = any
|
||||
> extends BaseProjectableViewModel implements IBaseViewModelWithAgendaItem<M>, IBaseViewModelWithListOfSpeakers<M> {
|
||||
protected _item?: ViewItem;
|
||||
protected _listOfSpeakers?: ViewListOfSpeakers;
|
||||
protected _list_of_speakers?: ViewListOfSpeakers;
|
||||
|
||||
public get agendaItem(): ViewItem | null {
|
||||
return this._item;
|
||||
@ -38,7 +37,7 @@ export abstract class BaseViewModelWithAgendaItemAndListOfSpeakers<
|
||||
}
|
||||
|
||||
public get listOfSpeakers(): ViewListOfSpeakers | null {
|
||||
return this._listOfSpeakers;
|
||||
return this._list_of_speakers;
|
||||
}
|
||||
|
||||
public get list_of_speakers_id(): number {
|
||||
@ -50,11 +49,8 @@ export abstract class BaseViewModelWithAgendaItemAndListOfSpeakers<
|
||||
public getListOfSpeakersTitle: () => string;
|
||||
public getListOfSpeakersSlideTitle: () => string;
|
||||
|
||||
public constructor(collectionString: string, model: M, item?: ViewItem, listOfSpeakers?: ViewListOfSpeakers) {
|
||||
public constructor(collectionString: string, model: M) {
|
||||
super(collectionString, model);
|
||||
// Explicit set to null instead of undefined, if not given
|
||||
this._item = item || null;
|
||||
this._listOfSpeakers = listOfSpeakers || null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -71,12 +67,4 @@ export abstract class BaseViewModelWithAgendaItemAndListOfSpeakers<
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,8 +3,7 @@ import { SearchRepresentation } from 'app/core/ui-services/search.service';
|
||||
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';
|
||||
import { TitleInformation } from './base-view-model';
|
||||
|
||||
export function isBaseViewModelWithAgendaItem(obj: any): obj is BaseViewModelWithAgendaItem {
|
||||
const model = <BaseViewModelWithAgendaItem>obj;
|
||||
@ -105,11 +104,4 @@ export abstract class BaseViewModelWithAgendaItem<M extends BaseModelWithAgendaI
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,38 +26,8 @@ export abstract class BaseViewModelWithContentObject<
|
||||
/**
|
||||
* @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
|
||||
) {
|
||||
public constructor(collectionString: string, model: M) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { BaseProjectableViewModel } from './base-projectable-view-model';
|
||||
import { isDetailNavigable, DetailNavigable } from 'app/shared/models/base/detail-navigable';
|
||||
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;
|
||||
@ -38,10 +36,10 @@ export interface IBaseViewModelWithListOfSpeakers<M extends BaseModelWithListOfS
|
||||
export abstract class BaseViewModelWithListOfSpeakers<M extends BaseModelWithListOfSpeakers = any>
|
||||
extends BaseProjectableViewModel<M>
|
||||
implements IBaseViewModelWithListOfSpeakers<M> {
|
||||
protected _listOfSpeakers?: any;
|
||||
protected _list_of_speakers?: any;
|
||||
|
||||
public get listOfSpeakers(): any | null {
|
||||
return this._listOfSpeakers;
|
||||
return this._list_of_speakers;
|
||||
}
|
||||
|
||||
public get list_of_speakers_id(): number {
|
||||
@ -53,15 +51,8 @@ export abstract class BaseViewModelWithListOfSpeakers<M extends BaseModelWithLis
|
||||
|
||||
public constructor(collectionString: string, model: M, listOfSpeakers?: any) {
|
||||
super(collectionString, model);
|
||||
this._listOfSpeakers = listOfSpeakers || null; // Explicit set to null instead of undefined, if not given
|
||||
this._list_of_speakers = 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ import { Displayable } from './displayable';
|
||||
import { Identifiable } from '../../shared/models/base/identifiable';
|
||||
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;
|
||||
|
||||
@ -14,8 +13,7 @@ export interface ViewModelConstructor<T extends BaseViewModel> {
|
||||
/**
|
||||
* Base class for view models. alls view models should have titles.
|
||||
*/
|
||||
export abstract class BaseViewModel<M extends BaseModel = any>
|
||||
implements Displayable, Identifiable, Collection, Updateable {
|
||||
export abstract class BaseViewModel<M extends BaseModel = any> implements Displayable, Identifiable, Collection {
|
||||
protected _model: M;
|
||||
|
||||
public get id(): number {
|
||||
@ -72,8 +70,6 @@ export abstract class BaseViewModel<M extends BaseModel = any>
|
||||
return this._model;
|
||||
}
|
||||
|
||||
public abstract updateDependencies(update: BaseViewModel): void;
|
||||
|
||||
public toString(): string {
|
||||
return this.getTitle();
|
||||
}
|
||||
|
@ -94,8 +94,6 @@ export class ViewConfig extends BaseViewModel<Config> implements ConfigTitleInfo
|
||||
super(Config.COLLECTIONSTRING, config);
|
||||
}
|
||||
|
||||
public updateDependencies(update: BaseViewModel): void {}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
|
@ -52,8 +52,8 @@
|
||||
{{ directory.title }}
|
||||
</span>
|
||||
</span>
|
||||
<span class="visibility" *ngIf="canEdit && directory && directory.inherited_access_groups_id !== true">
|
||||
<span class="visible-for" *ngIf="directory.has_inherited_access_groups" translate>
|
||||
<span class="visibility" *ngIf="canEdit && directory && directory.has_inherited_access_groups">
|
||||
<span class="visible-for" translate>
|
||||
<os-icon-container
|
||||
icon="visibility"
|
||||
matTooltip="{{ 'Allowed access groups for this directory' | translate }}"
|
||||
@ -64,6 +64,16 @@
|
||||
</os-icon-container>
|
||||
</span>
|
||||
</span>
|
||||
<span class="visibility" *ngIf="canEdit && directory && directory.inherited_access_groups_id === false">
|
||||
<span class="visible-for">
|
||||
<os-icon-container
|
||||
icon="visibility"
|
||||
matTooltip="{{ 'Allowed access groups for this directory' | translate }}"
|
||||
>
|
||||
<span translate>Noone</span>
|
||||
</os-icon-container>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
</div>
|
||||
|
@ -1,10 +1,8 @@
|
||||
import { BaseViewModel } from '../../base/base-view-model';
|
||||
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 { 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';
|
||||
import { ViewGroup } from 'app/site/users/models/view-group';
|
||||
|
||||
export const IMAGE_MIMETYPES = ['image/png', 'image/jpeg', 'image/gif'];
|
||||
@ -95,17 +93,8 @@ export class ViewMediafile extends BaseViewModelWithListOfSpeakers<Mediafile>
|
||||
return this.mediafile.create_timestamp ? this.mediafile.create_timestamp : null;
|
||||
}
|
||||
|
||||
public constructor(
|
||||
mediafile: Mediafile,
|
||||
listOfSpeakers?: ViewListOfSpeakers,
|
||||
parent?: ViewMediafile,
|
||||
access_groups?: ViewGroup[],
|
||||
inherited_access_groups?: ViewGroup[]
|
||||
) {
|
||||
super(Mediafile.COLLECTIONSTRING, mediafile, listOfSpeakers);
|
||||
this._parent = parent;
|
||||
this._access_groups = access_groups;
|
||||
this._inherited_access_groups = inherited_access_groups;
|
||||
public constructor(mediafile: Mediafile) {
|
||||
super(Mediafile.COLLECTIONSTRING, mediafile);
|
||||
}
|
||||
|
||||
public formatForSearch(): SearchRepresentation {
|
||||
@ -202,29 +191,4 @@ export class ViewMediafile extends BaseViewModelWithListOfSpeakers<Mediafile>
|
||||
return 'insert_drive_file';
|
||||
}
|
||||
}
|
||||
|
||||
public updateDependencies(update: BaseViewModel): void {
|
||||
super.updateDependencies(update);
|
||||
if (update instanceof ViewMediafile && update.id === this.parent_id) {
|
||||
this._parent = update;
|
||||
} else if (update instanceof ViewGroup) {
|
||||
if (this.access_groups_id.includes(update.id)) {
|
||||
const groupIndex = this.access_groups.findIndex(group => group.id === update.id);
|
||||
if (groupIndex < 0) {
|
||||
this.access_groups.push(update);
|
||||
} else {
|
||||
this.access_groups[groupIndex] = update;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.has_inherited_access_groups && (<number[]>this.inherited_access_groups_id).includes(update.id)) {
|
||||
const groupIndex = this.inherited_access_groups.findIndex(group => group.id === update.id);
|
||||
if (groupIndex < 0) {
|
||||
this.inherited_access_groups.push(update);
|
||||
} else {
|
||||
this.inherited_access_groups[groupIndex] = update;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -75,9 +75,8 @@ export class ViewCategory extends BaseViewModel<Category> implements CategoryTit
|
||||
}
|
||||
}
|
||||
|
||||
public constructor(category: Category, parent?: ViewCategory) {
|
||||
public constructor(category: Category) {
|
||||
super(Category.COLLECTIONSTRING, category);
|
||||
this._parent = parent;
|
||||
}
|
||||
|
||||
public formatForSearch(): SearchRepresentation {
|
||||
@ -102,14 +101,4 @@ export class ViewCategory extends BaseViewModel<Category> implements CategoryTit
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the local objects if required
|
||||
* @param update
|
||||
*/
|
||||
public updateDependencies(update: BaseViewModel): void {
|
||||
if (update instanceof ViewCategory && update.id === this.parent_id) {
|
||||
this._parent = update;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,6 @@
|
||||
import { WorkflowState } from 'app/shared/models/motions/workflow-state';
|
||||
import { ViewMotion } from './view-motion';
|
||||
import { CreateMotion } from './create-motion';
|
||||
import { ViewUser } from 'app/site/users/models/view-user';
|
||||
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
|
||||
@ -21,57 +11,23 @@ import { PersonalNoteContent } from 'app/shared/models/users/personal-note';
|
||||
export class ViewCreateMotion extends ViewMotion {
|
||||
protected _model: CreateMotion;
|
||||
|
||||
protected _submitterUsers: ViewUser[];
|
||||
|
||||
public get motion(): CreateMotion {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
public get submitters(): ViewUser[] {
|
||||
return this._submitters;
|
||||
public get submittersAsUsers(): ViewUser[] {
|
||||
return this._submitterUsers;
|
||||
}
|
||||
|
||||
public get submitters_id(): number[] {
|
||||
return this.motion ? this.motion.sorted_submitters_id : null;
|
||||
}
|
||||
|
||||
public set submitters(users: ViewUser[]) {
|
||||
this._submitters = users;
|
||||
public set submittersAsUsers(users: ViewUser[]) {
|
||||
this._submitterUsers = users;
|
||||
this._model.submitters_id = users.map(user => user.id);
|
||||
}
|
||||
|
||||
public constructor(
|
||||
motion: CreateMotion,
|
||||
category?: ViewCategory,
|
||||
submitters?: ViewUser[],
|
||||
supporters?: ViewUser[],
|
||||
workflow?: ViewWorkflow,
|
||||
state?: WorkflowState,
|
||||
item?: ViewItem,
|
||||
listOfSpeakers?: ViewListOfSpeakers,
|
||||
block?: ViewMotionBlock,
|
||||
attachments?: ViewMediafile[],
|
||||
tags?: ViewTag[],
|
||||
parent?: ViewMotion,
|
||||
changeRecommendations?: ViewMotionChangeRecommendation[],
|
||||
amendments?: ViewMotion[],
|
||||
personalNote?: PersonalNoteContent
|
||||
) {
|
||||
super(
|
||||
motion,
|
||||
category,
|
||||
submitters,
|
||||
supporters,
|
||||
workflow,
|
||||
state,
|
||||
item,
|
||||
listOfSpeakers,
|
||||
block,
|
||||
attachments,
|
||||
tags,
|
||||
parent,
|
||||
changeRecommendations,
|
||||
amendments,
|
||||
personalNote
|
||||
);
|
||||
public constructor(motion: CreateMotion) {
|
||||
super(motion);
|
||||
}
|
||||
|
||||
public getVerboseName = () => {
|
||||
@ -82,13 +38,6 @@ export class ViewCreateMotion extends ViewMotion {
|
||||
* Duplicate this motion into a copy of itself
|
||||
*/
|
||||
public copy(): ViewCreateMotion {
|
||||
return new ViewCreateMotion(
|
||||
this._model,
|
||||
this._category,
|
||||
this._submitters,
|
||||
this._supporters,
|
||||
this._workflow,
|
||||
this._state
|
||||
);
|
||||
return new ViewCreateMotion(this._model);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ViewUnifiedChange, ViewUnifiedChangeType } from '../../../shared/models/motions/view-unified-change';
|
||||
import { ViewMotion } from './view-motion';
|
||||
import { LineRange } from 'app/core/ui-services/diff.service';
|
||||
import { MergeAmendment } from 'app/shared/models/motions/workflow-state';
|
||||
import { MergeAmendment } from 'app/shared/models/motions/state';
|
||||
|
||||
/**
|
||||
* This represents the Unified Diff part of an amendments.
|
||||
|
@ -2,9 +2,7 @@ import { MotionBlock } from 'app/shared/models/motions/motion-block';
|
||||
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 { 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 {
|
||||
@ -31,8 +29,8 @@ export class ViewMotionBlock extends BaseViewModelWithAgendaItemAndListOfSpeaker
|
||||
return this.motionBlock.internal;
|
||||
}
|
||||
|
||||
public constructor(motionBlock: MotionBlock, agendaItem?: ViewItem, listOfSpeakers?: ViewListOfSpeakers) {
|
||||
super(MotionBlock.COLLECTIONSTRING, motionBlock, agendaItem, listOfSpeakers);
|
||||
public constructor(motionBlock: MotionBlock) {
|
||||
super(MotionBlock.COLLECTIONSTRING, motionBlock);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -24,8 +24,6 @@ export class ViewMotionChangeRecommendation extends BaseViewModel<MotionChangeRe
|
||||
super(MotionChangeRecommendation.COLLECTIONSTRING, motionChangeRecommendation);
|
||||
}
|
||||
|
||||
public updateDependencies(update: BaseViewModel): void {}
|
||||
|
||||
public updateChangeReco(type: number, text: string, internal: boolean): void {
|
||||
// @TODO HTML sanitazion
|
||||
this.changeRecommendation.type = type;
|
||||
|
@ -17,8 +17,8 @@ export class ViewMotionCommentSection extends BaseViewModel<MotionCommentSection
|
||||
implements MotionCommentSectionTitleInformation {
|
||||
public static COLLECTIONSTRING = MotionCommentSection.COLLECTIONSTRING;
|
||||
|
||||
private _readGroups: ViewGroup[];
|
||||
private _writeGroups: ViewGroup[];
|
||||
private _read_groups: ViewGroup[];
|
||||
private _write_groups: ViewGroup[];
|
||||
|
||||
public get section(): MotionCommentSection {
|
||||
return this._model;
|
||||
@ -41,11 +41,11 @@ export class ViewMotionCommentSection extends BaseViewModel<MotionCommentSection
|
||||
}
|
||||
|
||||
public get read_groups(): ViewGroup[] {
|
||||
return this._readGroups;
|
||||
return this._read_groups;
|
||||
}
|
||||
|
||||
public get write_groups(): ViewGroup[] {
|
||||
return this._writeGroups;
|
||||
return this._write_groups;
|
||||
}
|
||||
|
||||
public get weight(): number {
|
||||
@ -59,10 +59,8 @@ export class ViewMotionCommentSection extends BaseViewModel<MotionCommentSection
|
||||
this._model.name = name;
|
||||
}
|
||||
|
||||
public constructor(motionCommentSection: MotionCommentSection, readGroups: ViewGroup[], writeGroups: ViewGroup[]) {
|
||||
public constructor(motionCommentSection: MotionCommentSection) {
|
||||
super(MotionCommentSection.COLLECTIONSTRING, motionCommentSection);
|
||||
this._readGroups = readGroups;
|
||||
this._writeGroups = writeGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,25 +1,22 @@
|
||||
import { Motion, MotionComment } from 'app/shared/models/motions/motion';
|
||||
import { PersonalNoteContent } from 'app/shared/models/users/personal-note';
|
||||
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 { Searchable } from 'app/site/base/searchable';
|
||||
import { ViewUser } from 'app/site/users/models/view-user';
|
||||
import { ViewTag } from 'app/site/tags/models/view-tag';
|
||||
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
|
||||
import { ViewItem } from 'app/site/agenda/models/view-item';
|
||||
import { ViewWorkflow } from './view-workflow';
|
||||
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 { 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';
|
||||
import { ViewState } from './view-state';
|
||||
import { ViewSubmitter } from './view-submitter';
|
||||
|
||||
/**
|
||||
* The line numbering mode for the motion detail view.
|
||||
@ -67,11 +64,12 @@ export class ViewMotion extends BaseViewModelWithAgendaItemAndListOfSpeakers<Mot
|
||||
public static COLLECTIONSTRING = Motion.COLLECTIONSTRING;
|
||||
|
||||
protected _category?: ViewCategory;
|
||||
protected _submitters?: ViewUser[];
|
||||
protected _submitters?: ViewSubmitter[];
|
||||
protected _supporters?: ViewUser[];
|
||||
protected _workflow?: ViewWorkflow;
|
||||
protected _state?: WorkflowState;
|
||||
protected _block?: ViewMotionBlock;
|
||||
protected _state?: ViewState;
|
||||
protected _recommendation?: ViewState;
|
||||
protected _motion_block?: ViewMotionBlock;
|
||||
protected _attachments?: ViewMediafile[];
|
||||
protected _tags?: ViewTag[];
|
||||
protected _parent?: ViewMotion;
|
||||
@ -87,10 +85,22 @@ export class ViewMotion extends BaseViewModelWithAgendaItemAndListOfSpeakers<Mot
|
||||
return this._category;
|
||||
}
|
||||
|
||||
public get submitters(): ViewUser[] {
|
||||
public get state(): ViewState | null {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
public get recommendation(): ViewState | null {
|
||||
return this._recommendation;
|
||||
}
|
||||
|
||||
public get submitters(): ViewSubmitter[] {
|
||||
return this._submitters || [];
|
||||
}
|
||||
|
||||
public get submittersAsUsers(): ViewUser[] {
|
||||
return this.submitters.map(submitter => submitter.user);
|
||||
}
|
||||
|
||||
public get supporters(): ViewUser[] {
|
||||
return this._supporters || [];
|
||||
}
|
||||
@ -104,7 +114,7 @@ export class ViewMotion extends BaseViewModelWithAgendaItemAndListOfSpeakers<Mot
|
||||
}
|
||||
|
||||
public get motion_block(): ViewMotionBlock | null {
|
||||
return this._block;
|
||||
return this._motion_block;
|
||||
}
|
||||
|
||||
public get attachments(): ViewMediafile[] {
|
||||
@ -185,21 +195,12 @@ export class ViewMotion extends BaseViewModelWithAgendaItemAndListOfSpeakers<Mot
|
||||
return this.motion.workflow_id;
|
||||
}
|
||||
|
||||
public get state(): WorkflowState | null {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
public get changeRecommendations(): ViewMotionChangeRecommendation[] {
|
||||
return this._changeRecommendations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current state of thw workflow is final
|
||||
*
|
||||
* @returns true if it is final
|
||||
*/
|
||||
public get isFinalState(): boolean {
|
||||
return this._state.isFinalState;
|
||||
public get change_recommendations_id(): number[] {
|
||||
return this.motion.change_recommendations_id;
|
||||
}
|
||||
|
||||
public get state_id(): number {
|
||||
@ -214,28 +215,14 @@ export class ViewMotion extends BaseViewModelWithAgendaItemAndListOfSpeakers<Mot
|
||||
return this.motion.statute_paragraph_id;
|
||||
}
|
||||
|
||||
public get recommendation(): WorkflowState {
|
||||
return this.workflow ? this.workflow.getStateById(this.recommendation_id) : null;
|
||||
}
|
||||
|
||||
public get possibleRecommendations(): WorkflowState[] {
|
||||
return this.workflow
|
||||
? this.workflow.states.filter(recommendation => recommendation.recommendation_label !== undefined)
|
||||
: null;
|
||||
public get possibleRecommendations(): ViewState[] {
|
||||
return this.workflow ? this.workflow.states.filter(state => state.recommendation_label !== undefined) : null;
|
||||
}
|
||||
|
||||
public get origin(): string {
|
||||
return this.motion.origin;
|
||||
}
|
||||
|
||||
public get nextStates(): WorkflowState[] {
|
||||
return this.state && this.workflow ? this.state.getNextStates(this.workflow.workflow) : [];
|
||||
}
|
||||
|
||||
public get previousStates(): WorkflowState[] {
|
||||
return this.state && this.workflow ? this.state.getPreviousStates(this.workflow.workflow) : [];
|
||||
}
|
||||
|
||||
public get agenda_type(): number {
|
||||
return this.agendaItem ? this.agendaItem.type : null;
|
||||
}
|
||||
@ -327,8 +314,6 @@ export class ViewMotion extends BaseViewModelWithAgendaItemAndListOfSpeakers<Mot
|
||||
|
||||
/**
|
||||
* Getter to query the 'favorite'/'star' status of the motions
|
||||
*
|
||||
* @returns the current state
|
||||
*/
|
||||
public get star(): boolean {
|
||||
return !!this.personalNote && !!this.personalNote.star;
|
||||
@ -355,36 +340,8 @@ export class ViewMotion extends BaseViewModelWithAgendaItemAndListOfSpeakers<Mot
|
||||
// This is set by the repository
|
||||
public getIdentifierOrTitle: () => string;
|
||||
|
||||
public constructor(
|
||||
motion: Motion,
|
||||
category?: ViewCategory,
|
||||
submitters?: ViewUser[],
|
||||
supporters?: ViewUser[],
|
||||
workflow?: ViewWorkflow,
|
||||
state?: WorkflowState,
|
||||
item?: ViewItem,
|
||||
listOfSpeakers?: ViewListOfSpeakers,
|
||||
block?: ViewMotionBlock,
|
||||
attachments?: ViewMediafile[],
|
||||
tags?: ViewTag[],
|
||||
parent?: ViewMotion,
|
||||
changeRecommendations?: ViewMotionChangeRecommendation[],
|
||||
amendments?: ViewMotion[],
|
||||
personalNote?: PersonalNoteContent
|
||||
) {
|
||||
super(Motion.COLLECTIONSTRING, motion, item, listOfSpeakers);
|
||||
this._category = category;
|
||||
this._submitters = submitters;
|
||||
this._supporters = supporters;
|
||||
this._workflow = workflow;
|
||||
this._state = state;
|
||||
this._block = block;
|
||||
this._attachments = attachments;
|
||||
this._tags = tags;
|
||||
this._parent = parent;
|
||||
this._amendments = amendments;
|
||||
this._changeRecommendations = changeRecommendations;
|
||||
this.personalNote = personalNote;
|
||||
public constructor(motion: Motion) {
|
||||
super(Motion.COLLECTIONSTRING, motion);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -397,7 +354,7 @@ export class ViewMotion extends BaseViewModelWithAgendaItemAndListOfSpeakers<Mot
|
||||
if (this.amendment_paragraphs) {
|
||||
searchValues = searchValues.concat(this.amendment_paragraphs.filter(x => !!x));
|
||||
}
|
||||
searchValues = searchValues.concat(this.submitters.map(user => user.full_name));
|
||||
searchValues = searchValues.concat(this.submittersAsUsers.map(user => user.full_name));
|
||||
searchValues = searchValues.concat(this.supporters.map(user => user.full_name));
|
||||
searchValues = searchValues.concat(this.tags.map(tag => tag.getTitle()));
|
||||
searchValues = searchValues.concat(this.motion.comments.map(comment => comment.comment));
|
||||
@ -429,146 +386,6 @@ export class ViewMotion extends BaseViewModelWithAgendaItemAndListOfSpeakers<Mot
|
||||
return this.motion.comments.find(comment => comment.section_id === section.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the local objects if required
|
||||
*
|
||||
* @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 ViewMotionBlock) {
|
||||
this.updateMotionBlock(update);
|
||||
} else if (update instanceof ViewUser) {
|
||||
this.updateUser(update);
|
||||
} else if (update instanceof ViewMediafile) {
|
||||
this.updateAttachments(update);
|
||||
} else if (update instanceof ViewTag) {
|
||||
this.updateTags(update);
|
||||
} else if (update instanceof ViewMotion && update.id !== this.id) {
|
||||
this.updateMotion(update);
|
||||
} else if (update instanceof ViewMotionChangeRecommendation) {
|
||||
this.updateChangeRecommendation(update);
|
||||
} else if (update instanceof ViewPersonalNote) {
|
||||
this.updatePersonalNote(update);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update routine for the workflow
|
||||
*
|
||||
* @param workflow potentially the (changed workflow (state). Needs manual verification
|
||||
*/
|
||||
private updateWorkflow(workflow: ViewWorkflow): void {
|
||||
if (workflow.id === this.motion.workflow_id) {
|
||||
this._workflow = workflow;
|
||||
this._state = workflow.getStateById(this.state_id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update routine for the category
|
||||
*
|
||||
* @param category potentially the changed category. Needs manual verification
|
||||
*/
|
||||
private updateCategory(category: ViewCategory): void {
|
||||
if (this.category_id && category.id === this.motion.category_id) {
|
||||
this._category = category;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update routine for the motion block
|
||||
*
|
||||
* @param block potentially the changed motion block. Needs manual verification
|
||||
*/
|
||||
private updateMotionBlock(block: ViewMotionBlock): void {
|
||||
if (this.motion_block_id && block.id === this.motion.motion_block_id) {
|
||||
this._block = block;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update routine for supporters and submitters
|
||||
*
|
||||
* @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)) {
|
||||
const userIndex = this.motion.submitters.findIndex(submitter => submitter.user_id === update.id);
|
||||
this.submitters[userIndex] = update;
|
||||
}
|
||||
if (this.motion.supporters_id && this.motion.supporters_id.includes(update.id)) {
|
||||
const userIndex = this.supporters.findIndex(user => user.id === update.id);
|
||||
if (userIndex < 0) {
|
||||
this.supporters.push(update);
|
||||
} else {
|
||||
this.supporters[userIndex] = update;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update routine for attachments
|
||||
*
|
||||
* @param mediafile
|
||||
*/
|
||||
private updateAttachments(mediafile: ViewMediafile): void {
|
||||
if (this.attachments_id && this.attachments_id.includes(mediafile.id)) {
|
||||
const attachmentIndex = this.attachments.findIndex(_mediafile => _mediafile.id === mediafile.id);
|
||||
if (attachmentIndex < 0) {
|
||||
this.attachments.push(mediafile);
|
||||
} else {
|
||||
this.attachments[attachmentIndex] = mediafile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateTags(tag: ViewTag): void {
|
||||
if (this.tags_id && this.tags_id.includes(tag.id)) {
|
||||
const tagIndex = this.tags.findIndex(_tag => _tag.id === tag.id);
|
||||
if (tagIndex < 0) {
|
||||
this.tags.push(tag);
|
||||
} else {
|
||||
this.tags[tagIndex] = tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The update cen be the parent or a child motion (=amendment).
|
||||
*/
|
||||
private updateMotion(update: ViewMotion): void {
|
||||
if (this.parent_id && this.parent_id === update.id) {
|
||||
this._parent = update;
|
||||
} else if (update.parent_id && update.parent_id === this.id) {
|
||||
const index = this._amendments.findIndex(m => m.id === update.id);
|
||||
if (index >= 0) {
|
||||
this._amendments[index] = update;
|
||||
} else {
|
||||
this._amendments.push(update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateChangeRecommendation(cr: ViewMotionChangeRecommendation): void {
|
||||
if (cr.motion_id === this.id) {
|
||||
const index = this.changeRecommendations.findIndex(_cr => _cr.id === cr.id);
|
||||
if (index < 0) {
|
||||
this.changeRecommendations.push(cr);
|
||||
} else {
|
||||
this.changeRecommendations[index] = cr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updatePersonalNote(personalNote: ViewPersonalNote): void {
|
||||
this.personalNote = personalNote.getNoteContent(this.collectionString, this.id);
|
||||
}
|
||||
|
||||
public hasSupporters(): boolean {
|
||||
return !!(this.supporters && this.supporters.length > 0);
|
||||
}
|
||||
@ -589,7 +406,7 @@ export class ViewMotion extends BaseViewModelWithAgendaItemAndListOfSpeakers<Mot
|
||||
* Determine if the motion is in its final workflow state
|
||||
*/
|
||||
public isInFinalState(): boolean {
|
||||
return this.nextStates.length === 0;
|
||||
return this.state ? this.state.isFinalState : false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -635,19 +452,6 @@ export class ViewMotion extends BaseViewModelWithAgendaItemAndListOfSpeakers<Mot
|
||||
* Duplicate this motion into a copy of itself
|
||||
*/
|
||||
public copy(): ViewMotion {
|
||||
return new ViewMotion(
|
||||
this._model,
|
||||
this._category,
|
||||
this._submitters,
|
||||
this._supporters,
|
||||
this._workflow,
|
||||
this._state,
|
||||
this._item,
|
||||
this._listOfSpeakers,
|
||||
this._block,
|
||||
this._attachments,
|
||||
this._tags,
|
||||
this._parent
|
||||
);
|
||||
return new ViewMotion(this._model);
|
||||
}
|
||||
}
|
||||
|
99
client/src/app/site/motions/models/view-state.ts
Normal file
99
client/src/app/site/motions/models/view-state.ts
Normal file
@ -0,0 +1,99 @@
|
||||
import { BaseViewModel } from '../../base/base-view-model';
|
||||
import { State, MergeAmendment } from 'app/shared/models/motions/state';
|
||||
import { ViewWorkflow } from './view-workflow';
|
||||
|
||||
export interface StateTitleInformation {
|
||||
name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* class for the ViewState.
|
||||
* @ignore
|
||||
*/
|
||||
export class ViewState extends BaseViewModel<State> implements StateTitleInformation {
|
||||
public static COLLECTIONSTRING = State.COLLECTIONSTRING;
|
||||
|
||||
private _next_states?: ViewState[];
|
||||
public _workflow?: ViewWorkflow;
|
||||
|
||||
public get state(): State {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
public get workflow(): ViewWorkflow | null {
|
||||
return this._workflow;
|
||||
}
|
||||
|
||||
public get next_states(): ViewState[] {
|
||||
return this._next_states || [];
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
return this.state.name;
|
||||
}
|
||||
|
||||
public get recommendation_label(): string {
|
||||
return this.state.recommendation_label;
|
||||
}
|
||||
|
||||
public get css_class(): string {
|
||||
return this.state.css_class;
|
||||
}
|
||||
|
||||
public get restriction(): string[] {
|
||||
return this.state.restriction;
|
||||
}
|
||||
|
||||
public get allow_support(): boolean {
|
||||
return this.state.allow_support;
|
||||
}
|
||||
|
||||
public get allow_create_poll(): boolean {
|
||||
return this.state.allow_create_poll;
|
||||
}
|
||||
|
||||
public get allow_submitter_edit(): boolean {
|
||||
return this.state.allow_submitter_edit;
|
||||
}
|
||||
|
||||
public get dont_set_identifier(): boolean {
|
||||
return this.state.dont_set_identifier;
|
||||
}
|
||||
|
||||
public get show_state_extension_field(): boolean {
|
||||
return this.state.show_state_extension_field;
|
||||
}
|
||||
|
||||
public get merge_amendment_into_final(): MergeAmendment {
|
||||
return this.state.merge_amendment_into_final;
|
||||
}
|
||||
|
||||
public get show_recommendation_extension_field(): boolean {
|
||||
return this.state.show_recommendation_extension_field;
|
||||
}
|
||||
|
||||
public get next_states_id(): number[] {
|
||||
return this.state.next_states_id;
|
||||
}
|
||||
|
||||
public get workflow_id(): number {
|
||||
return this.state.workflow_id;
|
||||
}
|
||||
|
||||
public get isFinalState(): boolean {
|
||||
return !this.next_states_id || this.next_states_id.length === 0;
|
||||
}
|
||||
|
||||
public get previous_states(): ViewState[] {
|
||||
if (!this.workflow) {
|
||||
return [];
|
||||
}
|
||||
return this.workflow.states.filter(state => {
|
||||
return state.next_states_id.includes(this.id);
|
||||
});
|
||||
}
|
||||
|
||||
public constructor(state: State) {
|
||||
super(State.COLLECTIONSTRING, state);
|
||||
}
|
||||
}
|
@ -45,10 +45,4 @@ export class ViewStatuteParagraph extends BaseViewModel<StatuteParagraph>
|
||||
public getDetailStateURL(): string {
|
||||
return '/motions/statute-paragraphs';
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the local objects if required
|
||||
* @param section
|
||||
*/
|
||||
public updateDependencies(update: BaseViewModel): void {}
|
||||
}
|
||||
|
44
client/src/app/site/motions/models/view-submitter.ts
Normal file
44
client/src/app/site/motions/models/view-submitter.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { ViewUser } from 'app/site/users/models/view-user';
|
||||
import { Submitter } from 'app/shared/models/motions/submitter';
|
||||
import { BaseViewModel } from 'app/site/base/base-view-model';
|
||||
|
||||
export class ViewSubmitter extends BaseViewModel<Submitter> {
|
||||
public static COLLECTIONSTRING = Submitter.COLLECTIONSTRING;
|
||||
private _user?: ViewUser;
|
||||
|
||||
public get submitter(): Submitter {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
public get user(): ViewUser {
|
||||
return this._user;
|
||||
}
|
||||
|
||||
public get id(): number {
|
||||
return this.submitter.id;
|
||||
}
|
||||
|
||||
public get user_id(): number {
|
||||
return this.submitter.user_id;
|
||||
}
|
||||
|
||||
public get motion_id(): number {
|
||||
return this.submitter.motion_id;
|
||||
}
|
||||
|
||||
public get weight(): number {
|
||||
return this.submitter.weight;
|
||||
}
|
||||
|
||||
public constructor(submitter: Submitter) {
|
||||
super(Submitter.COLLECTIONSTRING, submitter);
|
||||
}
|
||||
|
||||
public getTitle = () => {
|
||||
return this.user ? this.user.getTitle() : '';
|
||||
};
|
||||
|
||||
public getListTitle = () => {
|
||||
return this.getTitle();
|
||||
};
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { Workflow } from 'app/shared/models/motions/workflow';
|
||||
import { WorkflowState } from 'app/shared/models/motions/workflow-state';
|
||||
import { BaseViewModel } from '../../base/base-view-model';
|
||||
import { ViewState } from './view-state';
|
||||
|
||||
export interface WorkflowTitleInformation {
|
||||
name: string;
|
||||
@ -13,6 +13,9 @@ export interface WorkflowTitleInformation {
|
||||
export class ViewWorkflow extends BaseViewModel<Workflow> implements WorkflowTitleInformation {
|
||||
public static COLLECTIONSTRING = Workflow.COLLECTIONSTRING;
|
||||
|
||||
private _states?: ViewState[];
|
||||
private _first_state?: ViewState;
|
||||
|
||||
public get workflow(): Workflow {
|
||||
return this._model;
|
||||
}
|
||||
@ -21,34 +24,23 @@ export class ViewWorkflow extends BaseViewModel<Workflow> implements WorkflowTit
|
||||
return this.workflow.name;
|
||||
}
|
||||
|
||||
public get states(): WorkflowState[] {
|
||||
return this.workflow.states;
|
||||
public get states(): ViewState[] {
|
||||
return this._states || [];
|
||||
}
|
||||
|
||||
public get states_id(): number[] {
|
||||
return this.workflow.states_id;
|
||||
}
|
||||
|
||||
public get first_state_id(): number {
|
||||
return this.workflow.first_state_id;
|
||||
}
|
||||
|
||||
public get firstState(): WorkflowState {
|
||||
return this.getStateById(this.first_state_id);
|
||||
public get first_state(): ViewState | null {
|
||||
return this._first_state;
|
||||
}
|
||||
|
||||
public constructor(workflow: Workflow) {
|
||||
super(Workflow.COLLECTIONSTRING, workflow);
|
||||
}
|
||||
|
||||
public sortStates(): void {
|
||||
this.workflow.sortStates();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the local objects if required
|
||||
*
|
||||
* @param update
|
||||
*/
|
||||
public updateDependencies(update: BaseViewModel): void {}
|
||||
|
||||
public getStateById(id: number): WorkflowState {
|
||||
return this.states.find(state => id === state.id);
|
||||
}
|
||||
}
|
||||
|
@ -13,8 +13,8 @@
|
||||
</h4>
|
||||
|
||||
<div *ngIf="!isEditMode || !perms.isAllowed('change_metadata')">
|
||||
<mat-chip-list *ngFor="let submitter of motion.submitters" class="user">
|
||||
<mat-chip disableRipple>{{ submitter.full_name }}</mat-chip>
|
||||
<mat-chip-list *ngFor="let user of motion.submittersAsUsers" class="user">
|
||||
<mat-chip disableRipple *ngIf="user">{{ user.getTitle() }}</mat-chip>
|
||||
</mat-chip-list>
|
||||
</div>
|
||||
|
||||
|
@ -93,7 +93,7 @@ export class ManageSubmittersComponent extends BaseViewComponent {
|
||||
*/
|
||||
public onEdit(): void {
|
||||
this.isEditMode = true;
|
||||
this.editSubmitterSubject.next(this.motion.submitters.map(x => x));
|
||||
this.editSubmitterSubject.next(this.motion.submittersAsUsers);
|
||||
this.addSubmitterForm.reset();
|
||||
|
||||
// get all users for the submitter add form
|
||||
|
@ -261,13 +261,13 @@
|
||||
extensionLabel="{{ 'Extension' | translate }}"
|
||||
(success)="setStateExtension($event)"
|
||||
>
|
||||
<span class="trigger-menu">
|
||||
<button *ngFor="let state of motion.nextStates" mat-menu-item (click)="setState(state.id)">
|
||||
<span class="trigger-menu" *ngIf="motion.state">
|
||||
<button *ngFor="let state of motion.state.next_states" mat-menu-item (click)="setState(state.id)">
|
||||
{{ state.name | translate }} <span *ngIf="state.show_state_extension_field"> ...</span>
|
||||
</button>
|
||||
<div>
|
||||
<mat-divider *ngIf="motion.nextStates.length > 0"></mat-divider>
|
||||
<button *ngFor="let state of motion.previousStates" mat-menu-item (click)="setState(state.id)">
|
||||
<mat-divider *ngIf="motion.state.next_states.length > 0"></mat-divider>
|
||||
<button *ngFor="let state of motion.state.previous_states" mat-menu-item (click)="setState(state.id)">
|
||||
<mat-icon>arrow_back</mat-icon> {{ state.name | translate }}
|
||||
<span *ngIf="state.show_state_extension_field"> ...</span>
|
||||
</button>
|
||||
|
@ -47,10 +47,7 @@ import {
|
||||
LineNumberingMode,
|
||||
verboseChangeRecoMode
|
||||
} from 'app/site/motions/models/view-motion';
|
||||
import {
|
||||
ViewMotionNotificationEditMotion,
|
||||
TypeOfNotificationViewMotion
|
||||
} from 'app/site/motions/models/view-motion-notify';
|
||||
import { MotionEditNotification, MotionEditNotificationType } from 'app/site/motions/motion-edit-notification';
|
||||
import { ViewMotionBlock } from 'app/site/motions/models/view-motion-block';
|
||||
import { ViewCategory } from 'app/site/motions/models/view-category';
|
||||
import { ViewCreateMotion } from 'app/site/motions/models/view-create-motion';
|
||||
@ -517,7 +514,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
* Sends a notification to user editors of the motion was edited
|
||||
*/
|
||||
public ngOnDestroy(): void {
|
||||
this.unsubscribeEditNotifications(TypeOfNotificationViewMotion.TYPE_CLOSING_EDITING_MOTION);
|
||||
this.unsubscribeEditNotifications(MotionEditNotificationType.TYPE_CLOSING_EDITING_MOTION);
|
||||
if (this.navigationSubscription) {
|
||||
this.navigationSubscription.unsubscribe();
|
||||
}
|
||||
@ -832,7 +829,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
} else {
|
||||
this.updateMotionFromForm();
|
||||
// When saving the changes, notify other users if they edit the same motion.
|
||||
this.unsubscribeEditNotifications(TypeOfNotificationViewMotion.TYPE_SAVING_EDITING_MOTION);
|
||||
this.unsubscribeEditNotifications(MotionEditNotificationType.TYPE_SAVING_EDITING_MOTION);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1165,7 +1162,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
this.motionCopy = this.motion.copy();
|
||||
this.patchForm(this.motionCopy);
|
||||
this.editNotificationSubscription = this.listenToEditNotification();
|
||||
this.sendEditNotification(TypeOfNotificationViewMotion.TYPE_BEGIN_EDITING_MOTION);
|
||||
this.sendEditNotification(MotionEditNotificationType.TYPE_BEGIN_EDITING_MOTION);
|
||||
}
|
||||
if (!mode && this.newMotion) {
|
||||
this.router.navigate(['./motions/']);
|
||||
@ -1173,7 +1170,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
// If the user cancels the work on this motion,
|
||||
// notify the users who are still editing the same motion
|
||||
if (!mode && !this.newMotion) {
|
||||
this.unsubscribeEditNotifications(TypeOfNotificationViewMotion.TYPE_CLOSING_EDITING_MOTION);
|
||||
this.unsubscribeEditNotifications(MotionEditNotificationType.TYPE_CLOSING_EDITING_MOTION);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1412,8 +1409,8 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
* @param type TypeOfNotificationViewMotion defines the type of the notification which is sent.
|
||||
* @param user Optional userId. If set the function will send a notification to the given userId.
|
||||
*/
|
||||
private sendEditNotification(type: TypeOfNotificationViewMotion, user?: number): void {
|
||||
const content: ViewMotionNotificationEditMotion = {
|
||||
private sendEditNotification(type: MotionEditNotificationType, user?: number): void {
|
||||
const content: MotionEditNotification = {
|
||||
motionId: this.motion.id,
|
||||
senderId: this.operator.viewUser.id,
|
||||
senderName: this.operator.viewUser.short_name,
|
||||
@ -1422,7 +1419,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
if (user) {
|
||||
this.notifyService.sendToUsers(this.NOTIFICATION_EDIT_MOTION, content, user);
|
||||
} else {
|
||||
this.notifyService.sendToAllUsers<ViewMotionNotificationEditMotion>(this.NOTIFICATION_EDIT_MOTION, content);
|
||||
this.notifyService.sendToAllUsers<MotionEditNotification>(this.NOTIFICATION_EDIT_MOTION, content);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1434,13 +1431,13 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
*/
|
||||
private listenToEditNotification(): Subscription {
|
||||
return this.notifyService.getMessageObservable(this.NOTIFICATION_EDIT_MOTION).subscribe(message => {
|
||||
const content = <ViewMotionNotificationEditMotion>message.content;
|
||||
const content = <MotionEditNotification>message.content;
|
||||
if (this.operator.viewUser.id !== content.senderId && content.motionId === this.motion.id) {
|
||||
let warning = '';
|
||||
|
||||
switch (content.type) {
|
||||
case TypeOfNotificationViewMotion.TYPE_BEGIN_EDITING_MOTION:
|
||||
case TypeOfNotificationViewMotion.TYPE_ALSO_EDITING_MOTION: {
|
||||
case MotionEditNotificationType.TYPE_BEGIN_EDITING_MOTION:
|
||||
case MotionEditNotificationType.TYPE_ALSO_EDITING_MOTION: {
|
||||
if (!this.otherWorkOnMotion.includes(content.senderName)) {
|
||||
this.otherWorkOnMotion.push(content.senderName);
|
||||
}
|
||||
@ -1448,19 +1445,19 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
warning = `${this.translate.instant('Following users are currently editing this motion:')} ${
|
||||
this.otherWorkOnMotion
|
||||
}`;
|
||||
if (content.type === TypeOfNotificationViewMotion.TYPE_BEGIN_EDITING_MOTION) {
|
||||
if (content.type === MotionEditNotificationType.TYPE_BEGIN_EDITING_MOTION) {
|
||||
this.sendEditNotification(
|
||||
TypeOfNotificationViewMotion.TYPE_ALSO_EDITING_MOTION,
|
||||
MotionEditNotificationType.TYPE_ALSO_EDITING_MOTION,
|
||||
message.senderUserId
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TypeOfNotificationViewMotion.TYPE_CLOSING_EDITING_MOTION: {
|
||||
case MotionEditNotificationType.TYPE_CLOSING_EDITING_MOTION: {
|
||||
this.recognizeOtherWorkerOnMotion(content.senderName);
|
||||
break;
|
||||
}
|
||||
case TypeOfNotificationViewMotion.TYPE_SAVING_EDITING_MOTION: {
|
||||
case MotionEditNotificationType.TYPE_SAVING_EDITING_MOTION: {
|
||||
warning = `${content.senderName} ${this.translate.instant(
|
||||
'has saved his work on this motion.'
|
||||
)}`;
|
||||
@ -1496,7 +1493,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
*
|
||||
* @param unsubscriptionReason The reason for the unsubscription.
|
||||
*/
|
||||
private unsubscribeEditNotifications(unsubscriptionReason: TypeOfNotificationViewMotion): void {
|
||||
private unsubscribeEditNotifications(unsubscriptionReason: MotionEditNotificationType): void {
|
||||
if (!!this.editNotificationSubscription && !this.editNotificationSubscription.closed) {
|
||||
this.sendEditNotification(unsubscriptionReason);
|
||||
this.closeSnackBar();
|
||||
|
@ -19,7 +19,7 @@
|
||||
<div class="title-line">
|
||||
<strong>
|
||||
<span translate>First state</span>:
|
||||
<span>{{ workflow.firstState.name | translate }}</span>
|
||||
<span>{{ workflow.first_state.name | translate }}</span>
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
@ -70,20 +70,13 @@
|
||||
{{ state[perm.selector] | translate }}
|
||||
</mat-basic-chip>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="perm.type === 'state'"
|
||||
>
|
||||
<div *ngIf="perm.type === 'state'">
|
||||
<div class="inner-table">
|
||||
<div *ngIf="!state.next_states_id.length">
|
||||
<div *ngIf="!state.next_states.length">
|
||||
-
|
||||
</div>
|
||||
<div *ngIf="state.next_states_id.length">
|
||||
<div
|
||||
*ngFor="
|
||||
let nextstate of state.getNextStates(workflow.workflow);
|
||||
let last = last
|
||||
"
|
||||
>
|
||||
<div *ngIf="state.next_states.length">
|
||||
<div *ngFor="let nextstate of state.next_states; let last = last">
|
||||
{{ nextstate.name | translate }}<span *ngIf="!last">, </span>
|
||||
</div>
|
||||
</div>
|
||||
@ -92,11 +85,9 @@
|
||||
class="clickable-cell stretch-to-fill-parent"
|
||||
[matMenuTriggerFor]="nextStatesMenu"
|
||||
[matMenuTriggerData]="{ state: state }"
|
||||
></div>
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="perm.type === 'amendment'"
|
||||
>
|
||||
<div *ngIf="perm.type === 'amendment'">
|
||||
<div class="inner-table">
|
||||
{{ getMergeAmendmentLabel(state.merge_amendment_into_final) | translate }}
|
||||
</div>
|
||||
@ -106,9 +97,7 @@
|
||||
[matMenuTriggerData]="{ state: state }"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="perm.type === 'restriction'"
|
||||
>
|
||||
<div *ngIf="perm.type === 'restriction'">
|
||||
<div class="inner-table">
|
||||
<div *ngIf="!state.restriction.length">
|
||||
-
|
||||
|
@ -12,8 +12,10 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||
import { ViewWorkflow } from 'app/site/motions/models/view-workflow';
|
||||
import { WorkflowRepositoryService } from 'app/core/repositories/motions/workflow-repository.service';
|
||||
import { WorkflowState, MergeAmendment } from 'app/shared/models/motions/workflow-state';
|
||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||
import { MergeAmendment, State } from 'app/shared/models/motions/state';
|
||||
import { ViewState } from 'app/site/motions/models/view-state';
|
||||
import { StateRepositoryService } from 'app/core/repositories/motions/state-repository.service';
|
||||
|
||||
/**
|
||||
* Declares data for the workflow dialog
|
||||
@ -153,6 +155,7 @@ export class WorkflowDetailComponent extends BaseViewComponent implements OnInit
|
||||
private promtService: PromptService,
|
||||
private dialog: MatDialog,
|
||||
private workflowRepo: WorkflowRepositoryService,
|
||||
private stateRepo: StateRepositoryService,
|
||||
private route: ActivatedRoute
|
||||
) {
|
||||
super(title, translate, matSnackBar);
|
||||
@ -180,17 +183,17 @@ export class WorkflowDetailComponent extends BaseViewComponent implements OnInit
|
||||
*
|
||||
* @param state the selected workflow state
|
||||
*/
|
||||
public onClickStateName(state: WorkflowState): void {
|
||||
public onClickStateName(state: ViewState): void {
|
||||
this.openEditDialog(state.name, 'Rename state', '', true).subscribe(result => {
|
||||
if (result) {
|
||||
if (result.action === 'update') {
|
||||
this.workflowRepo.updateState({ name: result.value }, state).then(() => {}, this.raiseError);
|
||||
this.stateRepo.update({ name: result.value }, state).then(() => {}, this.raiseError);
|
||||
} else if (result.action === 'delete') {
|
||||
const content = this.translate.instant('Delete') + ` ${state.name}?`;
|
||||
|
||||
this.promtService.open('Are you sure', content).then(promptResult => {
|
||||
if (promptResult) {
|
||||
this.workflowRepo.deleteState(state).then(() => {}, this.raiseError);
|
||||
this.stateRepo.delete(state).then(() => {}, this.raiseError);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -206,7 +209,11 @@ export class WorkflowDetailComponent extends BaseViewComponent implements OnInit
|
||||
this.openEditDialog('', this.translate.instant('New state'), this.translate.instant('Name')).subscribe(
|
||||
result => {
|
||||
if (result && result.action === 'update') {
|
||||
this.workflowRepo.addState(result.value, this.workflow).then(() => {}, this.raiseError);
|
||||
const state = new State({
|
||||
name: result.value,
|
||||
workflow_id: this.workflow.id
|
||||
});
|
||||
this.stateRepo.create(state).then(() => {}, this.raiseError);
|
||||
}
|
||||
}
|
||||
);
|
||||
@ -231,10 +238,10 @@ export class WorkflowDetailComponent extends BaseViewComponent implements OnInit
|
||||
* @param perm The permission
|
||||
* @param state The selected workflow state
|
||||
*/
|
||||
public onClickInputPerm(perm: StatePerm, state: WorkflowState): void {
|
||||
public onClickInputPerm(perm: StatePerm, state: ViewState): void {
|
||||
this.openEditDialog(state[perm.selector], 'Edit', perm.name).subscribe(result => {
|
||||
if (result && result.action === 'update') {
|
||||
this.workflowRepo.updateState({ [perm.selector]: result.value }, state).then(() => {}, this.raiseError);
|
||||
this.stateRepo.update({ [perm.selector]: result.value }, state).then(() => {}, this.raiseError);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -246,8 +253,8 @@ export class WorkflowDetailComponent extends BaseViewComponent implements OnInit
|
||||
* @param perm The states permission that was changed
|
||||
* @param event The change event.
|
||||
*/
|
||||
public onToggleStatePerm(state: WorkflowState, perm: string, event: MatCheckboxChange): void {
|
||||
this.workflowRepo.updateState({ [perm]: event.checked }, state).then(() => {}, this.raiseError);
|
||||
public onToggleStatePerm(state: ViewState, perm: string, event: MatCheckboxChange): void {
|
||||
this.stateRepo.update({ [perm]: event.checked }, state).then(() => {}, this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -257,8 +264,8 @@ export class WorkflowDetailComponent extends BaseViewComponent implements OnInit
|
||||
* @param state The selected workflow state
|
||||
* @param color The selected color
|
||||
*/
|
||||
public onSelectColor(state: WorkflowState, color: string): void {
|
||||
this.workflowRepo.updateState({ css_class: color }, state).then(() => {}, this.raiseError);
|
||||
public onSelectColor(state: ViewState, color: string): void {
|
||||
this.stateRepo.update({ css_class: color }, state).then(() => {}, this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -267,7 +274,7 @@ export class WorkflowDetailComponent extends BaseViewComponent implements OnInit
|
||||
* @param nextState the potential next workflow state
|
||||
* @param state the state to add or remove another state to
|
||||
*/
|
||||
public onSetNextState(nextState: WorkflowState, state: WorkflowState): void {
|
||||
public onSetNextState(nextState: ViewState, state: ViewState): void {
|
||||
const ids = state.next_states_id.map(id => id);
|
||||
const stateIdIndex = ids.findIndex(id => id === nextState.id);
|
||||
|
||||
@ -276,7 +283,7 @@ export class WorkflowDetailComponent extends BaseViewComponent implements OnInit
|
||||
} else {
|
||||
ids.splice(stateIdIndex, 1);
|
||||
}
|
||||
this.workflowRepo.updateState({ next_states_id: ids }, state).then(() => {}, this.raiseError);
|
||||
this.stateRepo.update({ next_states_id: ids }, state).then(() => {}, this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -285,7 +292,7 @@ export class WorkflowDetailComponent extends BaseViewComponent implements OnInit
|
||||
* @param restrictions The new restrictions
|
||||
* @param state the state to change
|
||||
*/
|
||||
public onSetRestriction(restriction: string, state: WorkflowState): void {
|
||||
public onSetRestriction(restriction: string, state: ViewState): void {
|
||||
const restrictions = state.restriction.map(r => r);
|
||||
const restrictionIndex = restrictions.findIndex(r => r === restriction);
|
||||
|
||||
@ -294,7 +301,7 @@ export class WorkflowDetailComponent extends BaseViewComponent implements OnInit
|
||||
} else {
|
||||
restrictions.splice(restrictionIndex, 1);
|
||||
}
|
||||
this.workflowRepo.updateState({ restriction: restrictions }, state).then(() => {}, this.raiseError);
|
||||
this.stateRepo.update({ restriction: restrictions }, state).then(() => {}, this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -311,8 +318,8 @@ export class WorkflowDetailComponent extends BaseViewComponent implements OnInit
|
||||
* @param amendment determines the amendment
|
||||
* @param state the state to change
|
||||
*/
|
||||
public setMergeAmendment(amendment: number, state: WorkflowState): void {
|
||||
this.workflowRepo.updateState({ merge_amendment_into_final: amendment }, state).then(() => {}, this.raiseError);
|
||||
public setMergeAmendment(amendment: number, state: ViewState): void {
|
||||
this.stateRepo.update({ merge_amendment_into_final: amendment }, state).then(() => {}, this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -383,7 +390,7 @@ export class WorkflowDetailComponent extends BaseViewComponent implements OnInit
|
||||
* @param state the workflow state
|
||||
* @returns a unique definition
|
||||
*/
|
||||
public getColumnDef(state: WorkflowState): string {
|
||||
public getColumnDef(state: ViewState): string {
|
||||
return `${state.name}${state.id}`;
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,7 @@
|
||||
</p>
|
||||
<span>
|
||||
<!-- The HTML Editor -->
|
||||
<h4>Statute paragraph</h4>
|
||||
<h4 translate>Statute paragraph</h4>
|
||||
<editor formControlName="text" [init]="tinyMceSettings"></editor>
|
||||
</span>
|
||||
</form>
|
||||
@ -67,7 +67,7 @@
|
||||
</p>
|
||||
<span>
|
||||
<!-- The HTML Editor -->
|
||||
<h4>Statute paragraph</h4>
|
||||
<h4 translate>Statute paragraph</h4>
|
||||
<editor formControlName="text" [init]="tinyMceSettings"></editor>
|
||||
</span>
|
||||
</form>
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Enum to define different types of notifications.
|
||||
*/
|
||||
export enum TypeOfNotificationViewMotion {
|
||||
export enum MotionEditNotificationType {
|
||||
/**
|
||||
* Type to declare editing a motion.
|
||||
*/
|
||||
@ -25,7 +25,7 @@ export enum TypeOfNotificationViewMotion {
|
||||
/**
|
||||
* Class to specify the notifications for editing a motion.
|
||||
*/
|
||||
export interface ViewMotionNotificationEditMotion {
|
||||
export interface MotionEditNotification {
|
||||
/**
|
||||
* The id of the motion the user wants to edit.
|
||||
* Necessary to identify if users edit the same motion.
|
||||
@ -48,5 +48,5 @@ export interface ViewMotionNotificationEditMotion {
|
||||
* The type of the notification.
|
||||
* Separates if the user is beginning the work or closing the edit-view.
|
||||
*/
|
||||
type: TypeOfNotificationViewMotion;
|
||||
type: MotionEditNotificationType;
|
||||
}
|
@ -20,6 +20,9 @@ import { ViewMotionBlock } from './models/view-motion-block';
|
||||
import { ViewStatuteParagraph } from './models/view-statute-paragraph';
|
||||
import { ViewMotion } from './models/view-motion';
|
||||
import { ViewWorkflow } from './models/view-workflow';
|
||||
import { State } from 'app/shared/models/motions/state';
|
||||
import { ViewState } from './models/view-state';
|
||||
import { StateRepositoryService } from 'app/core/repositories/motions/state-repository.service';
|
||||
|
||||
export const MotionsAppConfig: AppConfig = {
|
||||
name: 'motions',
|
||||
@ -44,6 +47,12 @@ export const MotionsAppConfig: AppConfig = {
|
||||
viewModel: ViewWorkflow,
|
||||
repository: WorkflowRepositoryService
|
||||
},
|
||||
{
|
||||
collectionString: 'motions/state',
|
||||
model: State,
|
||||
viewModel: ViewState,
|
||||
repository: StateRepositoryService
|
||||
},
|
||||
{
|
||||
collectionString: 'motions/motion-comment-section',
|
||||
model: MotionCommentSection,
|
||||
|
@ -70,9 +70,9 @@ export class LocalPermissionsService {
|
||||
motion.state &&
|
||||
motion.state.allow_support &&
|
||||
motion.submitters &&
|
||||
motion.submitters.indexOf(this.operator.viewUser) === -1 &&
|
||||
!motion.submittersAsUsers.includes(this.operator.viewUser) &&
|
||||
motion.supporters &&
|
||||
motion.supporters.indexOf(this.operator.viewUser) === -1
|
||||
!motion.supporters.includes(this.operator.viewUser)
|
||||
);
|
||||
}
|
||||
case 'unsupport': {
|
||||
|
@ -152,7 +152,7 @@ export class MotionCsvExportService {
|
||||
[
|
||||
{ label: 'Called', map: motion => (motion.sort_parent_id ? '' : motion.identifierOrTitle) },
|
||||
{ label: 'Called with', map: motion => (!motion.sort_parent_id ? '' : motion.identifierOrTitle) },
|
||||
{ label: 'submitters', map: motion => motion.submitters.map(s => s.short_name).join(',') },
|
||||
{ label: 'submitters', map: motion => motion.submittersAsUsers.map(s => s.short_name).join(',') },
|
||||
{ property: 'title' },
|
||||
{
|
||||
label: 'recommendation',
|
||||
|
@ -125,12 +125,12 @@ export class MotionImportService extends BaseImportService<ViewMotion> {
|
||||
newEntry.motion[this.expectedHeader[idx]] = line[idx];
|
||||
}
|
||||
}
|
||||
const updateModels = this.repo.getMotionDuplicates(newEntry);
|
||||
const hasDuplicates = this.repo.getViewModelList().some(motion => motion.identifier === newEntry.identifier);
|
||||
const entry: NewEntry<ViewMotion> = {
|
||||
newEntry: newEntry,
|
||||
duplicates: updateModels,
|
||||
status: updateModels.length ? 'error' : 'new',
|
||||
errors: updateModels.length ? ['Duplicates'] : []
|
||||
hasDuplicates: hasDuplicates,
|
||||
status: hasDuplicates ? 'error' : 'new',
|
||||
errors: hasDuplicates ? ['Duplicates'] : []
|
||||
};
|
||||
if (!entry.newEntry.title) {
|
||||
this.setError(entry, 'Title');
|
||||
|
@ -237,9 +237,9 @@ export class MotionPdfService {
|
||||
|
||||
// submitters
|
||||
if (!infoToExport || infoToExport.includes('submitters')) {
|
||||
const submitters = motion.submitters
|
||||
.map(submitter => {
|
||||
return submitter.full_name;
|
||||
const submitters = motion.submittersAsUsers
|
||||
.map(user => {
|
||||
return user.full_name;
|
||||
})
|
||||
.join(', ');
|
||||
|
||||
@ -793,7 +793,7 @@ export class MotionPdfService {
|
||||
text: motion.sort_parent_id ? '' : motion.identifierOrTitle
|
||||
},
|
||||
{ text: motion.sort_parent_id ? motion.identifierOrTitle : '' },
|
||||
{ text: motion.submitters.length ? motion.submitters.map(s => s.short_name).join(', ') : '' },
|
||||
{ text: motion.submitters.length ? motion.submittersAsUsers.map(s => s.short_name).join(', ') : '' },
|
||||
{ text: motion.title },
|
||||
{
|
||||
text: motion.recommendation ? this.motionRepo.getExtendedRecommendationLabel(motion) : ''
|
||||
|
@ -72,12 +72,12 @@ export class StatuteImportService extends BaseImportService<ViewStatuteParagraph
|
||||
break;
|
||||
}
|
||||
}
|
||||
const updateModels = this.repo.getViewModelList().filter(paragraph => paragraph.title === newEntry.title);
|
||||
const hasDuplicates = this.repo.getViewModelList().some(paragraph => paragraph.title === newEntry.title);
|
||||
return {
|
||||
newEntry: newEntry,
|
||||
duplicates: updateModels,
|
||||
status: updateModels.length ? 'error' : 'new',
|
||||
errors: updateModels.length ? ['Duplicates'] : []
|
||||
hasDuplicates: hasDuplicates,
|
||||
status: hasDuplicates ? 'error' : 'new',
|
||||
errors: hasDuplicates ? ['Duplicates'] : []
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Countdown } from 'app/shared/models/core/countdown';
|
||||
import { BaseProjectableViewModel } from 'app/site/base/base-projectable-view-model';
|
||||
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
|
||||
import { BaseViewModel } from 'app/site/base/base-view-model';
|
||||
|
||||
export interface CountdownTitleInformation {
|
||||
title: string;
|
||||
@ -39,8 +38,6 @@ export class ViewCountdown extends BaseProjectableViewModel<Countdown> implement
|
||||
super(Countdown.COLLECTIONSTRING, countdown);
|
||||
}
|
||||
|
||||
public updateDependencies(update: BaseViewModel): void {}
|
||||
|
||||
public getSlide(): ProjectorElementBuildDeskriptor {
|
||||
return {
|
||||
getBasicProjectorElement: options => ({
|
||||
|
@ -28,6 +28,4 @@ export class ViewProjectionDefault extends BaseViewModel<ProjectionDefault>
|
||||
public constructor(projectionDefault: ProjectionDefault) {
|
||||
super(ProjectionDefault.COLLECTIONSTRING, projectionDefault);
|
||||
}
|
||||
|
||||
public updateDependencies(update: BaseViewModel): void {}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { BaseProjectableViewModel } from 'app/site/base/base-projectable-view-model';
|
||||
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
|
||||
import { ProjectorMessage } from 'app/shared/models/core/projector-message';
|
||||
import { BaseViewModel } from 'app/site/base/base-view-model';
|
||||
import { stripHtmlTags } from 'app/shared/utils/strip-html-tags';
|
||||
|
||||
export type ProjectorMessageTitleInformation = object;
|
||||
@ -22,8 +21,6 @@ export class ViewProjectorMessage extends BaseProjectableViewModel<ProjectorMess
|
||||
super(ProjectorMessage.COLLECTIONSTRING, projectorMessage);
|
||||
}
|
||||
|
||||
public updateDependencies(update: BaseViewModel): void {}
|
||||
|
||||
public getSlide(): ProjectorElementBuildDeskriptor {
|
||||
return {
|
||||
getBasicProjectorElement: options => ({
|
||||
|
@ -106,14 +106,7 @@ export class ViewProjector extends BaseViewModel<Projector> {
|
||||
return this.projector.show_logo;
|
||||
}
|
||||
|
||||
public constructor(projector: Projector, referenceProjector?: ViewProjector) {
|
||||
public constructor(projector: Projector) {
|
||||
super(Projector.COLLECTIONSTRING, projector);
|
||||
this._referenceProjector = referenceProjector;
|
||||
}
|
||||
|
||||
public updateDependencies(update: BaseViewModel): void {
|
||||
if (update instanceof ViewProjector && this.reference_projector_id === update.id) {
|
||||
this._referenceProjector = update;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,10 +36,4 @@ export class ViewTag extends BaseViewModel<Tag> implements TagTitleInformation,
|
||||
public getDetailStateURL(): string {
|
||||
return `/tags`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the local objects if required
|
||||
* @param update
|
||||
*/
|
||||
public updateDependencies(update: BaseViewModel): void {}
|
||||
}
|
||||
|
@ -2,9 +2,6 @@ import { Topic } from 'app/shared/models/topics/topic';
|
||||
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 '../../agenda/models/view-item';
|
||||
import { BaseViewModel } from 'app/site/base/base-view-model';
|
||||
import { ViewListOfSpeakers } from '../../agenda/models/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';
|
||||
|
||||
@ -42,14 +39,8 @@ export class ViewTopic extends BaseViewModelWithAgendaItemAndListOfSpeakers impl
|
||||
return this.topic.text;
|
||||
}
|
||||
|
||||
public constructor(
|
||||
topic: Topic,
|
||||
attachments?: ViewMediafile[],
|
||||
item?: ViewItem,
|
||||
listOfSpeakers?: ViewListOfSpeakers
|
||||
) {
|
||||
super(Topic.COLLECTIONSTRING, topic, item, listOfSpeakers);
|
||||
this._attachments = attachments;
|
||||
public constructor(topic: Topic) {
|
||||
super(Topic.COLLECTIONSTRING, topic);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -89,16 +80,4 @@ export class ViewTopic extends BaseViewModelWithAgendaItemAndListOfSpeakers impl
|
||||
public hasAttachments(): boolean {
|
||||
return this.attachments && this.attachments.length > 0;
|
||||
}
|
||||
|
||||
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) {
|
||||
this.attachments.push(update);
|
||||
} else {
|
||||
this.attachments[attachmentIndex] = update;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,4 @@ export class ViewGroup extends BaseViewModel<Group> implements GroupTitleInforma
|
||||
public hasPermission(perm: string): boolean {
|
||||
return this.permissions.includes(perm);
|
||||
}
|
||||
|
||||
public updateDependencies(update: BaseViewModel): void {}
|
||||
}
|
||||
|
@ -29,6 +29,4 @@ export class ViewPersonalNote extends BaseViewModel<PersonalNote> implements Per
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public updateDependencies(update: BaseViewModel): void {}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
|
||||
import { Searchable } from 'app/site/base/searchable';
|
||||
import { SearchRepresentation } from 'app/core/ui-services/search.service';
|
||||
import { ViewGroup } from './view-group';
|
||||
import { BaseViewModel } from 'app/site/base/base-view-model';
|
||||
|
||||
export interface UserTitleInformation {
|
||||
username: string;
|
||||
@ -121,9 +120,8 @@ export class ViewUser extends BaseProjectableViewModel<User> implements UserTitl
|
||||
public getFullName: () => string;
|
||||
public getShortName: () => string;
|
||||
|
||||
public constructor(user: User, groups?: ViewGroup[]) {
|
||||
public constructor(user: User) {
|
||||
super(User.COLLECTIONSTRING, user);
|
||||
this._groups = groups;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -151,15 +149,4 @@ export class ViewUser extends BaseProjectableViewModel<User> implements UserTitl
|
||||
getDialogTitle: () => this.getTitle()
|
||||
};
|
||||
}
|
||||
|
||||
public updateDependencies(update: BaseViewModel): void {
|
||||
if (update instanceof ViewGroup && this.user.groups_id.includes(update.id)) {
|
||||
const groupIndex = this.groups.findIndex(group => group.id === update.id);
|
||||
if (groupIndex < 0) {
|
||||
this.groups.push(update);
|
||||
} else {
|
||||
this.groups[groupIndex] = update;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -269,14 +269,13 @@ export class UserImportService extends BaseImportService<ViewUser> {
|
||||
private userToEntry(newUser: ViewCsvCreateUser): NewEntry<ViewUser> {
|
||||
const newEntry: NewEntry<ViewUser> = {
|
||||
newEntry: newUser,
|
||||
duplicates: [],
|
||||
hasDuplicates: false,
|
||||
status: 'new',
|
||||
errors: []
|
||||
};
|
||||
if (newUser.isValid) {
|
||||
const updateModels = this.repo.getUserDuplicates(newUser);
|
||||
if (updateModels.length) {
|
||||
newEntry.duplicates = updateModels;
|
||||
newEntry.hasDuplicates = this.repo.getViewModelList().some(user => user.full_name === newUser.full_name);
|
||||
if (newEntry.hasDuplicates) {
|
||||
this.setError(newEntry, 'Duplicates');
|
||||
}
|
||||
} else {
|
||||
|
@ -499,7 +499,7 @@ class ListOfSpeakersViewSet(
|
||||
if not isinstance(speaker_id, int) or speakers.get(speaker_id) is None:
|
||||
raise ValidationError({"detail": "Invalid data."})
|
||||
valid_speakers.append(speakers[speaker_id])
|
||||
weight = 0
|
||||
weight = 1
|
||||
with transaction.atomic():
|
||||
for speaker in valid_speakers:
|
||||
speaker.weight = weight
|
||||
|
@ -171,3 +171,11 @@ class WorkflowAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
|
||||
base_permission = "motions.can_see"
|
||||
|
||||
|
||||
class StateAccessPermissions(BaseAccessPermissions):
|
||||
"""
|
||||
Access permissions container for State and StateViewSet.
|
||||
"""
|
||||
|
||||
base_permission = "motions.can_see"
|
||||
|
@ -89,6 +89,7 @@ class MotionsAppConfig(AppConfig):
|
||||
"Motion",
|
||||
"MotionBlock",
|
||||
"Workflow",
|
||||
"State",
|
||||
"MotionChangeRecommendation",
|
||||
"MotionCommentSection",
|
||||
):
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user