Merge pull request #5062 from FinnStutzenstein/perf
Use Proxies for ViewModels
This commit is contained in:
commit
611c0bce45
@ -97,9 +97,9 @@ export class AutoupdateService {
|
||||
elements = elements.concat(this.mapObjectsToBaseModels(collection, autoupdate.changed[collection]));
|
||||
});
|
||||
|
||||
const updateSlot = await this.DSUpdateManager.getNewUpdateSlot(this.DS, true);
|
||||
const updateSlot = await this.DSUpdateManager.getNewUpdateSlot(this.DS);
|
||||
await this.DS.set(elements, autoupdate.to_change_id);
|
||||
this.DSUpdateManager.commit(updateSlot);
|
||||
this.DSUpdateManager.commit(updateSlot, autoupdate.to_change_id, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -130,7 +130,7 @@ export class AutoupdateService {
|
||||
|
||||
await this.DS.flushToStorage(autoupdate.to_change_id);
|
||||
|
||||
this.DSUpdateManager.commit(updateSlot);
|
||||
this.DSUpdateManager.commit(updateSlot, autoupdate.to_change_id);
|
||||
} else {
|
||||
// autoupdate fully in the future. we are missing something!
|
||||
this.requestChanges();
|
||||
@ -172,7 +172,7 @@ export class AutoupdateService {
|
||||
const oldChangeId = this.DS.maxChangeId;
|
||||
const response = await this.websocketService.sendAndGetResponse<{}, AutoupdateFormat>('getElements', {});
|
||||
|
||||
const updateSlot = await this.DSUpdateManager.getNewUpdateSlot(this.DS, true);
|
||||
const updateSlot = await this.DSUpdateManager.getNewUpdateSlot(this.DS);
|
||||
let allModels: BaseModel[] = [];
|
||||
for (const collection of Object.keys(response.changed)) {
|
||||
if (this.modelMapper.isCollectionRegistered(collection)) {
|
||||
@ -183,7 +183,7 @@ export class AutoupdateService {
|
||||
}
|
||||
|
||||
await this.DS.set(allModels, response.to_change_id);
|
||||
this.DSUpdateManager.commit(updateSlot);
|
||||
this.DSUpdateManager.commit(updateSlot, response.to_change_id, true);
|
||||
|
||||
console.log(`Full update done from ${oldChangeId} to ${response.to_change_id}`);
|
||||
}
|
||||
|
@ -3,9 +3,9 @@ import { EventEmitter, Injectable } from '@angular/core';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
|
||||
import { BaseModel, ModelConstructor } from '../../shared/models/base/base-model';
|
||||
import { BaseRepository } from '../repositories/base-repository';
|
||||
import { CollectionStringMapperService } from './collection-string-mapper.service';
|
||||
import { Deferred } from '../promises/deferred';
|
||||
import { RelationCacheService } from './relation-cache.service';
|
||||
import { StorageService } from './storage.service';
|
||||
|
||||
/**
|
||||
@ -49,7 +49,7 @@ export class UpdateSlot {
|
||||
/**
|
||||
* @param DS Carries the DataStore: TODO (see below `DataStoreUpdateManagerService.getNewUpdateSlot`)
|
||||
*/
|
||||
public constructor(public readonly DS: DataStoreService, public readonly initialLoading: boolean) {
|
||||
public constructor(public readonly DS: DataStoreService) {
|
||||
this._id = UpdateSlot.ID_COUTNER++;
|
||||
}
|
||||
|
||||
@ -169,7 +169,10 @@ export class DataStoreUpdateManagerService {
|
||||
/**
|
||||
* @param mapperService
|
||||
*/
|
||||
public constructor(private mapperService: CollectionStringMapperService) {}
|
||||
public constructor(
|
||||
private mapperService: CollectionStringMapperService,
|
||||
private relationCacheService: RelationCacheService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieve the current update slot.
|
||||
@ -185,13 +188,13 @@ export class DataStoreUpdateManagerService {
|
||||
* @param DS The DataStore. This is a hack, becuase we cannot use the DataStore
|
||||
* here, because these are cyclic dependencies... --> TODO
|
||||
*/
|
||||
public async getNewUpdateSlot(DS: DataStoreService, initialLoading: boolean = false): Promise<UpdateSlot> {
|
||||
public async getNewUpdateSlot(DS: DataStoreService): Promise<UpdateSlot> {
|
||||
if (this.currentUpdateSlot) {
|
||||
const request = new Deferred();
|
||||
this.updateSlotRequests.push(request);
|
||||
await request;
|
||||
}
|
||||
this.currentUpdateSlot = new UpdateSlot(DS, initialLoading);
|
||||
this.currentUpdateSlot = new UpdateSlot(DS);
|
||||
return this.currentUpdateSlot;
|
||||
}
|
||||
|
||||
@ -204,7 +207,7 @@ export class DataStoreUpdateManagerService {
|
||||
*
|
||||
* @param slot The slot to commit
|
||||
*/
|
||||
public commit(slot: UpdateSlot): void {
|
||||
public commit(slot: UpdateSlot, changeId: number, resetCache: boolean = false): void {
|
||||
if (!this.currentUpdateSlot || !this.currentUpdateSlot.equal(slot)) {
|
||||
throw new Error('No or wrong update slot to be finished!');
|
||||
}
|
||||
@ -212,36 +215,26 @@ export class DataStoreUpdateManagerService {
|
||||
|
||||
// notify repositories in two phases
|
||||
const repositories = this.mapperService.getAllRepositories();
|
||||
// just commit the update in a repository, if something was changed. Save
|
||||
// this information in this mapping. the boolean is not evaluated; if there is an
|
||||
const affectedRepos: { [collection: string]: BaseRepository<any, any, any> } = {};
|
||||
|
||||
if (resetCache) {
|
||||
this.relationCacheService.reset();
|
||||
}
|
||||
|
||||
// Phase 1: deleting and creating of view models (in this order)
|
||||
repositories.forEach(repo => {
|
||||
const deletedModelIds = slot.getDeletedModelIdsForCollection(repo.collectionString);
|
||||
repo.deleteModels(deletedModelIds);
|
||||
this.relationCacheService.registerDeletedModels(repo.collectionString, deletedModelIds);
|
||||
const changedModelIds = slot.getChangedModelIdsForCollection(repo.collectionString);
|
||||
repo.changedModels(changedModelIds, slot.initialLoading);
|
||||
|
||||
if (deletedModelIds.length || changedModelIds.length) {
|
||||
affectedRepos[repo.collectionString] = repo;
|
||||
}
|
||||
repo.changedModels(changedModelIds);
|
||||
this.relationCacheService.registerChangedModels(repo.collectionString, changedModelIds, changeId);
|
||||
});
|
||||
|
||||
// Phase 2: updating dependencies (deleting ad changing in this order)
|
||||
// Phase 2: updating all repositories
|
||||
repositories.forEach(repo => {
|
||||
if (repo.updateDependenciesForDeletedModels(slot.getDeletedModels())) {
|
||||
affectedRepos[repo.collectionString] = repo;
|
||||
}
|
||||
if (repo.updateDependenciesForChangedModels(slot.getChangedModels())) {
|
||||
affectedRepos[repo.collectionString] = repo;
|
||||
}
|
||||
repo.commitUpdate();
|
||||
});
|
||||
|
||||
// Phase 3: committing the update to all affected repos. This will trigger all
|
||||
// list observables/subjects to emit the new list.
|
||||
Object.values(affectedRepos).forEach(repo => repo.commitUpdate());
|
||||
|
||||
slot.DS.triggerModifiedObservable();
|
||||
|
||||
// serve next slot request
|
||||
@ -350,7 +343,7 @@ export class DataStoreService {
|
||||
// This promise will be resolved with cached datastore.
|
||||
const store = await this.storageService.get<JsonStorage>(DataStoreService.cachePrefix + 'DS');
|
||||
if (store) {
|
||||
const updateSlot = await this.DSUpdateManager.getNewUpdateSlot(this, true);
|
||||
const updateSlot = await this.DSUpdateManager.getNewUpdateSlot(this);
|
||||
|
||||
// There is a store. Deserialize it
|
||||
this.jsonStore = store;
|
||||
@ -370,7 +363,7 @@ export class DataStoreService {
|
||||
});
|
||||
});
|
||||
|
||||
this.DSUpdateManager.commit(updateSlot);
|
||||
this.DSUpdateManager.commit(updateSlot, maxChangeId, true);
|
||||
} else {
|
||||
await this.clear();
|
||||
}
|
||||
|
@ -0,0 +1,16 @@
|
||||
import { inject, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { E2EImportsModule } from '../../../e2e-imports.module';
|
||||
import { RelationCacheService } from './relation-cache.service';
|
||||
|
||||
describe('RelationCacheService', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule],
|
||||
providers: [RelationCacheService]
|
||||
});
|
||||
});
|
||||
it('should be created', inject([RelationCacheService], (service: RelationCacheService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
});
|
84
client/src/app/core/core-services/relation-cache.service.ts
Normal file
84
client/src/app/core/core-services/relation-cache.service.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
export interface CacheChangeIds {
|
||||
[elementId: string]: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles caching metadata for the relation manager.
|
||||
*
|
||||
* Mainly holds an object mapping element ids to their last updates change ids.
|
||||
* The manager can detect invalid caches, if the change id in the cache's metadata
|
||||
* diverges from the change ids in this service.
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class RelationCacheService {
|
||||
private cache: {
|
||||
[elementId: string]: number;
|
||||
} = {};
|
||||
|
||||
public constructor() {}
|
||||
|
||||
/**
|
||||
* Reset the cache.
|
||||
*/
|
||||
public reset(): void {
|
||||
this.cache = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes models from this cache.
|
||||
*
|
||||
* @param collection Collection
|
||||
* @param ids Ids from all models in the collection
|
||||
*/
|
||||
public registerDeletedModels(collection: string, ids: number[]): void {
|
||||
ids.forEach(id => {
|
||||
const elementId = collection + ':' + id;
|
||||
delete this.cache[elementId];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds models to the cache with the given change id.
|
||||
*
|
||||
* @param collection Collection
|
||||
* @param ids Ids from all models in the collection
|
||||
* @param changeId The change id to put into the cache
|
||||
*/
|
||||
public registerChangedModels(collection: string, ids: number[], changeId: number): void {
|
||||
ids.forEach(id => {
|
||||
const elementId = collection + ':' + id;
|
||||
this.cache[elementId] = changeId;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the change id for one element.
|
||||
*
|
||||
* @param elementId The element to query.
|
||||
*/
|
||||
public query(elementId: string): number | null {
|
||||
return this.cache[elementId] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks, if all given change ids are valid.
|
||||
*/
|
||||
public checkCacheValidity(changeIds: CacheChangeIds): boolean {
|
||||
if (!changeIds) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const elementIds = Object.keys(changeIds);
|
||||
if (!elementIds.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return elementIds.every(elementId => {
|
||||
return this.query(elementId) === changeIds[elementId];
|
||||
});
|
||||
}
|
||||
}
|
@ -2,74 +2,35 @@ import { Injectable } from '@angular/core';
|
||||
|
||||
import { BaseModel } from 'app/shared/models/base/base-model';
|
||||
import { BaseViewModel, ViewModelConstructor } from 'app/site/base/base-view-model';
|
||||
import { ModelDescriptor, NestedModelDescriptors } from '../repositories/base-repository';
|
||||
import { CacheChangeIds, RelationCacheService } from './relation-cache.service';
|
||||
import {
|
||||
BaseOrderedRelation,
|
||||
isCustomRelationDefinition,
|
||||
isGenericRelationDefinition,
|
||||
isNestedRelationDefinition,
|
||||
isNormalRelationDefinition,
|
||||
isReverseRelationDefinition,
|
||||
RelationDefinition,
|
||||
ReverseRelationDefinition
|
||||
RelationDefinition
|
||||
} from '../definitions/relations';
|
||||
import { ViewModelStoreService } from './view-model-store.service';
|
||||
|
||||
/**
|
||||
* Manages relations between view models. This service is and should only used by the
|
||||
* base repository to offload maanging relations between view models.
|
||||
* base repository to offload managing relations between view models.
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class RelationManagerService {
|
||||
public constructor(private viewModelStoreService: ViewModelStoreService) {}
|
||||
public constructor(
|
||||
private viewModelStoreService: ViewModelStoreService,
|
||||
private relationCacheService: RelationCacheService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Sorts the array of foreign view models in the given view models for the given relation.
|
||||
*/
|
||||
public sortByRelation<V extends BaseViewModel, VForegin extends BaseViewModel>(
|
||||
relation: BaseOrderedRelation<VForegin>,
|
||||
viewModel: V
|
||||
): void {
|
||||
const order = relation.order;
|
||||
viewModel['_' + relation.ownKey].sort((a: BaseViewModel, b: BaseViewModel) => {
|
||||
if (!order || a[order] === b[order]) {
|
||||
return a.id - b.id;
|
||||
} else {
|
||||
return a[order] - b[order];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a view model from the given model and model ctor. All dependencies will be
|
||||
* set accorting to relations.
|
||||
*/
|
||||
public createViewModel<M extends BaseModel, V extends BaseViewModel>(
|
||||
model: M,
|
||||
modelCtor: ViewModelConstructor<V>,
|
||||
relations: RelationDefinition[],
|
||||
initialLoading: boolean
|
||||
): V {
|
||||
const viewModel = new modelCtor(model) as V;
|
||||
|
||||
relations.forEach(relation => {
|
||||
this.setRelationsInViewModel(model, viewModel, relation, initialLoading);
|
||||
});
|
||||
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets one foreign view model in the view model according to the relation and the information
|
||||
* from the model.
|
||||
*/
|
||||
protected setRelationsInViewModel<M extends BaseModel, V extends BaseViewModel>(
|
||||
public handleRelation<M extends BaseModel, V extends BaseViewModel>(
|
||||
model: M,
|
||||
viewModel: V,
|
||||
relation: RelationDefinition,
|
||||
initialLoading: boolean
|
||||
): void {
|
||||
relation: RelationDefinition
|
||||
): any {
|
||||
if (isNormalRelationDefinition(relation)) {
|
||||
if (
|
||||
(relation.type === 'M2M' || relation.type === 'O2M') &&
|
||||
@ -80,22 +41,16 @@ export class RelationManagerService {
|
||||
relation.foreignViewModel,
|
||||
model[relation.ownIdKey]
|
||||
);
|
||||
viewModel['_' + relation.ownKey] = foreignViewModels;
|
||||
this.sortByRelation(relation, viewModel);
|
||||
if (relation.afterSetRelation) {
|
||||
relation.afterSetRelation(viewModel, foreignViewModels);
|
||||
}
|
||||
this.sortViewModels(foreignViewModels, relation.order);
|
||||
return foreignViewModels;
|
||||
} else if (relation.type === 'M2O') {
|
||||
const foreignViewModel = this.viewModelStoreService.get(
|
||||
relation.foreignViewModel,
|
||||
model[relation.ownIdKey]
|
||||
);
|
||||
viewModel['_' + relation.ownKey] = foreignViewModel;
|
||||
if (relation.afterSetRelation) {
|
||||
relation.afterSetRelation(viewModel, foreignViewModel);
|
||||
return foreignViewModel;
|
||||
}
|
||||
}
|
||||
} else if (isReverseRelationDefinition(relation) && !initialLoading) {
|
||||
} else if (isReverseRelationDefinition(relation)) {
|
||||
if (relation.type === 'M2M') {
|
||||
const foreignViewModels = this.viewModelStoreService.filter(
|
||||
relation.foreignViewModel,
|
||||
@ -104,16 +59,16 @@ export class RelationManagerService {
|
||||
foreignViewModel[relation.foreignIdKey].constructor === Array &&
|
||||
foreignViewModel[relation.foreignIdKey].includes(model.id)
|
||||
);
|
||||
viewModel['_' + relation.ownKey] = foreignViewModels;
|
||||
this.sortByRelation(relation, viewModel);
|
||||
this.sortViewModels(foreignViewModels, relation.order);
|
||||
return foreignViewModels;
|
||||
} else if (relation.type === 'O2M') {
|
||||
const foreignViewModels = this.viewModelStoreService.filter(
|
||||
relation.foreignViewModel,
|
||||
foreignViewModel =>
|
||||
foreignViewModel[relation.foreignIdKey] && foreignViewModel[relation.foreignIdKey] === model.id
|
||||
);
|
||||
viewModel['_' + relation.ownKey] = foreignViewModels;
|
||||
this.sortByRelation(relation, viewModel);
|
||||
this.sortViewModels(foreignViewModels, relation.order);
|
||||
return foreignViewModels;
|
||||
} else if (relation.type === 'M2O') {
|
||||
const foreignViewModel = this.viewModelStoreService.find(
|
||||
relation.foreignViewModel,
|
||||
@ -121,236 +76,156 @@ export class RelationManagerService {
|
||||
_foreignViewModel[relation.foreignIdKey] &&
|
||||
_foreignViewModel[relation.foreignIdKey] === model.id
|
||||
);
|
||||
viewModel['_' + relation.ownKey] = foreignViewModel;
|
||||
return foreignViewModel;
|
||||
}
|
||||
} else if (isNestedRelationDefinition(relation)) {
|
||||
const foreignModels = model[relation.ownKey].map(m => new relation.foreignModel(m));
|
||||
const foreignViewModels: BaseViewModel[] = foreignModels.map((foreignModel: BaseModel) =>
|
||||
this.createViewModel(
|
||||
foreignModel,
|
||||
relation.foreignViewModel,
|
||||
relation.relationDefinition || [],
|
||||
initialLoading
|
||||
)
|
||||
);
|
||||
viewModel['_' + relation.ownKey] = foreignViewModels;
|
||||
this.sortByRelation(relation, viewModel);
|
||||
} else if (isGenericRelationDefinition(relation)) {
|
||||
const contentObject = this.viewModelStoreService.get<BaseViewModel>(
|
||||
model[relation.ownContentObjectDataKey].collection,
|
||||
model[relation.ownContentObjectDataKey].id
|
||||
);
|
||||
if (contentObject && relation.isVForeign(contentObject)) {
|
||||
viewModel['_' + relation.ownKey] = contentObject;
|
||||
return contentObject;
|
||||
}
|
||||
} else if (isCustomRelationDefinition(relation)) {
|
||||
relation.setRelations(model, viewModel);
|
||||
return relation.get(model, viewModel);
|
||||
}
|
||||
}
|
||||
|
||||
public handleCachedRelation<V extends BaseViewModel>(
|
||||
property: string,
|
||||
target: V,
|
||||
model: BaseModel,
|
||||
viewModel: BaseViewModel,
|
||||
relation: RelationDefinition
|
||||
): any {
|
||||
let result: any;
|
||||
|
||||
const cacheProperty = '__' + property;
|
||||
const cachePropertyChangeIds = cacheProperty + '_cids';
|
||||
let cached: boolean = cacheProperty in target;
|
||||
let changeIds: CacheChangeIds | null = null;
|
||||
if (cached) {
|
||||
result = target[cacheProperty];
|
||||
changeIds = target[cachePropertyChangeIds];
|
||||
}
|
||||
|
||||
if (!isCustomRelationDefinition(relation)) {
|
||||
if (cached) {
|
||||
cached = this.relationCacheService.checkCacheValidity(changeIds);
|
||||
}
|
||||
|
||||
if (!cached) {
|
||||
result = this.handleRelation(model, viewModel, relation) as BaseViewModel | BaseViewModel[];
|
||||
|
||||
if (result) {
|
||||
// Cache it:
|
||||
target[cacheProperty] = result;
|
||||
const newChangeIds = {};
|
||||
if (Array.isArray(result)) {
|
||||
result.forEach(
|
||||
(_vm: BaseViewModel) =>
|
||||
(newChangeIds[_vm.elementId] = this.relationCacheService.query(_vm.elementId))
|
||||
);
|
||||
} else {
|
||||
newChangeIds[result.elementId] = this.relationCacheService.query(result.elementId);
|
||||
}
|
||||
target[cachePropertyChangeIds] = newChangeIds;
|
||||
} else {
|
||||
delete target[cacheProperty];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Custom relations
|
||||
const obj = relation.getCacheObjectToCheck(viewModel);
|
||||
if (cached) {
|
||||
if (obj && changeIds && changeIds[obj.elementId]) {
|
||||
cached = this.relationCacheService.query(obj.elementId) === changeIds[obj.elementId];
|
||||
} else {
|
||||
cached = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cached) {
|
||||
result = this.handleRelation(model, viewModel, relation);
|
||||
|
||||
if (result && obj) {
|
||||
target[cacheProperty] = result;
|
||||
target[cachePropertyChangeIds] = {};
|
||||
target[cachePropertyChangeIds][obj.elementId] = this.relationCacheService.query(obj.elementId);
|
||||
} else {
|
||||
delete target[cachePropertyChangeIds];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an own view model with an deleted model implicit given by the deletedId and
|
||||
* the collection via the relation.
|
||||
*
|
||||
* @return true, if something was updated.
|
||||
* Sorts the array of foreign view models in the given view models for the given relation.
|
||||
*/
|
||||
public updateSingleDependencyForDeletedModel(
|
||||
ownViewModel: BaseViewModel,
|
||||
relation: ReverseRelationDefinition,
|
||||
deletedId: number
|
||||
): boolean {
|
||||
// In both relations, the ownViewModel holds an array of foreignViewModels. Try to find the deleted
|
||||
// foreignViewModel in this array and remove it.
|
||||
if (relation.type === 'O2M' || relation.type === 'M2M') {
|
||||
const ownModelArray = <any>ownViewModel['_' + relation.ownKey];
|
||||
if (!ownModelArray) {
|
||||
return false;
|
||||
}
|
||||
// We have the array of foreign view models for our own view model. Put the foreignViewModel
|
||||
// into it (replace or push).
|
||||
const index = ownModelArray.findIndex(foreignViewModel => foreignViewModel.id === deletedId);
|
||||
if (index > -1) {
|
||||
ownModelArray.splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// The ownViewModel holds one foreignViewModel. Check, if it is the deleted one.
|
||||
else if (relation.type === 'M2O') {
|
||||
if (ownViewModel['_' + relation.ownKey] && ownViewModel['_' + relation.ownKey].id === deletedId) {
|
||||
ownViewModel['_' + relation.ownKey] = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an own view model with an implicit given model by the collection and changedId.
|
||||
*
|
||||
* @return true, if something was updated.
|
||||
*/
|
||||
public updateSingleDependencyForChangedModel(
|
||||
ownViewModel: BaseViewModel,
|
||||
relation: RelationDefinition,
|
||||
collection: string,
|
||||
changedId: number
|
||||
): boolean {
|
||||
if (isNormalRelationDefinition(relation)) {
|
||||
if (relation.type === 'M2M' || relation.type === 'O2M') {
|
||||
// For the side of the ownViewModel these relations are the same:
|
||||
// the ownViewModel does have may foreign models and we do have a normal relation (not a
|
||||
// reverse one), we just set the many-part of the relation in the ownViewModel.
|
||||
if (
|
||||
ownViewModel[relation.ownIdKey] &&
|
||||
ownViewModel[relation.ownIdKey].constructor === Array &&
|
||||
ownViewModel[relation.ownIdKey].includes(changedId) // The foreign view model belongs to us.
|
||||
) {
|
||||
const foreignViewModel = <any>this.viewModelStoreService.get(collection, changedId);
|
||||
this.setForeingViewModelInOwnViewModelArray(foreignViewModel, ownViewModel, relation.ownKey);
|
||||
if (relation.afterDependencyChange) {
|
||||
relation.afterDependencyChange(ownViewModel, foreignViewModel);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else if (relation.type === 'M2O') {
|
||||
if (ownViewModel[relation.ownIdKey] === <any>changedId) {
|
||||
// Check, if this is the matching foreign view model.
|
||||
const foreignViewModel = this.viewModelStoreService.get(collection, changedId);
|
||||
ownViewModel['_' + relation.ownKey] = <any>foreignViewModel;
|
||||
if (relation.afterDependencyChange) {
|
||||
relation.afterDependencyChange(ownViewModel, foreignViewModel);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (isReverseRelationDefinition(relation)) {
|
||||
const foreignViewModel = <any>this.viewModelStoreService.get(collection, changedId);
|
||||
|
||||
// The foreign model has one id. Check, if the ownViewModel is the matching view model.
|
||||
// If so, add the foreignViewModel to the array from the ownViewModel (with many foreignViewModels)
|
||||
// If not, check, if the model _was_ in our foreignViewModel array and remove it.
|
||||
if (relation.type === 'O2M') {
|
||||
if (foreignViewModel[relation.foreignIdKey] === ownViewModel.id) {
|
||||
this.setForeingViewModelInOwnViewModelArray(foreignViewModel, ownViewModel, relation.ownKey);
|
||||
return true;
|
||||
public sortViewModels(viewModels: BaseViewModel[], order?: string): void {
|
||||
viewModels.sort((a: BaseViewModel, b: BaseViewModel) => {
|
||||
if (!order || a[order] === b[order]) {
|
||||
return a.id - b.id;
|
||||
} else {
|
||||
const ownViewModelArray = <any>ownViewModel['_' + relation.ownKey];
|
||||
if (ownViewModelArray) {
|
||||
// We have the array of foreign view models for our own view model. Remove the foreignViewModel (if it was there).
|
||||
const index = ownViewModelArray.findIndex(
|
||||
_foreignViewModel => _foreignViewModel.id === foreignViewModel.id
|
||||
);
|
||||
if (index > -1) {
|
||||
ownViewModelArray.splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The foreign model should hold an array of ids. If the ownViewModel is in it, the foreignViewModel must
|
||||
// be included into the array from the ownViewModel (with many foreignViewModels).
|
||||
// If not, check, if the model _was_ in our foreignViewModel array and remove it.
|
||||
else if (relation.type === 'M2M') {
|
||||
if (
|
||||
foreignViewModel[relation.foreignIdKey] &&
|
||||
foreignViewModel[relation.foreignIdKey].constructor === Array &&
|
||||
foreignViewModel[relation.foreignIdKey].includes(ownViewModel.id)
|
||||
) {
|
||||
this.setForeingViewModelInOwnViewModelArray(foreignViewModel, ownViewModel, relation.ownKey);
|
||||
return true;
|
||||
} else {
|
||||
const ownViewModelArray = <any>ownViewModel['_' + relation.ownKey];
|
||||
if (ownViewModelArray) {
|
||||
// We have the array of foreign view models for our own view model. Remove the foreignViewModel (if it was there).
|
||||
const index = ownViewModelArray.findIndex(
|
||||
_foreignViewModel => _foreignViewModel.id === foreignViewModel.id
|
||||
);
|
||||
if (index > -1) {
|
||||
ownViewModelArray.splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The foreign model should hold an array of ids. If the ownViewModel is in it, the foreignViewModel is the
|
||||
// one and only matching model for the ownViewModel. If the ownViewModel is not in it, check if the
|
||||
// foreignViewModel _was_ the matching model. If so, set the reference to null.
|
||||
else if (relation.type === 'M2O') {
|
||||
if (
|
||||
foreignViewModel[relation.foreignIdKey] &&
|
||||
foreignViewModel[relation.foreignIdKey].constructor === Array &&
|
||||
foreignViewModel[relation.foreignIdKey].includes(ownViewModel.id)
|
||||
) {
|
||||
ownViewModel['_' + relation.ownKey] = foreignViewModel;
|
||||
return true;
|
||||
} else if (
|
||||
ownViewModel['_' + relation.ownKey] &&
|
||||
ownViewModel['_' + relation.ownKey].id === foreignViewModel.id
|
||||
) {
|
||||
ownViewModel['_' + relation.ownKey] = null;
|
||||
}
|
||||
}
|
||||
} else if (isNestedRelationDefinition(relation)) {
|
||||
let updated = false;
|
||||
(relation.relationDefinition || []).forEach(nestedRelation => {
|
||||
const nestedViewModels = ownViewModel[relation.ownKey] as BaseViewModel[];
|
||||
nestedViewModels.forEach(nestedViewModel => {
|
||||
if (
|
||||
this.updateSingleDependencyForChangedModel(
|
||||
nestedViewModel,
|
||||
nestedRelation,
|
||||
collection,
|
||||
changedId
|
||||
)
|
||||
) {
|
||||
updated = true;
|
||||
return a[order] - b[order];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public createViewModel<M extends BaseModel, V extends BaseViewModel>(
|
||||
model: M,
|
||||
viewModelCtor: ViewModelConstructor<V>,
|
||||
relationsByKey: { [key: string]: RelationDefinition },
|
||||
nestedModelDescriptors: NestedModelDescriptors
|
||||
): V {
|
||||
let viewModel = new viewModelCtor(model);
|
||||
viewModel = new Proxy(viewModel, {
|
||||
get: (target: V, property) => {
|
||||
let result: any;
|
||||
const _model: M = target.getModel();
|
||||
const relation = typeof property === 'string' ? relationsByKey[property] : null;
|
||||
|
||||
if (property in target) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(viewModelCtor.prototype, property);
|
||||
if (descriptor && descriptor.get) {
|
||||
result = descriptor.get.bind(viewModel)();
|
||||
} else {
|
||||
result = target[property];
|
||||
}
|
||||
} else if (property in _model) {
|
||||
result = _model[property];
|
||||
} else if (relation) {
|
||||
result = this.handleCachedRelation(<any>property, target, _model, viewModel, relation);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
});
|
||||
return updated;
|
||||
} else if (isCustomRelationDefinition(relation)) {
|
||||
const foreignViewModel = <any>this.viewModelStoreService.get(collection, changedId);
|
||||
return relation.updateDependency(ownViewModel, foreignViewModel);
|
||||
} else if (isGenericRelationDefinition(relation)) {
|
||||
const foreignModel = <any>this.viewModelStoreService.get(collection, changedId);
|
||||
if (
|
||||
foreignModel &&
|
||||
foreignModel.collectionString === ownViewModel[relation.ownContentObjectDataKey].collection &&
|
||||
foreignModel.id === ownViewModel[relation.ownContentObjectDataKey].id
|
||||
) {
|
||||
if (relation.isVForeign(foreignModel)) {
|
||||
ownViewModel['_' + relation.ownKey] = foreignModel;
|
||||
return true;
|
||||
} else {
|
||||
console.warn(`The object is not an ${relation.VForeignVerbose}:` + foreignModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
// set nested models
|
||||
(nestedModelDescriptors[model.collectionString] || []).forEach(
|
||||
(modelDescriptor: ModelDescriptor<BaseModel, BaseViewModel>) => {
|
||||
const nestedModels = (model[modelDescriptor.ownKey] || []).map((nestedModel: object) => {
|
||||
return new modelDescriptor.foreignModel(nestedModel);
|
||||
});
|
||||
const nestedViewModels = nestedModels.map(nestedModel => {
|
||||
const nestedViewModel = this.createViewModel(
|
||||
nestedModel,
|
||||
modelDescriptor.foreignViewModel,
|
||||
modelDescriptor.relationDefinitionsByKey,
|
||||
nestedModelDescriptors
|
||||
);
|
||||
Object.keys(modelDescriptor.titles || {}).forEach(name => {
|
||||
nestedViewModel[name] = () => modelDescriptor.titles[name](nestedViewModel);
|
||||
});
|
||||
return nestedViewModel;
|
||||
});
|
||||
this.sortViewModels(nestedViewModels, modelDescriptor.order);
|
||||
|
||||
private setForeingViewModelInOwnViewModelArray(
|
||||
foreignViewModel: BaseViewModel,
|
||||
ownViewModel: BaseViewModel,
|
||||
ownKey: string
|
||||
): void {
|
||||
let ownViewModelArray = <any>ownViewModel['_' + ownKey];
|
||||
if (!ownViewModelArray) {
|
||||
ownViewModel['_' + ownKey] = [];
|
||||
ownViewModelArray = <any>ownViewModel['_' + ownKey]; // get the new reference
|
||||
}
|
||||
// We have the array of foreign view models for our own view model. Put the foreignViewModel
|
||||
// into it (replace or push).
|
||||
const index = ownViewModelArray.findIndex(_foreignViewModel => _foreignViewModel.id === foreignViewModel.id);
|
||||
if (index < 0) {
|
||||
ownViewModelArray.push(foreignViewModel);
|
||||
} else {
|
||||
ownViewModelArray[index] = foreignViewModel;
|
||||
viewModel[modelDescriptor.ownKey] = nestedViewModels;
|
||||
}
|
||||
);
|
||||
return viewModel;
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ export class TimeTravelService {
|
||||
* @param history the desired point in the history of OpenSlides
|
||||
*/
|
||||
public async loadHistoryPoint(history: History): Promise<void> {
|
||||
const updateSlot = await this.DSUpdateManager.getNewUpdateSlot(this.DS, true);
|
||||
const updateSlot = await this.DSUpdateManager.getNewUpdateSlot(this.DS);
|
||||
|
||||
await this.stopTime(history);
|
||||
const historyData: HistoryData = await this.getHistoryData(history);
|
||||
@ -67,7 +67,7 @@ export class TimeTravelService {
|
||||
});
|
||||
await this.DS.set(allModels, 0);
|
||||
|
||||
this.DSUpdateManager.commit(updateSlot);
|
||||
this.DSUpdateManager.commit(updateSlot, 1, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,12 +1,11 @@
|
||||
import { BaseModel, ModelConstructor } from 'app/shared/models/base/base-model';
|
||||
import { BaseModel } from 'app/shared/models/base/base-model';
|
||||
import { BaseViewModel, ViewModelConstructor } from 'app/site/base/base-view-model';
|
||||
|
||||
// All "standard" relations.
|
||||
export type RelationDefinition<VForeign extends BaseViewModel = BaseViewModel> =
|
||||
| NormalRelationDefinition<VForeign>
|
||||
| ReverseRelationDefinition<VForeign>
|
||||
| NestedRelationDefinition<VForeign>
|
||||
| CustomRelationDefinition<VForeign>
|
||||
| CustomRelationDefinition
|
||||
| GenericRelationDefinition<VForeign>;
|
||||
|
||||
interface BaseRelationDefinition<VForeign extends BaseViewModel> {
|
||||
@ -39,8 +38,6 @@ interface BaseNormalRelationDefinition<VForeign extends BaseViewModel> extends B
|
||||
* the model and view model. E.g. `category_id` in a motion.
|
||||
*/
|
||||
ownIdKey: string;
|
||||
|
||||
afterDependencyChange?: (ownViewModel: BaseViewModel, foreignViewModel: BaseViewModel) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -58,19 +55,16 @@ interface NormalM2MRelationDefinition<VForeign extends BaseViewModel>
|
||||
extends BaseNormalRelationDefinition<VForeign>,
|
||||
BaseOrderedRelation<VForeign> {
|
||||
type: 'M2M';
|
||||
afterSetRelation?: (ownViewModel: BaseViewModel, foreignViewModels: BaseViewModel[]) => void;
|
||||
}
|
||||
|
||||
interface NormalO2MRelationDefinition<VForeign extends BaseViewModel>
|
||||
extends BaseNormalRelationDefinition<VForeign>,
|
||||
BaseOrderedRelation<VForeign> {
|
||||
type: 'O2M';
|
||||
afterSetRelation?: (ownViewModel: BaseViewModel, foreignViewModels: BaseViewModel[]) => void;
|
||||
}
|
||||
|
||||
interface NormalM2ORelationDefinition<VForeign extends BaseViewModel> extends BaseNormalRelationDefinition<VForeign> {
|
||||
type: 'M2O';
|
||||
afterSetRelation?: (ownViewModel: BaseViewModel, foreignViewModel: BaseViewModel | null) => void;
|
||||
}
|
||||
|
||||
export type NormalRelationDefinition<VForeign extends BaseViewModel = BaseViewModel> =
|
||||
@ -131,29 +125,6 @@ export function isReverseRelationDefinition(obj: RelationDefinition): obj is Rev
|
||||
return (relation.type === 'M2O' || relation.type === 'O2M' || relation.type === 'M2M') && !!relation.foreignIdKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Nested relations in the REST-API. For the most values see
|
||||
* `NormalRelationDefinition`.
|
||||
*/
|
||||
export interface NestedRelationDefinition<VForeign extends BaseViewModel> extends BaseOrderedRelation<VForeign> {
|
||||
type: 'nested';
|
||||
ownKey: string;
|
||||
|
||||
/**
|
||||
* The nested relations.
|
||||
*/
|
||||
relationDefinition?: RelationDefinition[];
|
||||
|
||||
/**
|
||||
* The matching model for the foreignViewModel.
|
||||
*/
|
||||
foreignModel: ModelConstructor<BaseModel>;
|
||||
}
|
||||
|
||||
export function isNestedRelationDefinition(obj: RelationDefinition): obj is NestedRelationDefinition<BaseViewModel> {
|
||||
return obj.type === 'nested';
|
||||
}
|
||||
|
||||
interface GenericRelationDefinition<VForeign extends BaseViewModel = BaseViewModel> {
|
||||
type: 'generic';
|
||||
|
||||
@ -180,21 +151,19 @@ export function isGenericRelationDefinition(obj: RelationDefinition): obj is Gen
|
||||
/**
|
||||
* A custom relation with callbacks with things todo.
|
||||
*/
|
||||
interface CustomRelationDefinition<VForeign extends BaseViewModel> {
|
||||
interface CustomRelationDefinition {
|
||||
type: 'custom';
|
||||
foreignViewModel: ViewModelConstructor<VForeign>;
|
||||
|
||||
/**
|
||||
* Called, when the view model is created from the model.
|
||||
* The key to access the custom relation.
|
||||
*/
|
||||
setRelations: (model: BaseModel, viewModel: BaseViewModel) => void;
|
||||
ownKey: string;
|
||||
|
||||
/**
|
||||
* Called, when the dependency was updated.
|
||||
*/
|
||||
updateDependency: (ownViewModel: BaseViewModel, foreignViewModel: VForeign) => boolean;
|
||||
get: (model: BaseModel, viewModel: BaseViewModel) => any;
|
||||
|
||||
getCacheObjectToCheck: (viewModel: BaseViewModel) => BaseViewModel | null;
|
||||
}
|
||||
|
||||
export function isCustomRelationDefinition(obj: RelationDefinition): obj is CustomRelationDefinition<BaseViewModel> {
|
||||
export function isCustomRelationDefinition(obj: RelationDefinition): obj is CustomRelationDefinition {
|
||||
return obj.type === 'custom';
|
||||
}
|
||||
|
15
client/src/app/core/mixins.ts
Normal file
15
client/src/app/core/mixins.ts
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Mixes all own properties of all baseCtors into the derivedCtor.
|
||||
* See https://www.typescriptlang.org/docs/handbook/mixins.html
|
||||
*/
|
||||
export function applyMixins(derivedCtor: any, baseCtors: any[]): void {
|
||||
baseCtors.forEach(baseCtor => {
|
||||
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
|
||||
Object.defineProperty(
|
||||
derivedCtor.prototype,
|
||||
name,
|
||||
Object.getOwnPropertyDescriptor(baseCtor.prototype, name)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
@ -507,7 +507,7 @@ export class PdfDocumentService {
|
||||
* Cancel the pdf generation
|
||||
*/
|
||||
private cancelPdfCreation(): void {
|
||||
if (!!this.pdfWorker) {
|
||||
if (this.pdfWorker) {
|
||||
this.pdfWorker.terminate();
|
||||
this.pdfWorker = null;
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ function applyLayout(content: any): void {
|
||||
if (Array.isArray(section)) {
|
||||
applyLayout(section);
|
||||
} else {
|
||||
if (!!section.layout) {
|
||||
if (section.layout) {
|
||||
let layout: object;
|
||||
switch (section.layout) {
|
||||
case 'switchColorTableLayout': {
|
||||
@ -48,7 +48,7 @@ function applyLayout(content: any): void {
|
||||
}
|
||||
}
|
||||
|
||||
if (!!layout) {
|
||||
if (layout) {
|
||||
section.layout = layout;
|
||||
}
|
||||
}
|
||||
@ -94,7 +94,7 @@ addEventListener('message', ({ data }) => {
|
||||
|
||||
applyLayout(data.doc.content);
|
||||
|
||||
if (!!data.doc.tmpfooter) {
|
||||
if (data.doc.tmpfooter) {
|
||||
addPageNumbers(data);
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,6 @@ import { ItemTitleInformation, ViewItem } from 'app/site/agenda/models/view-item
|
||||
import { ViewAssignment } from 'app/site/assignments/models/view-assignment';
|
||||
import {
|
||||
BaseViewModelWithAgendaItem,
|
||||
IBaseViewModelWithAgendaItem,
|
||||
isBaseViewModelWithAgendaItem
|
||||
} from 'app/site/base/base-view-model-with-agenda-item';
|
||||
import { ViewMotion } from 'app/site/motions/models/view-motion';
|
||||
@ -129,8 +128,8 @@ export class ItemRepositoryService extends BaseHasContentObjectRepository<
|
||||
*
|
||||
* @returns {ViewItem} The modified item extended with the `getSubtitle()`-function.
|
||||
*/
|
||||
protected createViewModelWithTitles(model: Item, initialLoading: boolean): ViewItem {
|
||||
const viewModel = super.createViewModelWithTitles(model, initialLoading);
|
||||
protected createViewModelWithTitles(model: Item): ViewItem {
|
||||
const viewModel = super.createViewModelWithTitles(model);
|
||||
viewModel.getSubtitle = () => this.getSubtitle(viewModel);
|
||||
return viewModel;
|
||||
}
|
||||
@ -153,17 +152,13 @@ export class ItemRepositoryService extends BaseHasContentObjectRepository<
|
||||
* @param viewModel the view model that the update is based on
|
||||
*/
|
||||
public async update(update: Partial<Item>, viewModel: ViewItem): Promise<void> {
|
||||
const sendUpdate = new this.baseModelCtor();
|
||||
sendUpdate.patchValues(viewModel.getModel());
|
||||
sendUpdate.patchValues(update);
|
||||
|
||||
const sendUpdate = viewModel.getUpdatedModel(update);
|
||||
const clone = JSON.parse(JSON.stringify(sendUpdate));
|
||||
clone.item_number = clone._itemNumber;
|
||||
const restPath = `/rest/${sendUpdate.collectionString}/${sendUpdate.id}/`;
|
||||
return await this.httpService.put(restPath, clone);
|
||||
return await this.dataSend.updateModel(clone);
|
||||
}
|
||||
|
||||
public async addItemToAgenda(contentObject: IBaseViewModelWithAgendaItem<any>): Promise<Identifiable> {
|
||||
public async addItemToAgenda(contentObject: BaseViewModelWithAgendaItem<any>): Promise<Identifiable> {
|
||||
return await this.httpService.post('/rest/agenda/item/', {
|
||||
collection: contentObject.collectionString,
|
||||
id: contentObject.id
|
||||
|
@ -24,6 +24,7 @@ import { ViewTopic } from 'app/site/topics/models/view-topic';
|
||||
import { ViewUser } from 'app/site/users/models/view-user';
|
||||
import { BaseHasContentObjectRepository } from '../base-has-content-object-repository';
|
||||
import { BaseIsListOfSpeakersContentObjectRepository } from '../base-is-list-of-speakers-content-object-repository';
|
||||
import { NestedModelDescriptors } from '../base-repository';
|
||||
import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
|
||||
import { DataStoreService } from '../../core-services/data-store.service';
|
||||
import { ItemRepositoryService } from './item-repository.service';
|
||||
@ -36,23 +37,30 @@ const ListOfSpeakersRelations: RelationDefinition[] = [
|
||||
VForeignVerbose: 'BaseViewModelWithListOfSpeakers',
|
||||
ownContentObjectDataKey: 'contentObjectData',
|
||||
ownKey: 'contentObject'
|
||||
},
|
||||
}
|
||||
];
|
||||
|
||||
const ListOfSpeakersNestedModelDescriptors: NestedModelDescriptors = {
|
||||
'agenda/list-of-speakers': [
|
||||
{
|
||||
type: 'nested',
|
||||
ownKey: 'speakers',
|
||||
foreignViewModel: ViewSpeaker,
|
||||
foreignModel: Speaker,
|
||||
order: 'weight',
|
||||
relationDefinition: [
|
||||
{
|
||||
relationDefinitionsByKey: {
|
||||
user: {
|
||||
type: 'M2O',
|
||||
ownIdKey: 'user_id',
|
||||
ownKey: 'user',
|
||||
foreignViewModel: ViewUser
|
||||
}
|
||||
]
|
||||
},
|
||||
titles: {
|
||||
getTitle: (viewSpeaker: ViewSpeaker) => viewSpeaker.name
|
||||
}
|
||||
];
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/**
|
||||
* Repository service for lists of speakers
|
||||
@ -96,7 +104,8 @@ export class ListOfSpeakersRepositoryService extends BaseHasContentObjectReposit
|
||||
translate,
|
||||
relationManager,
|
||||
ListOfSpeakers,
|
||||
ListOfSpeakersRelations
|
||||
ListOfSpeakersRelations,
|
||||
ListOfSpeakersNestedModelDescriptors
|
||||
);
|
||||
}
|
||||
|
||||
@ -116,7 +125,7 @@ export class ListOfSpeakersRepositoryService extends BaseHasContentObjectReposit
|
||||
// TODO: This can be resolved with #4738
|
||||
const item = this.itemRepo.findByContentObject(titleInformation.contentObjectData);
|
||||
if (item) {
|
||||
(<any>titleInformation.title_information).agenda_item_number = item.itemNumber;
|
||||
(<any>titleInformation.title_information).agenda_item_number = item.item_number;
|
||||
}
|
||||
|
||||
return repo.getListOfSpeakersTitle(titleInformation.title_information);
|
||||
|
@ -19,6 +19,7 @@ import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
|
||||
import { ViewTag } from 'app/site/tags/models/view-tag';
|
||||
import { ViewUser } from 'app/site/users/models/view-user';
|
||||
import { BaseIsAgendaItemAndListOfSpeakersContentObjectRepository } from '../base-is-agenda-item-and-list-of-speakers-content-object-repository';
|
||||
import { NestedModelDescriptors } from '../base-repository';
|
||||
import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
|
||||
import { DataStoreService } from '../../core-services/data-store.service';
|
||||
|
||||
@ -34,46 +35,53 @@ const AssignmentRelations: RelationDefinition[] = [
|
||||
ownIdKey: 'attachments_id',
|
||||
ownKey: 'attachments',
|
||||
foreignViewModel: ViewMediafile
|
||||
},
|
||||
}
|
||||
];
|
||||
|
||||
const AssignmentNestedModelDescriptors: NestedModelDescriptors = {
|
||||
'assignments/assignment': [
|
||||
{
|
||||
type: 'nested',
|
||||
ownKey: 'assignment_related_users',
|
||||
foreignViewModel: ViewAssignmentRelatedUser,
|
||||
foreignModel: AssignmentRelatedUser,
|
||||
order: 'weight',
|
||||
relationDefinition: [
|
||||
{
|
||||
relationDefinitionsByKey: {
|
||||
user: {
|
||||
type: 'M2O',
|
||||
ownIdKey: 'user_id',
|
||||
ownKey: 'user',
|
||||
foreignViewModel: ViewUser
|
||||
}
|
||||
]
|
||||
},
|
||||
titles: {
|
||||
getTitle: (viewAssignmentRelatedUser: ViewAssignmentRelatedUser) =>
|
||||
viewAssignmentRelatedUser.user ? viewAssignmentRelatedUser.user.getFullName() : ''
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'nested',
|
||||
ownKey: 'polls',
|
||||
foreignViewModel: ViewAssignmentPoll,
|
||||
foreignModel: AssignmentPoll,
|
||||
relationDefinition: [
|
||||
relationDefinitionsByKey: {}
|
||||
}
|
||||
],
|
||||
'assignments/assignment-poll': [
|
||||
{
|
||||
type: 'nested',
|
||||
ownKey: 'options',
|
||||
foreignViewModel: ViewAssignmentPollOption,
|
||||
foreignModel: AssignmentPollOption,
|
||||
order: 'weight',
|
||||
relationDefinition: [
|
||||
{
|
||||
relationDefinitionsByKey: {
|
||||
user: {
|
||||
type: 'M2O',
|
||||
ownIdKey: 'user_id',
|
||||
ownIdKey: 'candidate_id',
|
||||
ownKey: 'user',
|
||||
foreignViewModel: ViewUser
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Repository Service for Assignments.
|
||||
@ -122,7 +130,8 @@ export class AssignmentRepositoryService extends BaseIsAgendaItemAndListOfSpeake
|
||||
translate,
|
||||
relationManager,
|
||||
Assignment,
|
||||
AssignmentRelations
|
||||
AssignmentRelations,
|
||||
AssignmentNestedModelDescriptors
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -40,9 +40,9 @@ export abstract class BaseHasContentObjectRepository<
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public changedModels(ids: number[], initialLoading: boolean): void {
|
||||
public changedModels(ids: number[]): void {
|
||||
ids.forEach(id => {
|
||||
const v = this.createViewModelWithTitles(this.DS.get(this.collectionString, id), initialLoading);
|
||||
const v = this.createViewModelWithTitles(this.DS.get(this.collectionString, id));
|
||||
this.viewModelStore[id] = v;
|
||||
|
||||
const contentObject = v.contentObjectData;
|
||||
|
@ -3,10 +3,10 @@ import { ViewItem } from 'app/site/agenda/models/view-item';
|
||||
import { ViewListOfSpeakers } from 'app/site/agenda/models/view-list-of-speakers';
|
||||
import { BaseProjectableViewModel } from 'app/site/base/base-projectable-view-model';
|
||||
import {
|
||||
IBaseViewModelWithAgendaItem,
|
||||
BaseViewModelWithAgendaItem,
|
||||
TitleInformationWithAgendaItem
|
||||
} from 'app/site/base/base-view-model-with-agenda-item';
|
||||
import { IBaseViewModelWithListOfSpeakers } from 'app/site/base/base-view-model-with-list-of-speakers';
|
||||
import { BaseViewModelWithListOfSpeakers } from 'app/site/base/base-view-model-with-list-of-speakers';
|
||||
import {
|
||||
IBaseIsAgendaItemContentObjectRepository,
|
||||
isBaseIsAgendaItemContentObjectRepository
|
||||
@ -30,14 +30,14 @@ export function isBaseIsAgendaItemAndListOfSpeakersContentObjectRepository(
|
||||
* multi-inheritance by implementing both inherit classes again...
|
||||
*/
|
||||
export abstract class BaseIsAgendaItemAndListOfSpeakersContentObjectRepository<
|
||||
V extends BaseProjectableViewModel & IBaseViewModelWithAgendaItem & IBaseViewModelWithListOfSpeakers & T,
|
||||
V extends BaseProjectableViewModel & BaseViewModelWithAgendaItem & BaseViewModelWithListOfSpeakers & T,
|
||||
M extends BaseModel,
|
||||
T extends TitleInformationWithAgendaItem
|
||||
> extends BaseRepository<V, M, T>
|
||||
implements
|
||||
IBaseIsAgendaItemContentObjectRepository<V, M, T>,
|
||||
IBaseIsListOfSpeakersContentObjectRepository<V, M, T> {
|
||||
protected groupRelationsByCollections(): void {
|
||||
protected extendRelations(): void {
|
||||
this.relationDefinitions.push({
|
||||
type: 'M2O',
|
||||
ownIdKey: 'agenda_item_id',
|
||||
@ -47,10 +47,9 @@ export abstract class BaseIsAgendaItemAndListOfSpeakersContentObjectRepository<
|
||||
this.relationDefinitions.push({
|
||||
type: 'M2O',
|
||||
ownIdKey: 'list_of_speakers_id',
|
||||
ownKey: 'list_of_speakers',
|
||||
ownKey: 'listOfSpeakers',
|
||||
foreignViewModel: ViewListOfSpeakers
|
||||
});
|
||||
super.groupRelationsByCollections();
|
||||
}
|
||||
|
||||
public getAgendaListTitle(titleInformation: T): string {
|
||||
@ -87,8 +86,8 @@ export abstract class BaseIsAgendaItemAndListOfSpeakersContentObjectRepository<
|
||||
return this.getAgendaSlideTitle(titleInformation);
|
||||
};
|
||||
|
||||
protected createViewModelWithTitles(model: M, initialLoading: boolean): V {
|
||||
const viewModel = super.createViewModelWithTitles(model, initialLoading);
|
||||
protected createViewModelWithTitles(model: M): V {
|
||||
const viewModel = super.createViewModelWithTitles(model);
|
||||
viewModel.getAgendaListTitle = () => this.getAgendaListTitle(viewModel);
|
||||
viewModel.getAgendaListTitleWithoutItemNumber = () => this.getAgendaListTitleWithoutItemNumber(viewModel);
|
||||
viewModel.getAgendaSlideTitle = () => this.getAgendaSlideTitle(viewModel);
|
||||
|
@ -64,14 +64,13 @@ export abstract class BaseIsAgendaItemContentObjectRepository<
|
||||
);
|
||||
}
|
||||
|
||||
protected groupRelationsByCollections(): void {
|
||||
protected extendRelations(): void {
|
||||
this.relationDefinitions.push({
|
||||
type: 'M2O',
|
||||
ownIdKey: 'agenda_item_id',
|
||||
ownKey: 'item',
|
||||
foreignViewModel: ViewItem
|
||||
});
|
||||
super.groupRelationsByCollections();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -115,8 +114,8 @@ export abstract class BaseIsAgendaItemContentObjectRepository<
|
||||
/**
|
||||
* Adds the agenda titles to the viewmodel.
|
||||
*/
|
||||
protected createViewModelWithTitles(model: M, initialLoading: boolean): V {
|
||||
const viewModel = super.createViewModelWithTitles(model, initialLoading);
|
||||
protected createViewModelWithTitles(model: M): V {
|
||||
const viewModel = super.createViewModelWithTitles(model);
|
||||
viewModel.getAgendaListTitle = () => this.getAgendaListTitle(viewModel);
|
||||
viewModel.getAgendaListTitleWithoutItemNumber = () => this.getAgendaListTitleWithoutItemNumber(viewModel);
|
||||
viewModel.getAgendaSlideTitle = () => this.getAgendaSlideTitle(viewModel);
|
||||
|
@ -61,14 +61,13 @@ export abstract class BaseIsListOfSpeakersContentObjectRepository<
|
||||
);
|
||||
}
|
||||
|
||||
protected groupRelationsByCollections(): void {
|
||||
protected extendRelations(): void {
|
||||
this.relationDefinitions.push({
|
||||
type: 'M2O',
|
||||
ownIdKey: 'list_of_speakers_id',
|
||||
ownKey: 'list_of_speakers',
|
||||
foreignViewModel: ViewListOfSpeakers
|
||||
});
|
||||
super.groupRelationsByCollections();
|
||||
}
|
||||
|
||||
public getListOfSpeakersTitle(titleInformation: T): string {
|
||||
@ -82,8 +81,8 @@ export abstract class BaseIsListOfSpeakersContentObjectRepository<
|
||||
/**
|
||||
* Adds the list of speakers titles to the view model
|
||||
*/
|
||||
protected createViewModelWithTitles(model: M, initialLoading: boolean): V {
|
||||
const viewModel = super.createViewModelWithTitles(model, initialLoading);
|
||||
protected createViewModelWithTitles(model: M): V {
|
||||
const viewModel = super.createViewModelWithTitles(model);
|
||||
viewModel.getListOfSpeakersTitle = () => this.getListOfSpeakersTitle(viewModel);
|
||||
viewModel.getListOfSpeakersSlideTitle = () => this.getListOfSpeakersSlideTitle(viewModel);
|
||||
return viewModel;
|
||||
|
@ -7,18 +7,28 @@ import { BaseModel, ModelConstructor } from '../../shared/models/base/base-model
|
||||
import { BaseViewModel, TitleInformation, ViewModelConstructor } from '../../site/base/base-view-model';
|
||||
import { CollectionStringMapperService } from '../core-services/collection-string-mapper.service';
|
||||
import { DataSendService } from '../core-services/data-send.service';
|
||||
import { CollectionIds, DataStoreService } from '../core-services/data-store.service';
|
||||
import { DataStoreService } from '../core-services/data-store.service';
|
||||
import { Identifiable } from '../../shared/models/base/identifiable';
|
||||
import { OnAfterAppsLoaded } from '../definitions/on-after-apps-loaded';
|
||||
import { RelationManagerService } from '../core-services/relation-manager.service';
|
||||
import {
|
||||
isNormalRelationDefinition,
|
||||
isReverseRelationDefinition,
|
||||
RelationDefinition,
|
||||
ReverseRelationDefinition
|
||||
} from '../definitions/relations';
|
||||
import { RelationDefinition, ReverseRelationDefinition } from '../definitions/relations';
|
||||
import { ViewModelStoreService } from '../core-services/view-model-store.service';
|
||||
|
||||
export interface ModelDescriptor<M extends BaseModel, V extends BaseViewModel> {
|
||||
relationDefinitionsByKey: { [key: string]: RelationDefinition };
|
||||
ownKey: string;
|
||||
foreignViewModel: ViewModelConstructor<V>;
|
||||
foreignModel: ModelConstructor<M>;
|
||||
order?: string;
|
||||
titles?: {
|
||||
[key: string]: (viewModel: V) => string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface NestedModelDescriptors {
|
||||
[collection: string]: ModelDescriptor<BaseModel, BaseViewModel>[];
|
||||
}
|
||||
|
||||
export abstract class BaseRepository<V extends BaseViewModel & T, M extends BaseModel, T extends TitleInformation>
|
||||
implements OnAfterAppsLoaded, Collection {
|
||||
/**
|
||||
@ -89,6 +99,8 @@ export abstract class BaseRepository<V extends BaseViewModel & T, M extends Base
|
||||
|
||||
protected reverseRelationsByCollection: { [collection: string]: ReverseRelationDefinition<BaseViewModel>[] } = {};
|
||||
|
||||
protected relationsByKey: { [key: string]: RelationDefinition<BaseViewModel> } = {};
|
||||
|
||||
/**
|
||||
* The view model ctor of the encapsulated view model.
|
||||
*/
|
||||
@ -111,12 +123,16 @@ export abstract class BaseRepository<V extends BaseViewModel & T, M extends Base
|
||||
protected translate: TranslateService,
|
||||
protected relationManager: RelationManagerService,
|
||||
protected baseModelCtor: ModelConstructor<M>,
|
||||
protected relationDefinitions: RelationDefinition<BaseViewModel>[] = []
|
||||
protected relationDefinitions: RelationDefinition<BaseViewModel>[] = [],
|
||||
protected nestedModelDescriptors: NestedModelDescriptors = {}
|
||||
) {
|
||||
this._collectionString = baseModelCtor.COLLECTIONSTRING;
|
||||
|
||||
this.groupRelationsByCollections();
|
||||
this.buildReverseRelationsGrouping();
|
||||
this.extendRelations();
|
||||
|
||||
this.relationDefinitions.forEach(relation => {
|
||||
this.relationsByKey[relation.ownKey] = relation;
|
||||
});
|
||||
|
||||
// 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
|
||||
@ -130,55 +146,7 @@ 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 === 'M2O' ||
|
||||
relation.type === 'M2M' ||
|
||||
relation.type === 'O2M' ||
|
||||
relation.type === 'custom'
|
||||
) {
|
||||
const collection = relation.foreignViewModel.COLLECTIONSTRING;
|
||||
if (!this.relationsByCollection[collection]) {
|
||||
this.relationsByCollection[collection] = [];
|
||||
}
|
||||
this.relationsByCollection[collection].push(baseRelation);
|
||||
} else if (relation.type === 'generic') {
|
||||
relation.possibleModels.forEach(ctor => {
|
||||
const collection = ctor.COLLECTIONSTRING;
|
||||
if (!this.relationsByCollection[collection]) {
|
||||
this.relationsByCollection[collection] = [];
|
||||
}
|
||||
this.relationsByCollection[collection].push(baseRelation);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected buildReverseRelationsGrouping(): void {
|
||||
Object.keys(this.relationsByCollection).forEach(collection => {
|
||||
const reverseRelations = this.relationsByCollection[collection].filter(relation =>
|
||||
isReverseRelationDefinition(relation)
|
||||
) as ReverseRelationDefinition<BaseViewModel>[];
|
||||
if (reverseRelations.length) {
|
||||
this.reverseRelationsByCollection[collection] = reverseRelations;
|
||||
}
|
||||
});
|
||||
}
|
||||
protected extendRelations(): void {}
|
||||
|
||||
public onAfterAppsLoaded(): void {
|
||||
this.baseViewModelCtor = this.collectionStringMapperService.getViewModelConstructor(this.collectionString);
|
||||
@ -214,12 +182,9 @@ export abstract class BaseRepository<V extends BaseViewModel & T, M extends Base
|
||||
*
|
||||
* @param ids All model ids.
|
||||
*/
|
||||
public changedModels(ids: number[], initialLoading: boolean): void {
|
||||
public changedModels(ids: number[]): void {
|
||||
ids.forEach(id => {
|
||||
this.viewModelStore[id] = this.createViewModelWithTitles(
|
||||
this.DS.get(this.collectionString, id),
|
||||
initialLoading
|
||||
);
|
||||
this.viewModelStore[id] = this.createViewModelWithTitles(this.DS.get(this.collectionString, id));
|
||||
this.updateViewModelObservable(id);
|
||||
});
|
||||
}
|
||||
@ -228,115 +193,20 @@ 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, initialLoading: boolean): V {
|
||||
protected createViewModelWithTitles(model: M): V {
|
||||
const viewModel = this.relationManager.createViewModel(
|
||||
model,
|
||||
this.baseViewModelCtor,
|
||||
this.relationDefinitions,
|
||||
initialLoading
|
||||
this.relationsByKey,
|
||||
this.nestedModelDescriptors
|
||||
);
|
||||
|
||||
viewModel.getTitle = () => this.getTitle(viewModel);
|
||||
viewModel.getListTitle = () => this.getListTitle(viewModel);
|
||||
viewModel.getVerboseName = this.getVerboseName;
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all models in this repository with all changed models.
|
||||
*
|
||||
* @param changedModels A mapping of collections to ids of all changed models.
|
||||
* @returns if at least one model was affected.
|
||||
*/
|
||||
public updateDependenciesForChangedModels(changedModels: CollectionIds): boolean {
|
||||
if (!this.relationDefinitions.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get all viewModels from this repo once.
|
||||
const ownViewModels = this.getViewModelList();
|
||||
const updatedIds = [];
|
||||
Object.keys(changedModels).forEach(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.
|
||||
const relations = this.relationsByCollection[collection];
|
||||
ownViewModels.forEach(ownViewModel => {
|
||||
relations.forEach(relation => {
|
||||
changedModels[collection].forEach(id => {
|
||||
if (
|
||||
this.relationManager.updateSingleDependencyForChangedModel(
|
||||
ownViewModel,
|
||||
relation,
|
||||
collection,
|
||||
id
|
||||
)
|
||||
) {
|
||||
updatedIds.push(ownViewModel.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
// Order all relations, if neeed.
|
||||
if (updatedIds.length) {
|
||||
relations.forEach(relation => {
|
||||
if (
|
||||
(isNormalRelationDefinition(relation) || isReverseRelationDefinition(relation)) &&
|
||||
(relation.type === 'M2M' || relation.type === 'O2M') &&
|
||||
relation.order
|
||||
) {
|
||||
ownViewModels.forEach(ownViewModel => {
|
||||
if (ownViewModel['_' + relation.ownKey]) {
|
||||
this.relationManager.sortByRelation(relation, ownViewModel);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Inform about changes. (List updates is done in `commitUpdate` via `DataStoreUpdateManagerService`)
|
||||
updatedIds.forEach(id => {
|
||||
this.updateViewModelObservable(id);
|
||||
});
|
||||
});
|
||||
return !!updatedIds.length;
|
||||
}
|
||||
|
||||
public updateDependenciesForDeletedModels(deletedModels: CollectionIds): boolean {
|
||||
if (!Object.keys(this.reverseRelationsByCollection).length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get all viewModels from this repo once.
|
||||
const ownViewModels = this.getViewModelList();
|
||||
let somethingChanged = false;
|
||||
Object.keys(deletedModels).forEach(collection => {
|
||||
const dependencyChanged: boolean = Object.keys(this.reverseRelationsByCollection).includes(collection);
|
||||
if (!dependencyChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ok, we are affected by this collection. Update all viewModels from this repo.
|
||||
const relations = this.reverseRelationsByCollection[collection];
|
||||
ownViewModels.forEach(ownViewModel => {
|
||||
relations.forEach(relation => {
|
||||
deletedModels[collection].forEach(id => {
|
||||
if (this.relationManager.updateSingleDependencyForDeletedModel(ownViewModel, relation, id)) {
|
||||
// Inform about changes. (List updates is done in `commitUpdate` via `DataStoreUpdateManagerService`)
|
||||
this.updateViewModelObservable(id);
|
||||
somethingChanged = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
// Ordering all relations is not needed, because just deleting things out of arrays
|
||||
// will not unorder them.
|
||||
});
|
||||
return somethingChanged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the (full) update to an existing model. So called "update"-function
|
||||
* Provides a default procedure, but can be overwritten if required
|
||||
@ -345,10 +215,8 @@ export abstract class BaseRepository<V extends BaseViewModel & T, M extends Base
|
||||
* @param viewModel the view model that the update is based on
|
||||
*/
|
||||
public async update(update: Partial<M>, viewModel: V): Promise<void> {
|
||||
const sendUpdate = new this.baseModelCtor();
|
||||
sendUpdate.patchValues(viewModel.getModel());
|
||||
sendUpdate.patchValues(update);
|
||||
return await this.dataSend.updateModel(sendUpdate);
|
||||
const data = viewModel.getUpdatedModel(update);
|
||||
return await this.dataSend.updateModel(data);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -359,9 +227,8 @@ export abstract class BaseRepository<V extends BaseViewModel & T, M extends Base
|
||||
* @param viewModel the motion to update
|
||||
*/
|
||||
public async patch(update: Partial<M>, viewModel: V): Promise<void> {
|
||||
const patch = new this.baseModelCtor();
|
||||
const patch = new this.baseModelCtor(update);
|
||||
patch.id = viewModel.id;
|
||||
patch.patchValues(update);
|
||||
return await this.dataSend.partialUpdateModel(patch);
|
||||
}
|
||||
|
||||
@ -384,8 +251,7 @@ export abstract class BaseRepository<V extends BaseViewModel & T, M extends Base
|
||||
public async create(model: M): Promise<Identifiable> {
|
||||
// this ensures we get a valid base model, even if the view was just
|
||||
// sending an object with "as MyModelClass"
|
||||
const sendModel = new this.baseModelCtor();
|
||||
sendModel.patchValues(model);
|
||||
const sendModel = new this.baseModelCtor(model);
|
||||
|
||||
// Strips empty fields from the sending mode data (except false)
|
||||
// required for i.e. users, since group list is mandatory
|
||||
|
@ -154,8 +154,8 @@ export class ConfigRepositoryService extends BaseRepository<ViewConfig, Config,
|
||||
throw new Error('Config variables cannot be created');
|
||||
}
|
||||
|
||||
public changedModels(ids: number[], initialLoading: boolean): void {
|
||||
super.changedModels(ids, initialLoading);
|
||||
public changedModels(ids: number[]): void {
|
||||
super.changedModels(ids);
|
||||
|
||||
ids.forEach(id => {
|
||||
this.updateConfigStructure(false, this.viewModelStore[id]);
|
||||
@ -246,10 +246,7 @@ export class ConfigRepositoryService extends BaseRepository<ViewConfig, Config,
|
||||
* Saves a config value.
|
||||
*/
|
||||
public async update(config: Partial<Config>, viewConfig: ViewConfig): Promise<void> {
|
||||
const updatedConfig = new Config();
|
||||
updatedConfig.patchValues(viewConfig.config);
|
||||
updatedConfig.patchValues(config);
|
||||
// TODO: Use datasendService, if it can switch correctly between put, post and patch
|
||||
const updatedConfig = viewConfig.getUpdatedModel(config);
|
||||
await this.http.put(`/rest/${updatedConfig.collectionString}/${updatedConfig.key}/`, updatedConfig);
|
||||
}
|
||||
|
||||
|
@ -112,41 +112,29 @@ export class ChangeRecommendationRepositoryService extends BaseRepository<
|
||||
/**
|
||||
* Sets a change recommendation to accepted.
|
||||
*
|
||||
* @param {ViewMotionChangeRecommendation} change
|
||||
* @param {ViewMotionChangeRecommendation} changeRecommendation
|
||||
*/
|
||||
public async setAccepted(change: ViewMotionChangeRecommendation): Promise<void> {
|
||||
const changeReco = change.changeRecommendation;
|
||||
changeReco.patchValues({
|
||||
rejected: false
|
||||
});
|
||||
await this.dataSend.partialUpdateModel(changeReco);
|
||||
public async setAccepted(changeRecommendation: ViewMotionChangeRecommendation): Promise<void> {
|
||||
await this.patch({ rejected: false }, changeRecommendation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a change recommendation to rejected.
|
||||
*
|
||||
* @param {ViewMotionChangeRecommendation} change
|
||||
* @param {ViewMotionChangeRecommendation} changeRecommendation
|
||||
*/
|
||||
public async setRejected(change: ViewMotionChangeRecommendation): Promise<void> {
|
||||
const changeReco = change.changeRecommendation;
|
||||
changeReco.patchValues({
|
||||
rejected: true
|
||||
});
|
||||
await this.dataSend.partialUpdateModel(changeReco);
|
||||
public async setRejected(changeRecommendation: ViewMotionChangeRecommendation): Promise<void> {
|
||||
await this.patch({ rejected: true }, changeRecommendation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if a change recommendation is internal (for the administrators) or not.
|
||||
*
|
||||
* @param {ViewMotionChangeRecommendation} change
|
||||
* @param {ViewMotionChangeRecommendation} changeRecommendation
|
||||
* @param {boolean} internal
|
||||
*/
|
||||
public async setInternal(change: ViewMotionChangeRecommendation, internal: boolean): Promise<void> {
|
||||
const changeReco = change.changeRecommendation;
|
||||
changeReco.patchValues({
|
||||
internal: internal
|
||||
});
|
||||
await this.dataSend.partialUpdateModel(changeReco);
|
||||
public async setInternal(changeRecommendation: ViewMotionChangeRecommendation, internal: boolean): Promise<void> {
|
||||
await this.patch({ internal: internal }, changeRecommendation);
|
||||
}
|
||||
|
||||
public getTitleWithChanges = (originalTitle: string, change: ViewUnifiedChange, crMode: ChangeRecoMode): string => {
|
||||
|
@ -32,6 +32,7 @@ import { ViewTag } from 'app/site/tags/models/view-tag';
|
||||
import { ViewPersonalNote } from 'app/site/users/models/view-personal-note';
|
||||
import { ViewUser } from 'app/site/users/models/view-user';
|
||||
import { BaseIsAgendaItemAndListOfSpeakersContentObjectRepository } from '../base-is-agenda-item-and-list-of-speakers-content-object-repository';
|
||||
import { NestedModelDescriptors } from '../base-repository';
|
||||
import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
|
||||
import { DataSendService } from '../../core-services/data-send.service';
|
||||
import { LinenumberingService, LineNumberRange } from '../../ui-services/linenumbering.service';
|
||||
@ -94,21 +95,6 @@ const MotionRelations: RelationDefinition[] = [
|
||||
ownKey: 'motion_block',
|
||||
foreignViewModel: ViewMotionBlock
|
||||
},
|
||||
{
|
||||
type: 'nested',
|
||||
ownKey: 'submitters',
|
||||
foreignViewModel: ViewSubmitter,
|
||||
foreignModel: Submitter,
|
||||
order: 'weight',
|
||||
relationDefinition: [
|
||||
{
|
||||
type: 'M2O',
|
||||
ownIdKey: 'user_id',
|
||||
ownKey: 'user',
|
||||
foreignViewModel: ViewUser
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'M2M',
|
||||
ownIdKey: 'supporters_id',
|
||||
@ -138,10 +124,39 @@ const MotionRelations: RelationDefinition[] = [
|
||||
foreignIdKey: 'parent_id',
|
||||
ownKey: 'amendments',
|
||||
foreignViewModel: ViewMotion
|
||||
},
|
||||
// TMP:
|
||||
{
|
||||
type: 'M2O',
|
||||
ownIdKey: 'parent_id',
|
||||
ownKey: 'parent',
|
||||
foreignViewModel: ViewMotion
|
||||
}
|
||||
// Personal notes are dynamically added in the repo.
|
||||
];
|
||||
|
||||
const MotionNestedModelDescriptors: NestedModelDescriptors = {
|
||||
'motions/motion': [
|
||||
{
|
||||
ownKey: 'submitters',
|
||||
foreignViewModel: ViewSubmitter,
|
||||
foreignModel: Submitter,
|
||||
order: 'weight',
|
||||
relationDefinitionsByKey: {
|
||||
user: {
|
||||
type: 'M2O',
|
||||
ownIdKey: 'user_id',
|
||||
ownKey: 'user',
|
||||
foreignViewModel: ViewUser
|
||||
}
|
||||
},
|
||||
titles: {
|
||||
getTitle: (viewSubmitter: ViewSubmitter) => (viewSubmitter.user ? viewSubmitter.user.getTitle() : '')
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/**
|
||||
* Repository Services for motions (and potentially categories)
|
||||
*
|
||||
@ -198,7 +213,17 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
||||
private readonly diff: DiffService,
|
||||
private operator: OperatorService
|
||||
) {
|
||||
super(DS, dataSend, mapperService, viewModelStoreService, translate, relationManager, Motion, MotionRelations);
|
||||
super(
|
||||
DS,
|
||||
dataSend,
|
||||
mapperService,
|
||||
viewModelStoreService,
|
||||
translate,
|
||||
relationManager,
|
||||
Motion,
|
||||
MotionRelations,
|
||||
MotionNestedModelDescriptors
|
||||
);
|
||||
config.get<SortProperty>('motions_motions_sorting').subscribe(conf => {
|
||||
this.sortProperty = conf;
|
||||
this.setConfigSortFn();
|
||||
@ -209,54 +234,6 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the personal note custom relation to the relation definitions.
|
||||
* Also adds the parent relation here to get access to methods in this repo.
|
||||
*/
|
||||
protected groupRelationsByCollections(): void {
|
||||
this.relationDefinitions.push({
|
||||
type: 'custom',
|
||||
foreignViewModel: 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;
|
||||
}
|
||||
});
|
||||
this.relationDefinitions.push({
|
||||
type: 'M2O',
|
||||
ownIdKey: 'parent_id',
|
||||
ownKey: 'parent',
|
||||
foreignViewModel: ViewMotion,
|
||||
afterSetRelation: (motion: ViewMotion, foreignViewModel: ViewMotion | null) => {
|
||||
if (foreignViewModel) {
|
||||
try {
|
||||
motion.diffLines = this.getAmendmentParagraphs(motion, this.motionLineLength, false);
|
||||
} catch (e) {
|
||||
console.warn('Error with motion or amendment ', motion);
|
||||
}
|
||||
}
|
||||
},
|
||||
afterDependencyChange: (motion: ViewMotion, parent: ViewMotion) => {
|
||||
if (motion.parent) {
|
||||
try {
|
||||
motion.diffLines = this.getAmendmentParagraphs(motion, this.motionLineLength, false);
|
||||
} catch (e) {
|
||||
console.warn('Error with motion or amendment: ', motion);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
super.groupRelationsByCollections();
|
||||
}
|
||||
|
||||
public getTitle = (titleInformation: MotionTitleInformation) => {
|
||||
if (titleInformation.identifier) {
|
||||
return titleInformation.identifier + ': ' + titleInformation.title;
|
||||
@ -321,8 +298,8 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
||||
return this.translate.instant(plural ? 'Motions' : 'Motion');
|
||||
};
|
||||
|
||||
protected createViewModelWithTitles(model: Motion, initialLoading: boolean): ViewMotion {
|
||||
const viewModel = super.createViewModelWithTitles(model, initialLoading);
|
||||
protected createViewModelWithTitles(model: Motion): ViewMotion {
|
||||
const viewModel = super.createViewModelWithTitles(model);
|
||||
|
||||
viewModel.getIdentifierOrTitle = () => this.getIdentifierOrTitle(viewModel);
|
||||
viewModel.getProjectorTitle = () => this.getAgendaSlideTitle(viewModel);
|
||||
@ -330,6 +307,37 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
protected extendRelations(): void {
|
||||
this.relationDefinitions.push({
|
||||
type: 'custom',
|
||||
ownKey: 'personalNote',
|
||||
get: (motion: Motion, viewMotion: ViewMotion) => {
|
||||
return this.getPersonalNoteForMotion(motion);
|
||||
},
|
||||
getCacheObjectToCheck: (viewMotion: ViewMotion) => this.getPersonalNote()
|
||||
});
|
||||
this.relationDefinitions.push({
|
||||
type: 'custom',
|
||||
ownKey: 'diffLines',
|
||||
get: (motion: Motion, viewMotion: ViewMotion) => {
|
||||
if (viewMotion.parent) {
|
||||
return this.getAmendmentParagraphs(viewMotion, this.motionLineLength, false);
|
||||
}
|
||||
},
|
||||
getCacheObjectToCheck: (viewMotion: ViewMotion) => viewMotion.parent
|
||||
});
|
||||
super.extendRelations();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the personal note for the operator.
|
||||
*/
|
||||
private getPersonalNote(): ViewPersonalNote | null {
|
||||
return this.viewModelStoreService.find(ViewPersonalNote, pn => {
|
||||
return pn.user_id === this.operator.user.id;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the personal note content for one motion by their id
|
||||
*
|
||||
@ -341,9 +349,7 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
||||
return;
|
||||
}
|
||||
|
||||
const personalNote = this.viewModelStoreService.find(ViewPersonalNote, pn => {
|
||||
return pn.userId === this.operator.user.id;
|
||||
});
|
||||
const personalNote = this.getPersonalNote();
|
||||
if (!personalNote) {
|
||||
return;
|
||||
}
|
||||
@ -735,7 +741,7 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
||||
const motion = amendment.parent;
|
||||
const baseParagraphs = this.getTextParagraphs(motion, true, lineLength);
|
||||
|
||||
return amendment.amendment_paragraphs
|
||||
return (amendment.amendment_paragraphs || [])
|
||||
.map(
|
||||
(newText: string, paraNo: number): DiffLinesInParagraph => {
|
||||
if (newText !== null) {
|
||||
@ -779,7 +785,7 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
|
||||
const motion = amendment.parent;
|
||||
const baseParagraphs = this.getTextParagraphs(motion, true, lineLength);
|
||||
|
||||
return amendment.amendment_paragraphs
|
||||
return (amendment.amendment_paragraphs || [])
|
||||
.map(
|
||||
(newText: string, paraNo: number): ViewMotionAmendedParagraph => {
|
||||
if (newText === null) {
|
||||
|
@ -25,6 +25,12 @@ const StateRelations: RelationDefinition[] = [
|
||||
ownIdKey: 'next_states_id',
|
||||
ownKey: 'next_states',
|
||||
foreignViewModel: ViewState
|
||||
},
|
||||
{
|
||||
type: 'M2M',
|
||||
foreignIdKey: 'next_states_id',
|
||||
ownKey: 'previous_states',
|
||||
foreignViewModel: ViewState
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -80,8 +80,7 @@ export class ProjectorRepositoryService extends BaseRepository<ViewProjector, Pr
|
||||
* Creates a new projector. Adds the clock as default, stable element
|
||||
*/
|
||||
public async create(projectorData: Partial<Projector>): Promise<Identifiable> {
|
||||
const projector = new Projector();
|
||||
projector.patchValues(projectorData);
|
||||
const projector = new Projector(projectorData);
|
||||
projector.elements = [{ name: 'core/clock', stable: true }];
|
||||
return await this.dataSend.createModel(projector);
|
||||
}
|
||||
|
@ -147,8 +147,8 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User, UserTi
|
||||
/**
|
||||
* Adds teh short and full name to the view user.
|
||||
*/
|
||||
protected createViewModelWithTitles(model: User, initialLoading: boolean): ViewUser {
|
||||
const viewModel = super.createViewModelWithTitles(model, initialLoading);
|
||||
protected createViewModelWithTitles(model: User): ViewUser {
|
||||
const viewModel = super.createViewModelWithTitles(model);
|
||||
viewModel.getFullName = () => this.getFullName(viewModel);
|
||||
viewModel.getShortName = () => this.getShortName(viewModel);
|
||||
return viewModel;
|
||||
@ -163,23 +163,19 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User, UserTi
|
||||
* @param viewUser
|
||||
*/
|
||||
public async update(update: Partial<User>, viewUser: ViewUser): Promise<void> {
|
||||
const updateUser = new User();
|
||||
updateUser.patchValues(viewUser.user);
|
||||
updateUser.patchValues(update);
|
||||
|
||||
// if the user deletes the username, reset
|
||||
// prevents the server of generating '<firstname> <lastname> +1' as username
|
||||
if (updateUser.username === '') {
|
||||
updateUser.username = viewUser.username;
|
||||
if (update.username === '') {
|
||||
update.username = viewUser.username;
|
||||
}
|
||||
|
||||
// if the update user does not have a gender-field, send gender as empty string.
|
||||
// This allow to delete a previously selected gender
|
||||
if (!updateUser.gender) {
|
||||
updateUser.gender = '';
|
||||
if (!update.gender) {
|
||||
update.gender = '';
|
||||
}
|
||||
|
||||
return await this.dataSend.updateModel(updateUser);
|
||||
return super.update(update, viewUser);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -309,7 +309,7 @@ export abstract class BaseFilterListService<V extends BaseViewModel> {
|
||||
};
|
||||
});
|
||||
|
||||
if (!!noneOptionLabel) {
|
||||
if (noneOptionLabel) {
|
||||
filterProperties.push('-');
|
||||
filterProperties.push({
|
||||
condition: null,
|
||||
|
@ -95,7 +95,7 @@ export abstract class BaseSortService<T extends Identifiable & Displayable> {
|
||||
case 'number':
|
||||
return firstProperty > secondProperty ? 1 : -1;
|
||||
case 'string':
|
||||
if (!!firstProperty && !secondProperty) {
|
||||
if (firstProperty && !secondProperty) {
|
||||
return -1;
|
||||
} else if (!firstProperty && !!secondProperty) {
|
||||
return 1;
|
||||
|
@ -156,7 +156,7 @@ export class ExtensionFieldComponent implements OnInit, OnDestroy {
|
||||
this.searchValueSubscription = this.extensionFieldForm
|
||||
.get('list')
|
||||
.valueChanges.subscribe((value: number) => {
|
||||
if (!!value) {
|
||||
if (value) {
|
||||
if (this.listSubmitOnChange) {
|
||||
this.listChange.emit(value);
|
||||
}
|
||||
|
@ -531,7 +531,7 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
|
||||
propertyAsString = '' + item[prop];
|
||||
}
|
||||
|
||||
if (!!propertyAsString) {
|
||||
if (propertyAsString) {
|
||||
const foundProp =
|
||||
propertyAsString
|
||||
.trim()
|
||||
@ -635,9 +635,11 @@ export class ListViewTableComponent<V extends BaseViewModel, M extends BaseModel
|
||||
* does not work, tying the tables to the same hight.
|
||||
*/
|
||||
public async scrollToPreviousPosition(): Promise<void> {
|
||||
if (this.ngrid) {
|
||||
const scrollIndex = await this.getScrollIndex(this.listStorageKey);
|
||||
this.ngrid.viewport.scrollToIndex(scrollIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function changes the height of the row for virtual-scrolling in the relating `.scss`-file.
|
||||
|
@ -47,7 +47,7 @@ export class RoundedInputComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
@Input()
|
||||
public set model(value: string) {
|
||||
if (!!value) {
|
||||
if (value) {
|
||||
this.modelForm.setValue(value);
|
||||
}
|
||||
}
|
||||
|
@ -633,7 +633,7 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
||||
break;
|
||||
|
||||
case Direction.RIGHT:
|
||||
if (!!possibleParent) {
|
||||
if (possibleParent) {
|
||||
const nextLevel = this.nextNode.level + direction.steps;
|
||||
if (nextLevel <= possibleParent.level) {
|
||||
this.placeholderLevel = nextLevel;
|
||||
@ -646,7 +646,7 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
||||
break;
|
||||
|
||||
case Direction.NOWAY:
|
||||
if (!!possibleParent) {
|
||||
if (!possibleParent) {
|
||||
if (this.nextNode.level <= possibleParent.level + 1) {
|
||||
this.placeholderLevel = this.nextNode.level;
|
||||
} else {
|
||||
@ -678,7 +678,7 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
||||
case Direction.UPWARDS:
|
||||
for (let i = 0; i < steps; ++i) {
|
||||
const parent = this.getExpandedParentNode(this.osTreeData[currentPosition - 1]);
|
||||
if (!!parent) {
|
||||
if (parent) {
|
||||
currentPosition = parent.position;
|
||||
} else {
|
||||
break;
|
||||
@ -693,7 +693,7 @@ export class SortingTreeComponent<T extends Identifiable & Displayable> implemen
|
||||
currentPosition -= 1;
|
||||
for (let i = 0; i < steps; ++i) {
|
||||
const parent = this.getExpandedParentNode(this.osTreeData[currentPosition]);
|
||||
if (!!parent) {
|
||||
if (parent) {
|
||||
currentPosition = parent.position - 1;
|
||||
} else {
|
||||
break;
|
||||
|
@ -35,7 +35,7 @@ export class SpeakerButtonComponent implements OnDestroy {
|
||||
|
||||
this.cleanLosSub();
|
||||
|
||||
if (!!listOfSpeakers) {
|
||||
if (listOfSpeakers) {
|
||||
this.losSub = this.listOfSpeakersRepo
|
||||
.getViewModelObservable(listOfSpeakers.id)
|
||||
.pipe(distinctUntilChanged())
|
||||
|
@ -2,6 +2,12 @@ import { BaseModelWithContentObject } from '../base/base-model-with-content-obje
|
||||
import { ContentObject } from '../base/content-object';
|
||||
import { Speaker } from './speaker';
|
||||
|
||||
export interface ListOfSpeakersWithoutNestedModels extends BaseModelWithContentObject<ListOfSpeakers> {
|
||||
id: number;
|
||||
title_information: object;
|
||||
closed: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Representations of agenda Item
|
||||
* @ignore
|
||||
@ -19,3 +25,5 @@ export class ListOfSpeakers extends BaseModelWithContentObject<ListOfSpeakers> {
|
||||
super(ListOfSpeakers.COLLECTIONSTRING, input);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ListOfSpeakers extends ListOfSpeakersWithoutNestedModels {}
|
||||
|
@ -17,10 +17,6 @@ export class AssignmentPollOption extends BaseModel<AssignmentPollOption> {
|
||||
|
||||
public id: number; // The AssignmentPollOption id
|
||||
public candidate_id: number; // the user id of the candidate
|
||||
public get user_id(): number {
|
||||
// to be consistent with user...
|
||||
return this.candidate_id;
|
||||
}
|
||||
public is_elected: boolean;
|
||||
public votes: AssignmentOptionVote[];
|
||||
public poll_id: number;
|
||||
|
@ -2,6 +2,20 @@ import { AssignmentPollMethod } from 'app/site/assignments/services/assignment-p
|
||||
import { AssignmentPollOption } from './assignment-poll-option';
|
||||
import { BaseModel } from '../base/base-model';
|
||||
|
||||
export interface AssignmentPollWithoutNestedModels extends BaseModel<AssignmentPoll> {
|
||||
id: number;
|
||||
pollmethod: AssignmentPollMethod;
|
||||
description: string;
|
||||
published: boolean;
|
||||
votesvalid: number;
|
||||
votesno: number;
|
||||
votesabstain: number;
|
||||
votesinvalid: number;
|
||||
votescast: number;
|
||||
has_votes: boolean;
|
||||
assignment_id: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Content of the 'polls' property of assignments
|
||||
* @ignore
|
||||
@ -11,21 +25,8 @@ export class AssignmentPoll extends BaseModel<AssignmentPoll> {
|
||||
private static DECIMAL_FIELDS = ['votesvalid', 'votesinvalid', 'votescast', 'votesno', 'votesabstain'];
|
||||
|
||||
public id: number;
|
||||
public pollmethod: AssignmentPollMethod;
|
||||
public description: string;
|
||||
public published: boolean;
|
||||
public options: AssignmentPollOption[];
|
||||
public votesvalid: number;
|
||||
public votesno: number;
|
||||
public votesabstain: number;
|
||||
public votesinvalid: number;
|
||||
public votescast: number;
|
||||
public has_votes: boolean;
|
||||
public assignment_id: number;
|
||||
|
||||
/**
|
||||
* @param input
|
||||
*/
|
||||
public constructor(input?: any) {
|
||||
// cast stringify numbers
|
||||
if (input) {
|
||||
@ -38,3 +39,4 @@ export class AssignmentPoll extends BaseModel<AssignmentPoll> {
|
||||
super(AssignmentPoll.COLLECTIONSTRING, input);
|
||||
}
|
||||
}
|
||||
export interface AssignmentPoll extends AssignmentPollWithoutNestedModels {}
|
||||
|
@ -2,6 +2,17 @@ import { AssignmentPoll } from './assignment-poll';
|
||||
import { AssignmentRelatedUser } from './assignment-related-user';
|
||||
import { BaseModelWithAgendaItemAndListOfSpeakers } from '../base/base-model-with-agenda-item-and-list-of-speakers';
|
||||
|
||||
export interface AssignmentWithoutNestedModels extends BaseModelWithAgendaItemAndListOfSpeakers<Assignment> {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
open_posts: number;
|
||||
phase: number; // see Openslides constants
|
||||
poll_description_default: number;
|
||||
tags_id: number[];
|
||||
attachments_id: number[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Representation of an assignment.
|
||||
* @ignore
|
||||
@ -10,15 +21,8 @@ export class Assignment extends BaseModelWithAgendaItemAndListOfSpeakers<Assignm
|
||||
public static COLLECTIONSTRING = 'assignments/assignment';
|
||||
|
||||
public id: number;
|
||||
public title: string;
|
||||
public description: string;
|
||||
public open_posts: number;
|
||||
public phase: number; // see Openslides constants
|
||||
public assignment_related_users: AssignmentRelatedUser[];
|
||||
public poll_description_default: number;
|
||||
public polls: AssignmentPoll[];
|
||||
public tags_id: number[];
|
||||
public attachments_id: number[];
|
||||
|
||||
public constructor(input?: any) {
|
||||
super(Assignment.COLLECTIONSTRING, input);
|
||||
@ -31,13 +35,5 @@ export class Assignment extends BaseModelWithAgendaItemAndListOfSpeakers<Assignm
|
||||
})
|
||||
.map((candidate: AssignmentRelatedUser) => candidate.user_id);
|
||||
}
|
||||
|
||||
public deserialize(input: any): void {
|
||||
Object.assign(this, input);
|
||||
|
||||
this.polls = [];
|
||||
if (input.polls instanceof Array) {
|
||||
this.polls = input.polls.map(pollData => new AssignmentPoll(pollData));
|
||||
}
|
||||
}
|
||||
}
|
||||
export interface Assignment extends AssignmentWithoutNestedModels {}
|
||||
|
@ -4,7 +4,7 @@ import { ContentObject } from './content-object';
|
||||
/**
|
||||
* A base model which has a content object, like items of list of speakers.
|
||||
*/
|
||||
export abstract class BaseModelWithContentObject<T = object> extends BaseModel<T> {
|
||||
export abstract class BaseModelWithContentObject<T = any> extends BaseModel<T> {
|
||||
public abstract content_object: ContentObject;
|
||||
|
||||
public get contentObjectData(): ContentObject {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Collection } from './collection';
|
||||
import { Deserializable } from './deserializable';
|
||||
import { Deserializer } from './deserializer';
|
||||
import { Identifiable } from './identifiable';
|
||||
|
||||
export interface ModelConstructor<T extends BaseModel<T>> {
|
||||
@ -11,69 +11,21 @@ export interface ModelConstructor<T extends BaseModel<T>> {
|
||||
* Abstract parent class to set rules and functions for all models.
|
||||
* When inherit from this class, give the subclass as the type. E.g. `class Motion extends BaseModel<Motion>`
|
||||
*/
|
||||
export abstract class BaseModel<T = object> implements Deserializable, Identifiable, Collection {
|
||||
/**
|
||||
* force children of BaseModel to have a collectionString.
|
||||
*
|
||||
* Has a getter but no setter.
|
||||
*/
|
||||
protected _collectionString: string;
|
||||
|
||||
/**
|
||||
* returns the collectionString.
|
||||
*
|
||||
* The server and the dataStore use it to identify the collection.
|
||||
*/
|
||||
public get collectionString(): string {
|
||||
return this._collectionString;
|
||||
export abstract class BaseModel<T = any> extends Deserializer implements Identifiable, Collection {
|
||||
public get elementId(): string {
|
||||
return `${this.collectionString}:${this.id}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* force children of BaseModel to have an id
|
||||
*/
|
||||
public abstract id: number;
|
||||
|
||||
/**
|
||||
* constructor that calls super from parent class
|
||||
*
|
||||
* @param collectionString
|
||||
* @param verboseName
|
||||
* @param input
|
||||
*/
|
||||
protected constructor(collectionString: string, input?: any) {
|
||||
this._collectionString = collectionString;
|
||||
|
||||
if (input) {
|
||||
this.changeNullValuesToUndef(input);
|
||||
this.deserialize(input);
|
||||
}
|
||||
protected constructor(public readonly collectionString: string, input?: Partial<T>) {
|
||||
super(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent to send literally "null" if should be send
|
||||
* @param input object to deserialize
|
||||
*/
|
||||
public changeNullValuesToUndef(input: any): void {
|
||||
Object.keys(input).forEach(key => {
|
||||
if (input[key] === null) {
|
||||
input[key] = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* update the values of the base model with new values
|
||||
*/
|
||||
public patchValues(update: Partial<T>): void {
|
||||
Object.assign(this, update);
|
||||
}
|
||||
|
||||
/**
|
||||
* Most simple and most commonly used deserialize function.
|
||||
* Inherited to children, can be overwritten for special use cases
|
||||
* @param input JSON data for deserialization.
|
||||
*/
|
||||
public deserialize(input: any): void {
|
||||
Object.assign(this, input);
|
||||
public getUpdatedVersion(update: Partial<T>): T {
|
||||
const copy: T = (<any>Object.assign({}, this)) as T;
|
||||
return Object.assign(copy, update);
|
||||
}
|
||||
}
|
||||
|
@ -11,28 +11,16 @@ export abstract class Deserializer implements Deserializable {
|
||||
*/
|
||||
protected constructor(input?: any) {
|
||||
if (input) {
|
||||
this.changeNullValuesToUndef(input);
|
||||
this.deserialize(input);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* should be used to assign JSON values to the object itself.
|
||||
* @param input
|
||||
* Most simple and most commonly used deserialize function.
|
||||
* Inherited to children, can be overwritten for special use cases
|
||||
* @param input JSON data for deserialization.
|
||||
*/
|
||||
public deserialize(input: any): void {
|
||||
Object.assign(this, input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent to send literally "null" if should be send
|
||||
* @param input object to deserialize
|
||||
*/
|
||||
public changeNullValuesToUndef(input: any): void {
|
||||
Object.keys(input).forEach(key => {
|
||||
if (input[key] === null) {
|
||||
input[key] = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,21 @@ interface FileMetadata {
|
||||
encrypted?: boolean;
|
||||
}
|
||||
|
||||
export interface MediafileWithoutNestedModels extends BaseModelWithListOfSpeakers<Mediafile> {
|
||||
id: number;
|
||||
title: string;
|
||||
media_url_prefix: string;
|
||||
filesize?: string;
|
||||
access_groups_id: number[];
|
||||
create_timestamp: string;
|
||||
parent_id: number | null;
|
||||
is_directory: boolean;
|
||||
path: string;
|
||||
inherited_access_groups_id: boolean | number[];
|
||||
|
||||
has_inherited_access_groups: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Representation of MediaFile. Has the nested property "File"
|
||||
* @ignore
|
||||
@ -16,25 +31,14 @@ interface FileMetadata {
|
||||
export class Mediafile extends BaseModelWithListOfSpeakers<Mediafile> {
|
||||
public static COLLECTIONSTRING = 'mediafiles/mediafile';
|
||||
public id: number;
|
||||
public title: string;
|
||||
public mediafile?: FileMetadata;
|
||||
public media_url_prefix: string;
|
||||
public filesize?: string;
|
||||
public access_groups_id: number[];
|
||||
public create_timestamp: string;
|
||||
public parent_id: number | null;
|
||||
public is_directory: boolean;
|
||||
public path: string;
|
||||
public inherited_access_groups_id: boolean | number[];
|
||||
|
||||
public get has_inherited_access_groups(): boolean {
|
||||
return typeof this.inherited_access_groups_id !== 'boolean';
|
||||
}
|
||||
|
||||
public constructor(input?: any) {
|
||||
super(Mediafile.COLLECTIONSTRING);
|
||||
// Do not change null to undefined...
|
||||
this.deserialize(input);
|
||||
super(Mediafile.COLLECTIONSTRING, input);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -46,3 +50,4 @@ export class Mediafile extends BaseModelWithListOfSpeakers<Mediafile> {
|
||||
return `${this.media_url_prefix}${this.path}`;
|
||||
}
|
||||
}
|
||||
export interface Mediafile extends MediafileWithoutNestedModels {}
|
||||
|
@ -9,6 +9,40 @@ export interface MotionComment {
|
||||
read_groups_id: number[];
|
||||
}
|
||||
|
||||
export interface MotionWithoutNestedModels extends BaseModelWithAgendaItemAndListOfSpeakers<Motion> {
|
||||
id: number;
|
||||
identifier: string;
|
||||
title: string;
|
||||
text: string;
|
||||
reason: string;
|
||||
amendment_paragraphs: string[] | null;
|
||||
modified_final_version: string;
|
||||
parent_id: number;
|
||||
category_id: number;
|
||||
category_weight: number;
|
||||
motion_block_id: number;
|
||||
origin: string;
|
||||
supporters_id: number[];
|
||||
comments: MotionComment[];
|
||||
workflow_id: number;
|
||||
state_id: number;
|
||||
state_extension: string;
|
||||
state_required_permission_to_see: string;
|
||||
statute_paragraph_id: number;
|
||||
recommendation_id: number;
|
||||
recommendation_extension: string;
|
||||
tags_id: number[];
|
||||
attachments_id: number[];
|
||||
polls: MotionPoll[];
|
||||
weight: number;
|
||||
sort_parent_id: number;
|
||||
created: string;
|
||||
last_modified: string;
|
||||
change_recommendations_id: number[];
|
||||
|
||||
sorted_submitter_ids: number[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Representation of Motion.
|
||||
*
|
||||
@ -20,35 +54,7 @@ export class Motion extends BaseModelWithAgendaItemAndListOfSpeakers<Motion> {
|
||||
public static COLLECTIONSTRING = 'motions/motion';
|
||||
|
||||
public id: number;
|
||||
public identifier: string;
|
||||
public title: string;
|
||||
public text: string;
|
||||
public reason: string;
|
||||
public amendment_paragraphs: string[];
|
||||
public modified_final_version: string;
|
||||
public parent_id: number;
|
||||
public category_id: number;
|
||||
public category_weight: number;
|
||||
public motion_block_id: number;
|
||||
public origin: string;
|
||||
public submitters: Submitter[];
|
||||
public supporters_id: number[];
|
||||
public comments: MotionComment[];
|
||||
public workflow_id: number;
|
||||
public state_id: number;
|
||||
public state_extension: string;
|
||||
public state_required_permission_to_see: string;
|
||||
public statute_paragraph_id: number;
|
||||
public recommendation_id: number;
|
||||
public recommendation_extension: string;
|
||||
public tags_id: number[];
|
||||
public attachments_id: number[];
|
||||
public polls: MotionPoll[];
|
||||
public weight: number;
|
||||
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);
|
||||
@ -57,7 +63,7 @@ export class Motion extends BaseModelWithAgendaItemAndListOfSpeakers<Motion> {
|
||||
/**
|
||||
* returns the motion submitters user ids
|
||||
*/
|
||||
public get sorted_submitters_id(): number[] {
|
||||
public get sorted_submitter_ids(): number[] {
|
||||
return this.submitters
|
||||
.sort((a: Submitter, b: Submitter) => {
|
||||
return a.weight - b.weight;
|
||||
@ -65,3 +71,5 @@ export class Motion extends BaseModelWithAgendaItemAndListOfSpeakers<Motion> {
|
||||
.map((submitter: Submitter) => submitter.user_id);
|
||||
}
|
||||
}
|
||||
|
||||
export interface Motion extends MotionWithoutNestedModels {}
|
||||
|
@ -40,21 +40,4 @@ export class State extends BaseModel<State> {
|
||||
public constructor(input?: any) {
|
||||
super(State.COLLECTIONSTRING, input);
|
||||
}
|
||||
|
||||
public toString = (): string => {
|
||||
return this.name;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a workflowstate has no 'next state' left, and is final
|
||||
*/
|
||||
public get isFinalState(): boolean {
|
||||
if (!this.next_states_id || !this.next_states_id.length) {
|
||||
return true;
|
||||
}
|
||||
if (this.next_states_id.length === 1 && this.next_states_id[0] === 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ export class Topic extends BaseModelWithAgendaItemAndListOfSpeakers<Topic> {
|
||||
public text: string;
|
||||
public attachments_id: number[];
|
||||
|
||||
public constructor(input?: any) {
|
||||
public constructor(input?: Partial<Topic>) {
|
||||
super(Topic.COLLECTIONSTRING, input);
|
||||
}
|
||||
}
|
||||
|
@ -11,11 +11,7 @@ export class Group extends BaseModel<Group> {
|
||||
public name: string;
|
||||
public permissions: string[];
|
||||
|
||||
public constructor(input?: any) {
|
||||
public constructor(input?: Partial<Group>) {
|
||||
super(Group.COLLECTIONSTRING, input);
|
||||
if (!input) {
|
||||
// permissions are required for new groups
|
||||
this.permissions = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ export class PersonalNote extends BaseModel<PersonalNote> implements PersonalNot
|
||||
public user_id: number;
|
||||
public notes: PersonalNotesFormat;
|
||||
|
||||
public constructor(input: any) {
|
||||
public constructor(input: Partial<PersonalNote>) {
|
||||
super(PersonalNote.COLLECTIONSTRING, input);
|
||||
}
|
||||
}
|
||||
|
@ -18,20 +18,20 @@ export class User extends BaseModel<User> {
|
||||
public title: string;
|
||||
public first_name: string;
|
||||
public last_name: string;
|
||||
public gender: string;
|
||||
public gender?: string;
|
||||
public structure_level: string;
|
||||
public number: string;
|
||||
public about_me: string;
|
||||
public groups_id: number[];
|
||||
public is_present: boolean;
|
||||
public is_committee: boolean;
|
||||
public email: string;
|
||||
public email?: string;
|
||||
public last_email_send?: string; // ISO datetime string
|
||||
public comment: string;
|
||||
public is_active: boolean;
|
||||
public default_password: string;
|
||||
public comment?: string;
|
||||
public is_active?: boolean;
|
||||
public default_password?: string;
|
||||
|
||||
public constructor(input?: any) {
|
||||
public constructor(input?: Partial<User>) {
|
||||
super(User.COLLECTIONSTRING, input);
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ export class AgendaListComponent extends BaseListViewComponent<ViewItem> impleme
|
||||
/**
|
||||
* Define extra filter properties
|
||||
*/
|
||||
public filterProps = ['itemNumber', 'comment', 'getListTitle'];
|
||||
public filterProps = ['item_number', 'comment', 'getListTitle'];
|
||||
|
||||
/**
|
||||
* The usual constructor for components
|
||||
@ -205,7 +205,7 @@ export class AgendaListComponent extends BaseListViewComponent<ViewItem> impleme
|
||||
public async onAutoNumbering(): Promise<void> {
|
||||
const title = this.translate.instant('Are you sure you want to number all agenda items?');
|
||||
if (await this.promptService.open(title)) {
|
||||
await this.repo.autoNumbering().then(null, this.raiseError);
|
||||
await this.repo.autoNumbering().catch(this.raiseError);
|
||||
}
|
||||
}
|
||||
|
||||
@ -213,7 +213,7 @@ export class AgendaListComponent extends BaseListViewComponent<ViewItem> impleme
|
||||
* Click handler for the done button in the dot-menu
|
||||
*/
|
||||
public async onDoneSingleButton(item: ViewItem): Promise<void> {
|
||||
await this.repo.update({ closed: !item.closed }, item).then(null, this.raiseError);
|
||||
await this.repo.update({ closed: !item.closed }, item).catch(this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -233,7 +233,7 @@ export class AgendaListComponent extends BaseListViewComponent<ViewItem> impleme
|
||||
const title = this.translate.instant('Are you sure you want to remove this entry from the agenda?');
|
||||
const content = item.contentObject.getTitle();
|
||||
if (await this.promptService.open(title, content)) {
|
||||
await this.repo.removeFromAgenda(item).then(null, this.raiseError);
|
||||
await this.repo.removeFromAgenda(item).catch(this.raiseError);
|
||||
}
|
||||
}
|
||||
|
||||
@ -244,7 +244,7 @@ export class AgendaListComponent extends BaseListViewComponent<ViewItem> impleme
|
||||
const title = this.translate.instant('Are you sure you want to delete this topic?');
|
||||
const content = item.contentObject.getTitle();
|
||||
if (await this.promptService.open(title, content)) {
|
||||
await this.topicRepo.delete(item.contentObject).then(null, this.raiseError);
|
||||
await this.topicRepo.delete(item.contentObject).catch(this.raiseError);
|
||||
}
|
||||
}
|
||||
|
||||
@ -295,7 +295,7 @@ export class AgendaListComponent extends BaseListViewComponent<ViewItem> impleme
|
||||
public async setAgendaType(agendaType: number): Promise<void> {
|
||||
try {
|
||||
for (const item of this.selectedRows) {
|
||||
await this.repo.update({ type: agendaType }, item).then(null, this.raiseError);
|
||||
await this.repo.update({ type: agendaType }, item).catch(this.raiseError);
|
||||
}
|
||||
} catch (e) {
|
||||
this.raiseError(e);
|
||||
|
@ -51,7 +51,7 @@ export class ItemInfoDialogComponent {
|
||||
// load current values
|
||||
this.agendaInfoForm.get('type').setValue(item.type);
|
||||
this.agendaInfoForm.get('durationText').setValue(this.durationService.durationToString(item.duration, 'h'));
|
||||
this.agendaInfoForm.get('item_number').setValue(item.itemNumber);
|
||||
this.agendaInfoForm.get('item_number').setValue(item.item_number);
|
||||
this.agendaInfoForm.get('comment').setValue(item.comment);
|
||||
}
|
||||
|
||||
|
@ -60,7 +60,7 @@
|
||||
<mat-icon>mic</mat-icon>
|
||||
</span>
|
||||
|
||||
<span class="name">{{ activeSpeaker.getTitle() }}</span>
|
||||
<span class="name">{{ activeSpeaker.getListTitle() }}</span>
|
||||
|
||||
<span class="suffix">
|
||||
<!-- Stop speaker button -->
|
||||
|
@ -417,9 +417,7 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
|
||||
*/
|
||||
public closeSpeakerList(): Promise<void> {
|
||||
if (!this.viewListOfSpeakers.closed) {
|
||||
return this.listOfSpeakersRepo
|
||||
.update({ closed: true }, this.viewListOfSpeakers)
|
||||
.then(null, this.raiseError);
|
||||
return this.listOfSpeakersRepo.update({ closed: true }, this.viewListOfSpeakers).catch(this.raiseError);
|
||||
}
|
||||
}
|
||||
|
||||
@ -428,9 +426,7 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
|
||||
*/
|
||||
public openSpeakerList(): Promise<void> {
|
||||
if (this.viewListOfSpeakers.closed) {
|
||||
return this.listOfSpeakersRepo
|
||||
.update({ closed: false }, this.viewListOfSpeakers)
|
||||
.then(null, this.raiseError);
|
||||
return this.listOfSpeakersRepo.update({ closed: false }, this.viewListOfSpeakers).catch(this.raiseError);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,34 +18,6 @@ export class ViewItem extends BaseViewModelWithContentObject<Item, BaseViewModel
|
||||
return this._model;
|
||||
}
|
||||
|
||||
public get itemNumber(): string {
|
||||
return this.item.item_number;
|
||||
}
|
||||
|
||||
public get title_information(): object {
|
||||
return this.item.title_information;
|
||||
}
|
||||
|
||||
public get duration(): number {
|
||||
return this.item.duration;
|
||||
}
|
||||
|
||||
public get type(): number {
|
||||
return this.item.type;
|
||||
}
|
||||
|
||||
public get closed(): boolean {
|
||||
return this.item.closed;
|
||||
}
|
||||
|
||||
public get comment(): string {
|
||||
return this.item.comment;
|
||||
}
|
||||
|
||||
public get level(): number {
|
||||
return this.item.level;
|
||||
}
|
||||
|
||||
public getSubtitle: () => string | null;
|
||||
|
||||
/**
|
||||
@ -71,19 +43,5 @@ export class ViewItem extends BaseViewModelWithContentObject<Item, BaseViewModel
|
||||
const type = ItemVisibilityChoices.find(choice => choice.key === this.type);
|
||||
return type ? type.csvName : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the weight the server assigns to that item. Mostly useful for sorting within
|
||||
* it's own hierarchy level (items sharing a parent)
|
||||
*/
|
||||
public get weight(): number {
|
||||
return this.item.weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the parent's id of that item (0 if no parent is set).
|
||||
*/
|
||||
public get parent_id(): number {
|
||||
return this.item.parent_id;
|
||||
}
|
||||
}
|
||||
export interface ViewItem extends Item {}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ListOfSpeakers } from 'app/shared/models/agenda/list-of-speakers';
|
||||
import { ListOfSpeakers, ListOfSpeakersWithoutNestedModels } from 'app/shared/models/agenda/list-of-speakers';
|
||||
import { ContentObject } from 'app/shared/models/base/content-object';
|
||||
import { BaseViewModelWithContentObject } from 'app/site/base/base-view-model-with-content-object';
|
||||
import { BaseViewModelWithListOfSpeakers } from 'app/site/base/base-view-model-with-list-of-speakers';
|
||||
@ -19,20 +19,10 @@ export class ViewListOfSpeakers extends BaseViewModelWithContentObject<ListOfSpe
|
||||
public static COLLECTIONSTRING = ListOfSpeakers.COLLECTIONSTRING;
|
||||
protected _collectionString = ListOfSpeakers.COLLECTIONSTRING;
|
||||
|
||||
private _speakers?: ViewSpeaker[];
|
||||
|
||||
public get listOfSpeakers(): ListOfSpeakers {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
public get speakers(): ViewSpeaker[] {
|
||||
return this._speakers || [];
|
||||
}
|
||||
|
||||
public get title_information(): object {
|
||||
return this.listOfSpeakers.title_information;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the amount of waiting speakers
|
||||
*/
|
||||
@ -40,10 +30,6 @@ export class ViewListOfSpeakers extends BaseViewModelWithContentObject<ListOfSpe
|
||||
return this.speakers.filter(speaker => speaker.state === SpeakerState.WAITING).length;
|
||||
}
|
||||
|
||||
public get closed(): boolean {
|
||||
return this.listOfSpeakers.closed;
|
||||
}
|
||||
|
||||
public get listOfSpeakersUrl(): string {
|
||||
return `/agenda/speakers/${this.id}`;
|
||||
}
|
||||
@ -65,3 +51,7 @@ export class ViewListOfSpeakers extends BaseViewModelWithContentObject<ListOfSpe
|
||||
};
|
||||
}
|
||||
}
|
||||
interface IListOfSpeakersRelations {
|
||||
speakers: ViewSpeaker[];
|
||||
}
|
||||
export interface ViewListOfSpeakers extends ListOfSpeakersWithoutNestedModels, IListOfSpeakersRelations {}
|
||||
|
@ -18,46 +18,10 @@ export class ViewSpeaker extends BaseViewModel<Speaker> {
|
||||
public static COLLECTIONSTRING = Speaker.COLLECTIONSTRING;
|
||||
protected _collectionString = Speaker.COLLECTIONSTRING;
|
||||
|
||||
private _user?: ViewUser;
|
||||
|
||||
public get speaker(): Speaker {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
public get user(): ViewUser | null {
|
||||
return this._user;
|
||||
}
|
||||
|
||||
public get id(): number {
|
||||
return this.speaker.id;
|
||||
}
|
||||
|
||||
public get user_id(): number {
|
||||
return this.speaker.user_id;
|
||||
}
|
||||
|
||||
public get weight(): number {
|
||||
return this.speaker.weight;
|
||||
}
|
||||
|
||||
public get marked(): boolean {
|
||||
return this.speaker.marked;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns an ISO datetime string or null
|
||||
*/
|
||||
public get begin_time(): string | null {
|
||||
return this.speaker.begin_time;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns an ISO datetime string or null
|
||||
*/
|
||||
public get end_time(): string | null {
|
||||
return this.speaker.end_time;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns
|
||||
* - waiting if there is no begin nor end time
|
||||
@ -82,7 +46,10 @@ export class ViewSpeaker extends BaseViewModel<Speaker> {
|
||||
return this.user ? this.user.gender : '';
|
||||
}
|
||||
|
||||
public getTitle = () => {
|
||||
return this.name;
|
||||
public getListTitle = () => {
|
||||
return this.getTitle();
|
||||
};
|
||||
}
|
||||
export interface ViewSpeaker extends Speaker {
|
||||
user?: ViewUser;
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ export class AgendaPdfService {
|
||||
},
|
||||
{
|
||||
width: 60,
|
||||
text: nodeItem.item.itemNumber
|
||||
text: nodeItem.item.item_number
|
||||
},
|
||||
{
|
||||
text: nodeItem.item.contentObject.getAgendaListTitleWithoutItemNumber()
|
||||
|
@ -36,11 +36,11 @@
|
||||
|
||||
<!-- Add/remove to/from agenda -->
|
||||
<div *osPerms="'agenda.can_manage'">
|
||||
<button mat-menu-item (click)="addToAgenda()" *ngIf="assignment && !assignment.agendaItem">
|
||||
<button mat-menu-item (click)="addToAgenda()" *ngIf="assignment && !assignment.item">
|
||||
<mat-icon>add</mat-icon>
|
||||
<span translate>Add to agenda</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="removeFromAgenda()" *ngIf="assignment && assignment.agendaItem">
|
||||
<button mat-menu-item (click)="removeFromAgenda()" *ngIf="assignment && assignment.item">
|
||||
<mat-icon>remove</mat-icon>
|
||||
<span translate>Remove from agenda</span>
|
||||
</button>
|
||||
@ -241,7 +241,6 @@
|
||||
matInput
|
||||
placeholder="{{ 'Title' | translate }}"
|
||||
formControlName="title"
|
||||
[value]="assignmentCopy.title || ''"
|
||||
/>
|
||||
<mat-error>{{ 'The title is required' | translate }}</mat-error>
|
||||
</mat-form-field>
|
||||
@ -287,7 +286,6 @@
|
||||
matInput
|
||||
placeholder="{{ 'Default comment on the ballot paper' | translate }}"
|
||||
formControlName="poll_description_default"
|
||||
[value]="assignmentCopy.assignment.poll_description_default || ''"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
@ -112,11 +112,6 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
||||
*/
|
||||
private _assignment: ViewAssignment;
|
||||
|
||||
/**
|
||||
* Copy instance of the assignment that the user might edit
|
||||
*/
|
||||
public assignmentCopy: ViewAssignment;
|
||||
|
||||
/**
|
||||
* Check if the operator is a candidate
|
||||
*
|
||||
@ -288,7 +283,6 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
||||
* @param assignment
|
||||
*/
|
||||
private patchForm(assignment: ViewAssignment): void {
|
||||
this.assignmentCopy = assignment;
|
||||
const contentPatch: { [key: string]: any } = {};
|
||||
Object.keys(this.assignmentForm.controls).forEach(control => {
|
||||
contentPatch[control] = assignment[control];
|
||||
@ -299,11 +293,11 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
||||
/**
|
||||
* Save the current state of the assignment
|
||||
*/
|
||||
public saveAssignment(): void {
|
||||
public async saveAssignment(): Promise<void> {
|
||||
if (this.newAssignment) {
|
||||
this.createAssignment();
|
||||
} else {
|
||||
this.updateAssignmentFromForm();
|
||||
await this.updateAssignmentFromForm();
|
||||
}
|
||||
}
|
||||
|
||||
@ -312,21 +306,21 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
||||
* TODO: directly open poll dialog?
|
||||
*/
|
||||
public async createPoll(): Promise<void> {
|
||||
await this.repo.addPoll(this.assignment).then(null, this.raiseError);
|
||||
await this.repo.addPoll(this.assignment).catch(this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the operator to list of candidates
|
||||
*/
|
||||
public async addSelf(): Promise<void> {
|
||||
await this.repo.addSelf(this.assignment).then(null, this.raiseError);
|
||||
await this.repo.addSelf(this.assignment).catch(this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the operator from list of candidates
|
||||
*/
|
||||
public async removeSelf(): Promise<void> {
|
||||
await this.repo.deleteSelf(this.assignment).then(null, this.raiseError);
|
||||
await this.repo.deleteSelf(this.assignment).catch(this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -337,7 +331,7 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
||||
public async addUser(userId: number): Promise<void> {
|
||||
const user = this.userRepo.getViewModel(userId);
|
||||
if (user) {
|
||||
await this.repo.changeCandidate(user, this.assignment, true).then(null, this.raiseError);
|
||||
await this.repo.changeCandidate(user, this.assignment, true).catch(this.raiseError);
|
||||
}
|
||||
}
|
||||
|
||||
@ -347,7 +341,7 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
||||
* @param candidate A ViewAssignmentUser currently in the list of related users
|
||||
*/
|
||||
public async removeUser(candidate: ViewAssignmentRelatedUser): Promise<void> {
|
||||
await this.repo.changeCandidate(candidate.user, this.assignment, false).then(null, this.raiseError);
|
||||
await this.repo.changeCandidate(candidate.user, this.assignment, false).catch(this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -385,7 +379,6 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
||||
this.editAssignment = true;
|
||||
// TODO set defaults?
|
||||
this.assignment = new ViewAssignment(new Assignment());
|
||||
this.assignmentCopy = new ViewAssignment(new Assignment());
|
||||
}
|
||||
}
|
||||
|
||||
@ -414,7 +407,7 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
||||
* @param value the phase to set
|
||||
*/
|
||||
public async onSetPhaseButton(value: number): Promise<void> {
|
||||
this.repo.update({ phase: value }, this.assignment).then(null, this.raiseError);
|
||||
this.repo.update({ phase: value }, this.assignment).catch(this.raiseError);
|
||||
}
|
||||
|
||||
public onDownloadPdf(): void {
|
||||
@ -438,10 +431,13 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
||||
}
|
||||
}
|
||||
|
||||
public updateAssignmentFromForm(): void {
|
||||
this.repo
|
||||
.patch({ ...this.assignmentForm.value }, this.assignmentCopy)
|
||||
.then(() => (this.editAssignment = false), this.raiseError);
|
||||
public async updateAssignmentFromForm(): Promise<void> {
|
||||
try {
|
||||
await this.repo.patch({ ...this.assignmentForm.value }, this.assignment);
|
||||
this.editAssignment = false;
|
||||
} catch (e) {
|
||||
this.raiseError(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -482,7 +478,7 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
||||
* (triggered on an autoupdate of either users or the assignment)
|
||||
*/
|
||||
private filterCandidates(): void {
|
||||
if (!this.assignment || !this.assignment.candidates) {
|
||||
if (!this.assignment || !this.assignment.candidates.length) {
|
||||
this.filteredCandidates.next(this.availableCandidates.getValue());
|
||||
} else {
|
||||
this.filteredCandidates.next(
|
||||
@ -499,14 +495,14 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
||||
public onSortingChange(listInNewOrder: ViewAssignmentRelatedUser[]): void {
|
||||
this.repo
|
||||
.sortCandidates(listInNewOrder.map(relatedUser => relatedUser.id), this.assignment)
|
||||
.then(null, this.raiseError);
|
||||
.catch(this.raiseError);
|
||||
}
|
||||
|
||||
public addToAgenda(): void {
|
||||
this.itemRepo.addItemToAgenda(this.assignment).then(null, this.raiseError);
|
||||
this.itemRepo.addItemToAgenda(this.assignment).catch(this.raiseError);
|
||||
}
|
||||
|
||||
public removeFromAgenda(): void {
|
||||
this.itemRepo.removeFromAgenda(this.assignment.agendaItem).then(null, this.raiseError);
|
||||
this.itemRepo.removeFromAgenda(this.assignment.item).catch(this.raiseError);
|
||||
}
|
||||
}
|
||||
|
@ -90,12 +90,10 @@ export class AssignmentPollDialogComponent {
|
||||
*/
|
||||
public submit(): void {
|
||||
const error = this.data.options.find(dataoption => {
|
||||
for (const key of this.optionPollKeys) {
|
||||
this.optionPollKeys.some(key => {
|
||||
const keyValue = dataoption.votes.find(o => o.value === key);
|
||||
if (!keyValue || keyValue.weight === undefined) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return !keyValue || keyValue.weight === undefined;
|
||||
});
|
||||
});
|
||||
if (error) {
|
||||
this.matSnackBar.open(
|
||||
|
@ -170,7 +170,7 @@
|
||||
<h4 translate>Candidates</h4>
|
||||
<div *ngFor="let option of poll.options">
|
||||
<span class="accent" *ngIf="option.user">{{ option.user.getFullName() }}</span>
|
||||
<span *ngIf="!option.user">No user {{ option.user_id }}</span>
|
||||
<span *ngIf="!option.user">No user {{ option.candidate_id }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -161,7 +161,7 @@ export class AssignmentPollComponent extends BaseViewComponent implements OnInit
|
||||
public async onDeletePoll(): Promise<void> {
|
||||
const title = this.translate.instant('Are you sure you want to delete this ballot?');
|
||||
if (await this.promptService.open(title)) {
|
||||
await this.assignmentRepo.deletePoll(this.poll).then(null, this.raiseError);
|
||||
await this.assignmentRepo.deletePoll(this.poll).catch(this.raiseError);
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,13 +197,12 @@ export class AssignmentPollComponent extends BaseViewComponent implements OnInit
|
||||
*/
|
||||
public enterVotes(): void {
|
||||
const dialogRef = this.dialog.open(AssignmentPollDialogComponent, {
|
||||
// TODO deep copy of this.poll (JSON parse is ugly workaround) or sending just copy of the options
|
||||
data: this.poll.copy(),
|
||||
...mediumDialogSettings
|
||||
});
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
if (result) {
|
||||
this.assignmentRepo.updateVotes(result, this.poll).then(null, this.raiseError);
|
||||
this.assignmentRepo.updateVotes(result, this.poll).catch(this.raiseError);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -236,7 +235,7 @@ export class AssignmentPollComponent extends BaseViewComponent implements OnInit
|
||||
|
||||
// TODO additional conditions: assignment not finished?
|
||||
const viewAssignmentRelatedUser = this.assignment.assignment_related_users.find(
|
||||
user => user.user_id === option.user_id
|
||||
user => user.user_id === option.candidate_id
|
||||
);
|
||||
if (viewAssignmentRelatedUser) {
|
||||
this.assignmentRepo.markElected(viewAssignmentRelatedUser, this.assignment, !option.is_elected);
|
||||
@ -249,7 +248,7 @@ export class AssignmentPollComponent extends BaseViewComponent implements OnInit
|
||||
*/
|
||||
public async onEditDescriptionButton(): Promise<void> {
|
||||
const desc: string = this.descriptionForm.get('description').value;
|
||||
await this.assignmentRepo.updatePoll({ description: desc }, this.poll).then(null, this.raiseError);
|
||||
await this.assignmentRepo.updatePoll({ description: desc }, this.poll).catch(this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -12,40 +12,14 @@ export class ViewAssignmentPollOption extends BaseViewModel<AssignmentPollOption
|
||||
public static COLLECTIONSTRING = AssignmentPollOption.COLLECTIONSTRING;
|
||||
protected _collectionString = AssignmentPollOption.COLLECTIONSTRING;
|
||||
|
||||
private _user?: ViewUser; // This is the "candidate". We'll stay consistent wich user here...
|
||||
|
||||
public get option(): AssignmentPollOption {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: "User" instead of "candidate" to be consistent.
|
||||
*/
|
||||
public get user(): ViewUser | null {
|
||||
return this._user;
|
||||
}
|
||||
|
||||
public get id(): number {
|
||||
return this.option.id;
|
||||
}
|
||||
|
||||
public get user_id(): number {
|
||||
return this.option.user_id;
|
||||
}
|
||||
|
||||
public get is_elected(): boolean {
|
||||
return this.option.is_elected;
|
||||
}
|
||||
|
||||
public get votes(): AssignmentOptionVote[] {
|
||||
return this.option.votes.sort((a, b) => votesOrder.indexOf(a.value) - votesOrder.indexOf(b.value));
|
||||
}
|
||||
|
||||
public get poll_id(): number {
|
||||
return this.option.poll_id;
|
||||
}
|
||||
|
||||
public get weight(): number {
|
||||
return this.option.weight;
|
||||
}
|
||||
export interface ViewAssignmentPollOption extends AssignmentPollOption {
|
||||
user: ViewUser;
|
||||
}
|
||||
|
@ -1,87 +1,17 @@
|
||||
import { AssignmentPoll } from 'app/shared/models/assignments/assignment-poll';
|
||||
import { AssignmentPoll, AssignmentPollWithoutNestedModels } from 'app/shared/models/assignments/assignment-poll';
|
||||
import { AssignmentPollOption } from 'app/shared/models/assignments/assignment-poll-option';
|
||||
import { BaseProjectableViewModel } from 'app/site/base/base-projectable-view-model';
|
||||
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
|
||||
import { AssignmentPollMethod } from '../services/assignment-poll.service';
|
||||
import { ViewAssignmentPollOption } from './view-assignment-poll-option';
|
||||
|
||||
export class ViewAssignmentPoll extends BaseProjectableViewModel<AssignmentPoll> {
|
||||
public static COLLECTIONSTRING = AssignmentPoll.COLLECTIONSTRING;
|
||||
protected _collectionString = AssignmentPoll.COLLECTIONSTRING;
|
||||
|
||||
private _options: ViewAssignmentPollOption[];
|
||||
|
||||
public get poll(): AssignmentPoll {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
public get options(): ViewAssignmentPollOption[] {
|
||||
return this._options;
|
||||
}
|
||||
|
||||
public get id(): number {
|
||||
return this.poll.id;
|
||||
}
|
||||
|
||||
public get pollmethod(): AssignmentPollMethod {
|
||||
return this.poll.pollmethod;
|
||||
}
|
||||
|
||||
public get description(): string {
|
||||
return this.poll.description;
|
||||
}
|
||||
|
||||
public get published(): boolean {
|
||||
return this.poll.published;
|
||||
}
|
||||
|
||||
public get votesno(): number {
|
||||
return this.poll.votesno;
|
||||
}
|
||||
public set votesno(amount: number) {
|
||||
this.poll.votesno = amount;
|
||||
}
|
||||
|
||||
public get votesabstain(): number {
|
||||
return this.poll.votesabstain;
|
||||
}
|
||||
public set votesabstain(amount: number) {
|
||||
this.poll.votesabstain = amount;
|
||||
}
|
||||
|
||||
public get votesvalid(): number {
|
||||
return this.poll.votesvalid;
|
||||
}
|
||||
public set votesvalid(amount: number) {
|
||||
this.poll.votesvalid = amount;
|
||||
}
|
||||
|
||||
public get votesinvalid(): number {
|
||||
return this.poll.votesinvalid;
|
||||
}
|
||||
public set votesinvalid(amount: number) {
|
||||
this.poll.votesinvalid = amount;
|
||||
}
|
||||
|
||||
public get votescast(): number {
|
||||
return this.poll.votescast;
|
||||
}
|
||||
public set votescast(amount: number) {
|
||||
this.poll.votescast = amount;
|
||||
}
|
||||
|
||||
public get has_votes(): boolean {
|
||||
return this.poll.has_votes;
|
||||
}
|
||||
|
||||
public get assignment_id(): number {
|
||||
return this.poll.assignment_id;
|
||||
}
|
||||
|
||||
public getTitle = () => {
|
||||
return 'Poll';
|
||||
};
|
||||
|
||||
public getListTitle = () => {
|
||||
return this.getTitle();
|
||||
};
|
||||
@ -90,6 +20,20 @@ export class ViewAssignmentPoll extends BaseProjectableViewModel<AssignmentPoll>
|
||||
return this.getTitle();
|
||||
};
|
||||
|
||||
public getSlide(): ProjectorElementBuildDeskriptor {
|
||||
return {
|
||||
getBasicProjectorElement: options => ({
|
||||
name: 'assignments/poll',
|
||||
assignment_id: this.assignment_id,
|
||||
poll_id: this.id,
|
||||
getIdentifiers: () => ['name', 'assignment_id', 'poll_id']
|
||||
}),
|
||||
slideOptions: [],
|
||||
projectionDefaultName: 'assignments',
|
||||
getDialogTitle: () => 'TODO'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy with deep-copy on all changing numerical values,
|
||||
* but intact uncopied references to the users
|
||||
@ -107,18 +51,8 @@ export class ViewAssignmentPoll extends BaseProjectableViewModel<AssignmentPoll>
|
||||
});
|
||||
return poll;
|
||||
}
|
||||
}
|
||||
|
||||
public getSlide(): ProjectorElementBuildDeskriptor {
|
||||
return {
|
||||
getBasicProjectorElement: options => ({
|
||||
name: 'assignments/poll',
|
||||
assignment_id: this.assignment_id,
|
||||
poll_id: this.id,
|
||||
getIdentifiers: () => ['name', 'assignment_id', 'poll_id']
|
||||
}),
|
||||
slideOptions: [],
|
||||
projectionDefaultName: 'assignments',
|
||||
getDialogTitle: () => 'TODO'
|
||||
};
|
||||
}
|
||||
export interface ViewAssignmentPoll extends AssignmentPollWithoutNestedModels {
|
||||
options: ViewAssignmentPollOption[];
|
||||
}
|
||||
|
@ -6,39 +6,15 @@ export class ViewAssignmentRelatedUser extends BaseViewModel<AssignmentRelatedUs
|
||||
public static COLLECTIONSTRING = AssignmentRelatedUser.COLLECTIONSTRING;
|
||||
protected _collectionString = AssignmentRelatedUser.COLLECTIONSTRING;
|
||||
|
||||
private _user?: ViewUser;
|
||||
|
||||
public get assignmentRelatedUser(): AssignmentRelatedUser {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
public get user(): ViewUser {
|
||||
return this._user;
|
||||
}
|
||||
|
||||
public get id(): number {
|
||||
return this.assignmentRelatedUser.id;
|
||||
}
|
||||
|
||||
public get user_id(): number {
|
||||
return this.assignmentRelatedUser.user_id;
|
||||
}
|
||||
|
||||
public get assignment_id(): number {
|
||||
return this.assignmentRelatedUser.assignment_id;
|
||||
}
|
||||
|
||||
public get elected(): boolean {
|
||||
return this.assignmentRelatedUser.elected;
|
||||
}
|
||||
|
||||
public get weight(): number {
|
||||
return this.assignmentRelatedUser.weight;
|
||||
}
|
||||
|
||||
public getListTitle: () => string = this.getTitle;
|
||||
|
||||
public getTitle: () => string = () => {
|
||||
return this.user ? this.user.getFullName() : '';
|
||||
public getListTitle = () => {
|
||||
return this.getTitle();
|
||||
};
|
||||
}
|
||||
|
||||
export interface ViewAssignmentRelatedUser extends AssignmentRelatedUser {
|
||||
user?: ViewUser;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { SearchRepresentation } from 'app/core/ui-services/search.service';
|
||||
import { Assignment } from 'app/shared/models/assignments/assignment';
|
||||
import { Assignment, AssignmentWithoutNestedModels } from 'app/shared/models/assignments/assignment';
|
||||
import { TitleInformationWithAgendaItem } from 'app/site/base/base-view-model-with-agenda-item';
|
||||
import { BaseViewModelWithAgendaItemAndListOfSpeakers } from 'app/site/base/base-view-model-with-agenda-item-and-list-of-speakers';
|
||||
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
|
||||
@ -41,60 +41,18 @@ export class ViewAssignment extends BaseViewModelWithAgendaItemAndListOfSpeakers
|
||||
public static COLLECTIONSTRING = Assignment.COLLECTIONSTRING;
|
||||
protected _collectionString = Assignment.COLLECTIONSTRING;
|
||||
|
||||
private _assignment_related_users?: ViewAssignmentRelatedUser[];
|
||||
private _polls?: ViewAssignmentPoll[];
|
||||
private _tags?: ViewTag[];
|
||||
private _attachments?: ViewMediafile[];
|
||||
|
||||
public get assignment(): Assignment {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
public get polls(): ViewAssignmentPoll[] {
|
||||
return this._polls || [];
|
||||
}
|
||||
|
||||
public get title(): string {
|
||||
return this.assignment.title;
|
||||
}
|
||||
|
||||
public get open_posts(): number {
|
||||
return this.assignment.open_posts;
|
||||
}
|
||||
|
||||
public get description(): string {
|
||||
return this.assignment.description;
|
||||
}
|
||||
|
||||
public get candidates(): ViewUser[] {
|
||||
return this.assignment_related_users.map(aru => aru.user).filter(x => !!x);
|
||||
}
|
||||
|
||||
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
|
||||
* TODO: Fix assignment creation: DO NOT create a ViewUser there...
|
||||
*/
|
||||
public get phase(): number {
|
||||
return this.assignment.phase;
|
||||
public get candidates(): ViewUser[] {
|
||||
if (!this.assignment_related_users) {
|
||||
return [];
|
||||
}
|
||||
return this.assignment_related_users.map(aru => aru.user).filter(x => !!x);
|
||||
}
|
||||
|
||||
public get phaseString(): string {
|
||||
@ -123,7 +81,7 @@ export class ViewAssignment extends BaseViewModelWithAgendaItemAndListOfSpeakers
|
||||
* @returns the amount of candidates in the assignment's candidate list
|
||||
*/
|
||||
public get candidateAmount(): number {
|
||||
return this._assignment_related_users ? this._assignment_related_users.length : 0;
|
||||
return this.assignment_related_users.length;
|
||||
}
|
||||
|
||||
public formatForSearch(): SearchRepresentation {
|
||||
@ -147,3 +105,11 @@ export class ViewAssignment extends BaseViewModelWithAgendaItemAndListOfSpeakers
|
||||
};
|
||||
}
|
||||
}
|
||||
interface IAssignmentRelations {
|
||||
assignment_related_users: ViewAssignmentRelatedUser[];
|
||||
polls?: ViewAssignmentPoll[];
|
||||
tags?: ViewTag[];
|
||||
attachments?: ViewMediafile[];
|
||||
}
|
||||
|
||||
export interface ViewAssignment extends AssignmentWithoutNestedModels, IAssignmentRelations {}
|
||||
|
@ -1,13 +1,11 @@
|
||||
import { SearchRepresentation } from 'app/core/ui-services/search.service';
|
||||
import { applyMixins } from 'app/core/mixins';
|
||||
import { BaseModelWithAgendaItemAndListOfSpeakers } from 'app/shared/models/base/base-model-with-agenda-item-and-list-of-speakers';
|
||||
import { BaseProjectableViewModel } from './base-projectable-view-model';
|
||||
import { IBaseViewModelWithAgendaItem, isBaseViewModelWithAgendaItem } from './base-view-model-with-agenda-item';
|
||||
import { BaseViewModelWithAgendaItem, isBaseViewModelWithAgendaItem } from './base-view-model-with-agenda-item';
|
||||
import {
|
||||
IBaseViewModelWithListOfSpeakers,
|
||||
BaseViewModelWithListOfSpeakers,
|
||||
isBaseViewModelWithListOfSpeakers
|
||||
} from './base-view-model-with-list-of-speakers';
|
||||
import { ViewItem } from '../agenda/models/view-item';
|
||||
import { ViewListOfSpeakers } from '../agenda/models/view-list-of-speakers';
|
||||
|
||||
export function isBaseViewModelWithAgendaItemAndListOfSpeakers(
|
||||
obj: any
|
||||
@ -15,54 +13,15 @@ export function isBaseViewModelWithAgendaItemAndListOfSpeakers(
|
||||
return !!obj && isBaseViewModelWithAgendaItem(obj) && isBaseViewModelWithListOfSpeakers(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base view class for view models with an agenda item and a list of speakers associated.
|
||||
*/
|
||||
export abstract class BaseViewModelWithAgendaItemAndListOfSpeakers<
|
||||
M extends BaseModelWithAgendaItemAndListOfSpeakers = any
|
||||
> extends BaseProjectableViewModel implements IBaseViewModelWithAgendaItem<M>, IBaseViewModelWithListOfSpeakers<M> {
|
||||
protected _item?: ViewItem;
|
||||
protected _list_of_speakers?: ViewListOfSpeakers;
|
||||
> extends BaseProjectableViewModel<M> {}
|
||||
|
||||
public get agendaItem(): ViewItem | null {
|
||||
return this._item;
|
||||
}
|
||||
export interface BaseViewModelWithAgendaItemAndListOfSpeakers<M extends BaseModelWithAgendaItemAndListOfSpeakers = any>
|
||||
extends BaseViewModelWithAgendaItem<M>,
|
||||
BaseViewModelWithListOfSpeakers<M> {}
|
||||
|
||||
public get agenda_item_id(): number {
|
||||
return this._model.agenda_item_id;
|
||||
}
|
||||
|
||||
public get agenda_item_number(): string | null {
|
||||
return this.agendaItem && this.agendaItem.itemNumber ? this.agendaItem.itemNumber : null;
|
||||
}
|
||||
|
||||
public get listOfSpeakers(): ViewListOfSpeakers | null {
|
||||
return this._list_of_speakers;
|
||||
}
|
||||
|
||||
public get list_of_speakers_id(): number {
|
||||
return this._model.list_of_speakers_id;
|
||||
}
|
||||
|
||||
public getAgendaSlideTitle: () => string;
|
||||
public getAgendaListTitle: () => string;
|
||||
public getAgendaListTitleWithoutItemNumber: () => string;
|
||||
public getAgendaSubtitle: () => string | null;
|
||||
public getListOfSpeakersTitle: () => string;
|
||||
public getListOfSpeakersSlideTitle: () => string;
|
||||
|
||||
/**
|
||||
* @returns the (optional) descriptive text to be exported in the CSV.
|
||||
* May be overridden by inheriting classes
|
||||
*/
|
||||
public getCSVExportText(): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
public abstract getDetailStateURL(): string;
|
||||
|
||||
/**
|
||||
* Should return a string representation of the object, so there can be searched for.
|
||||
*/
|
||||
public abstract formatForSearch(): SearchRepresentation;
|
||||
}
|
||||
applyMixins(BaseViewModelWithAgendaItemAndListOfSpeakers, [
|
||||
BaseViewModelWithAgendaItem,
|
||||
BaseViewModelWithListOfSpeakers
|
||||
]);
|
||||
|
@ -13,9 +13,11 @@ export function isBaseViewModelWithAgendaItem(obj: any): obj is BaseViewModelWit
|
||||
isSearchable(model) &&
|
||||
model.getAgendaSlideTitle !== undefined &&
|
||||
model.getAgendaListTitle !== undefined &&
|
||||
model.getAgendaSubtitle !== undefined &&
|
||||
model.getCSVExportText !== undefined &&
|
||||
model.agendaItem !== undefined &&
|
||||
model.agenda_item_id !== undefined
|
||||
model.item !== undefined &&
|
||||
model.getModel !== undefined &&
|
||||
model.getModel().agenda_item_id !== undefined
|
||||
);
|
||||
}
|
||||
|
||||
@ -26,15 +28,11 @@ export interface TitleInformationWithAgendaItem extends TitleInformation {
|
||||
/**
|
||||
* Describes a base class for view models.
|
||||
*/
|
||||
export interface IBaseViewModelWithAgendaItem<M extends BaseModelWithAgendaItem = any>
|
||||
export interface BaseViewModelWithAgendaItem<M extends BaseModelWithAgendaItem = any>
|
||||
extends BaseProjectableViewModel<M>,
|
||||
DetailNavigable,
|
||||
Searchable {
|
||||
agendaItem: any | null;
|
||||
|
||||
agenda_item_id: number;
|
||||
|
||||
agenda_item_number: string | null;
|
||||
item: any | null;
|
||||
|
||||
/**
|
||||
* @returns the agenda title
|
||||
@ -46,21 +44,10 @@ export interface IBaseViewModelWithAgendaItem<M extends BaseModelWithAgendaItem
|
||||
*/
|
||||
getAgendaListTitle: () => string;
|
||||
|
||||
/**
|
||||
* @return an optional subtitle for the agenda.
|
||||
*/
|
||||
getAgendaSubtitle: () => string | null;
|
||||
|
||||
/**
|
||||
* @return the agenda title with the verbose name of the content object
|
||||
*/
|
||||
getAgendaListTitleWithoutItemNumber: () => string;
|
||||
|
||||
/**
|
||||
* @returns the (optional) descriptive text to be exported in the CSV.
|
||||
* May be overridden by inheriting classes
|
||||
*/
|
||||
getCSVExportText(): string;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -68,41 +55,11 @@ export interface IBaseViewModelWithAgendaItem<M extends BaseModelWithAgendaItem
|
||||
*
|
||||
* TODO: Resolve circular dependencies with `ViewItem` to avoid `any`.
|
||||
*/
|
||||
export abstract class BaseViewModelWithAgendaItem<M extends BaseModelWithAgendaItem = any>
|
||||
extends BaseProjectableViewModel<M>
|
||||
implements IBaseViewModelWithAgendaItem<M> {
|
||||
protected _item?: any;
|
||||
|
||||
public get agendaItem(): any | null {
|
||||
return this._item;
|
||||
}
|
||||
|
||||
public get agenda_item_id(): number {
|
||||
return this._model.agenda_item_id;
|
||||
}
|
||||
|
||||
export abstract class BaseViewModelWithAgendaItem<
|
||||
M extends BaseModelWithAgendaItem = any
|
||||
> extends BaseProjectableViewModel<M> {
|
||||
public get agenda_item_number(): string | null {
|
||||
return this.agendaItem && this.agendaItem.itemNumber ? this.agendaItem.itemNumber : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the agenda title for the item slides
|
||||
*/
|
||||
public getAgendaSlideTitle: () => string;
|
||||
|
||||
/**
|
||||
* @return the agenda title for the list view
|
||||
*/
|
||||
public getAgendaListTitle: () => string;
|
||||
|
||||
/**
|
||||
* @return the agenda title without any item number.
|
||||
*/
|
||||
public getAgendaListTitleWithoutItemNumber: () => string;
|
||||
|
||||
public constructor(model: M, item?: any) {
|
||||
super(model);
|
||||
this._item = item || null; // Explicit set to null instead of undefined, if not given
|
||||
return this.item && this.item.item_number ? this.item.item_number : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -13,13 +13,14 @@ export abstract class BaseViewModelWithContentObject<
|
||||
M extends BaseModelWithContentObject = any,
|
||||
C extends BaseViewModel = any
|
||||
> extends BaseViewModel<M> {
|
||||
protected _contentObject?: C;
|
||||
|
||||
public get contentObjectData(): ContentObject {
|
||||
return this.getModel().content_object;
|
||||
}
|
||||
}
|
||||
|
||||
public get contentObject(): C | null {
|
||||
return this._contentObject;
|
||||
}
|
||||
export interface BaseViewModelWithContentObject<
|
||||
M extends BaseModelWithContentObject = any,
|
||||
C extends BaseViewModel = any
|
||||
> {
|
||||
contentObject: C | null;
|
||||
}
|
||||
|
@ -9,50 +9,19 @@ export function isBaseViewModelWithListOfSpeakers(obj: any): obj is BaseViewMode
|
||||
isDetailNavigable(model) &&
|
||||
model.getListOfSpeakersTitle !== undefined &&
|
||||
model.listOfSpeakers !== undefined &&
|
||||
model.list_of_speakers_id !== undefined
|
||||
model.getModel !== undefined &&
|
||||
model.getModel().list_of_speakers_id !== undefined
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes a base view model with a list of speakers.
|
||||
*/
|
||||
export interface IBaseViewModelWithListOfSpeakers<M extends BaseModelWithListOfSpeakers = any>
|
||||
extends BaseProjectableViewModel<M>,
|
||||
DetailNavigable {
|
||||
export interface BaseViewModelWithListOfSpeakers<M extends BaseModelWithListOfSpeakers = any> extends DetailNavigable {
|
||||
listOfSpeakers: any | null;
|
||||
|
||||
list_of_speakers_id: number;
|
||||
|
||||
getListOfSpeakersTitle: () => string;
|
||||
|
||||
getListOfSpeakersSlideTitle: () => string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base view model class for models with a list of speakers.
|
||||
*
|
||||
* TODO: Resolve circular dependencies with `ViewListOfSpeakers` to avoid `any`.
|
||||
*/
|
||||
export abstract class BaseViewModelWithListOfSpeakers<M extends BaseModelWithListOfSpeakers = any>
|
||||
extends BaseProjectableViewModel<M>
|
||||
implements IBaseViewModelWithListOfSpeakers<M> {
|
||||
protected _list_of_speakers?: any;
|
||||
|
||||
public get listOfSpeakers(): any | null {
|
||||
return this._list_of_speakers;
|
||||
}
|
||||
|
||||
public get list_of_speakers_id(): number {
|
||||
return this._model.list_of_speakers_id;
|
||||
}
|
||||
|
||||
public getListOfSpeakersTitle: () => string;
|
||||
public getListOfSpeakersSlideTitle: () => string;
|
||||
|
||||
public constructor(model: M, listOfSpeakers?: any) {
|
||||
super(model);
|
||||
this._list_of_speakers = listOfSpeakers || null; // Explicit set to null instead of undefined, if not given
|
||||
}
|
||||
|
||||
export abstract class BaseViewModelWithListOfSpeakers<
|
||||
M extends BaseModelWithListOfSpeakers = any
|
||||
> extends BaseProjectableViewModel<M> {
|
||||
public abstract getDetailStateURL(): string;
|
||||
}
|
||||
|
@ -11,47 +11,16 @@ export interface ViewModelConstructor<T extends BaseViewModel> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for view models. alls view models should have titles.
|
||||
* Base class for view models.
|
||||
*/
|
||||
export abstract class BaseViewModel<M extends BaseModel = any> implements Displayable, Identifiable, Collection {
|
||||
public get id(): number {
|
||||
return this._model.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* force children of BaseModel to have a collectionString.
|
||||
*
|
||||
* Has a getter but no setter.
|
||||
*/
|
||||
protected _collectionString: string;
|
||||
|
||||
/**
|
||||
* returns the collectionString.
|
||||
*
|
||||
* The server and the dataStore use it to identify the collection.
|
||||
*/
|
||||
public get collectionString(): string {
|
||||
return this._collectionString;
|
||||
}
|
||||
|
||||
export abstract class BaseViewModel<M extends BaseModel = any> {
|
||||
/**
|
||||
* @returns the element id of the model
|
||||
*/
|
||||
public get elementId(): string {
|
||||
return `${this.collectionString}:${this.id}`;
|
||||
return this._model.elementId;
|
||||
}
|
||||
|
||||
public getTitle: () => string;
|
||||
public getListTitle: () => string;
|
||||
|
||||
/**
|
||||
* Returns the verbose name.
|
||||
*
|
||||
* @param plural If the name should be plural
|
||||
* @returns the verbose name of the model
|
||||
*/
|
||||
public getVerboseName: (plural?: boolean) => string;
|
||||
|
||||
/**
|
||||
* @param collectionString
|
||||
* @param model
|
||||
@ -64,8 +33,25 @@ export abstract class BaseViewModel<M extends BaseModel = any> implements Displa
|
||||
public getModel(): M {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return this.getTitle();
|
||||
}
|
||||
public toJSON(): M {
|
||||
return this.getModel();
|
||||
}
|
||||
public getUpdatedModel(update: Partial<M>): M {
|
||||
return this.getModel().getUpdatedVersion(update);
|
||||
}
|
||||
}
|
||||
export interface BaseViewModel<M extends BaseModel = any> extends Displayable, Identifiable, Collection {
|
||||
getTitle: () => string;
|
||||
getListTitle: () => string;
|
||||
|
||||
/**
|
||||
* Returns the verbose name.
|
||||
*
|
||||
* @param plural If the name should be plural
|
||||
* @returns the verbose name of the model
|
||||
*/
|
||||
getVerboseName: (plural?: boolean) => string;
|
||||
}
|
||||
|
@ -98,7 +98,7 @@
|
||||
</div>
|
||||
<mat-divider [vertical]="true"></mat-divider>
|
||||
<div *ngIf="showPreview" class="result-preview flex-1">
|
||||
<div *ngIf="!!selectedModel">
|
||||
<div *ngIf="selectedModel">
|
||||
<os-preview [viewModel]="selectedModel"> </os-preview>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -376,7 +376,7 @@ export class SuperSearchComponent implements OnInit {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
if (!!this.selectedModel) {
|
||||
if (this.selectedModel) {
|
||||
if (event.key === 'Enter') {
|
||||
this.viewResult(this.selectedModel);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { SearchRepresentation } from 'app/core/ui-services/search.service';
|
||||
import { Mediafile } from 'app/shared/models/mediafiles/mediafile';
|
||||
import { Mediafile, MediafileWithoutNestedModels } from 'app/shared/models/mediafiles/mediafile';
|
||||
import { BaseViewModelWithListOfSpeakers } from 'app/site/base/base-view-model-with-list-of-speakers';
|
||||
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
|
||||
import { Searchable } from 'app/site/base/searchable';
|
||||
@ -18,72 +18,12 @@ export class ViewMediafile extends BaseViewModelWithListOfSpeakers<Mediafile>
|
||||
public static COLLECTIONSTRING = Mediafile.COLLECTIONSTRING;
|
||||
protected _collectionString = Mediafile.COLLECTIONSTRING;
|
||||
|
||||
private _parent?: ViewMediafile;
|
||||
private _access_groups?: ViewGroup[];
|
||||
private _inherited_access_groups?: ViewGroup[];
|
||||
|
||||
public get mediafile(): Mediafile {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
public get parent(): ViewMediafile | null {
|
||||
return this._parent;
|
||||
}
|
||||
|
||||
public get access_groups(): ViewGroup[] {
|
||||
return this._access_groups || [];
|
||||
}
|
||||
|
||||
public get access_groups_id(): number[] {
|
||||
return this.mediafile.access_groups_id;
|
||||
}
|
||||
|
||||
public get inherited_access_groups(): ViewGroup[] | null {
|
||||
return this._inherited_access_groups;
|
||||
}
|
||||
|
||||
public get inherited_access_groups_id(): boolean | number[] {
|
||||
return this.mediafile.inherited_access_groups_id;
|
||||
}
|
||||
|
||||
public get has_inherited_access_groups(): boolean {
|
||||
return this.mediafile.has_inherited_access_groups;
|
||||
}
|
||||
|
||||
public get title(): string {
|
||||
return this.filename;
|
||||
}
|
||||
|
||||
public get filename(): string {
|
||||
return this.mediafile.title;
|
||||
}
|
||||
|
||||
public get path(): string {
|
||||
return this.mediafile.path;
|
||||
}
|
||||
|
||||
public get parent_id(): number {
|
||||
return this.mediafile.parent_id;
|
||||
}
|
||||
|
||||
public get is_directory(): boolean {
|
||||
return this.mediafile.is_directory;
|
||||
}
|
||||
|
||||
public get is_file(): boolean {
|
||||
return !this.is_directory;
|
||||
}
|
||||
|
||||
public get size(): string | null {
|
||||
return this.mediafile.filesize;
|
||||
}
|
||||
|
||||
public get prefix(): string {
|
||||
return this.mediafile.media_url_prefix;
|
||||
}
|
||||
|
||||
public get url(): string {
|
||||
return this.mediafile.url;
|
||||
return this.title;
|
||||
}
|
||||
|
||||
public get type(): string | null {
|
||||
@ -105,7 +45,7 @@ export class ViewMediafile extends BaseViewModelWithListOfSpeakers<Mediafile>
|
||||
{ key: 'Path', value: this.path },
|
||||
{ key: 'Type', value: type },
|
||||
{ key: 'Timestamp', value: this.timestamp },
|
||||
{ key: 'Size', value: this.size ? this.size : '0' }
|
||||
{ key: 'Size', value: this.filesize ? this.filesize : '0' }
|
||||
];
|
||||
return {
|
||||
properties,
|
||||
@ -114,6 +54,10 @@ export class ViewMediafile extends BaseViewModelWithListOfSpeakers<Mediafile>
|
||||
};
|
||||
}
|
||||
|
||||
public get url(): string {
|
||||
return this.mediafile.url;
|
||||
}
|
||||
|
||||
public getDetailStateURL(): string {
|
||||
return this.is_directory ? ('/mediafiles/files/' + this.path).slice(0, -1) : this.url;
|
||||
}
|
||||
@ -205,3 +149,9 @@ export class ViewMediafile extends BaseViewModelWithListOfSpeakers<Mediafile>
|
||||
}
|
||||
}
|
||||
}
|
||||
interface IMediafileRelations {
|
||||
parent?: ViewMediafile;
|
||||
access_groups?: ViewGroup[];
|
||||
inherited_access_groups?: ViewGroup[];
|
||||
}
|
||||
export interface ViewMediafile extends MediafileWithoutNestedModels, IMediafileRelations {}
|
||||
|
@ -27,7 +27,7 @@ export class MediafilesSortListService extends BaseSortListService<ViewMediafile
|
||||
label: this.translate.instant('Type')
|
||||
},
|
||||
{
|
||||
property: 'size',
|
||||
property: 'filesize',
|
||||
label: this.translate.instant('Size')
|
||||
}
|
||||
];
|
||||
|
@ -20,18 +20,10 @@ export class ViewCategory extends BaseViewModel<Category> implements CategoryTit
|
||||
public static COLLECTIONSTRING = Category.COLLECTIONSTRING;
|
||||
protected _collectionString = Category.COLLECTIONSTRING;
|
||||
|
||||
private _parent?: ViewCategory;
|
||||
private _children?: ViewCategory[];
|
||||
private _motions?: ViewMotion[];
|
||||
|
||||
public get category(): Category {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
public get parent(): ViewCategory | null {
|
||||
return this._parent;
|
||||
}
|
||||
|
||||
public get oldestParent(): ViewCategory {
|
||||
if (!this.parent_id) {
|
||||
return this;
|
||||
@ -40,34 +32,6 @@ export class ViewCategory extends BaseViewModel<Category> implements CategoryTit
|
||||
}
|
||||
}
|
||||
|
||||
public get children(): ViewCategory[] {
|
||||
return this._children || [];
|
||||
}
|
||||
|
||||
public get motions(): ViewMotion[] {
|
||||
return this._motions || [];
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
return this.category.name;
|
||||
}
|
||||
|
||||
public get prefix(): string {
|
||||
return this.category.prefix;
|
||||
}
|
||||
|
||||
public get weight(): number {
|
||||
return this.category.weight;
|
||||
}
|
||||
|
||||
public get parent_id(): number {
|
||||
return this.category.parent_id;
|
||||
}
|
||||
|
||||
public get level(): number {
|
||||
return this.category.level;
|
||||
}
|
||||
|
||||
public get prefixedName(): string {
|
||||
return this.prefix ? this.prefix + ' - ' + this.name : this.name;
|
||||
}
|
||||
@ -132,3 +96,9 @@ export class ViewCategory extends BaseViewModel<Category> implements CategoryTit
|
||||
}
|
||||
}
|
||||
}
|
||||
interface ICategoryRelations {
|
||||
parent?: ViewCategory;
|
||||
children?: ViewCategory[];
|
||||
motions?: ViewMotion[];
|
||||
}
|
||||
export interface ViewCategory extends Category, ICategoryRelations {}
|
||||
|
@ -33,11 +33,4 @@ export class ViewCreateMotion extends ViewMotion {
|
||||
public getVerboseName = () => {
|
||||
throw new Error('This should not be used');
|
||||
};
|
||||
|
||||
/**
|
||||
* Duplicate this motion into a copy of itself
|
||||
*/
|
||||
public copy(): ViewCreateMotion {
|
||||
return new ViewCreateMotion(this._model);
|
||||
}
|
||||
}
|
||||
|
@ -19,22 +19,9 @@ export class ViewMotionBlock extends BaseViewModelWithAgendaItemAndListOfSpeaker
|
||||
public static COLLECTIONSTRING = MotionBlock.COLLECTIONSTRING;
|
||||
protected _collectionString = MotionBlock.COLLECTIONSTRING;
|
||||
|
||||
private _motions?: ViewMotion[];
|
||||
|
||||
public get motionBlock(): MotionBlock {
|
||||
return this._model;
|
||||
}
|
||||
public get motions(): ViewMotion[] {
|
||||
return this._motions || [];
|
||||
}
|
||||
|
||||
public get title(): string {
|
||||
return this.motionBlock.title;
|
||||
}
|
||||
|
||||
public get internal(): boolean {
|
||||
return this.motionBlock.internal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the category for search
|
||||
@ -67,3 +54,8 @@ export class ViewMotionBlock extends BaseViewModelWithAgendaItemAndListOfSpeaker
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
interface IMotionBLockRelations {
|
||||
motions?: ViewMotion[];
|
||||
}
|
||||
export interface ViewMotionBlock extends MotionBlock, IMotionBLockRelations {}
|
||||
|
@ -18,69 +18,13 @@ export class ViewMotionCommentSection extends BaseViewModel<MotionCommentSection
|
||||
public static COLLECTIONSTRING = MotionCommentSection.COLLECTIONSTRING;
|
||||
protected _collectionString = MotionCommentSection.COLLECTIONSTRING;
|
||||
|
||||
private _read_groups: ViewGroup[];
|
||||
private _write_groups: ViewGroup[];
|
||||
|
||||
public get section(): MotionCommentSection {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
public get id(): number {
|
||||
return this.section.id;
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
return this.section.name;
|
||||
}
|
||||
|
||||
public get read_groups_id(): number[] {
|
||||
return this.section.read_groups_id;
|
||||
}
|
||||
|
||||
public get write_groups_id(): number[] {
|
||||
return this.section.write_groups_id;
|
||||
}
|
||||
|
||||
public get read_groups(): ViewGroup[] {
|
||||
return this._read_groups;
|
||||
}
|
||||
|
||||
public get write_groups(): ViewGroup[] {
|
||||
return this._write_groups;
|
||||
}
|
||||
|
||||
public get weight(): number {
|
||||
return this.section.weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Where is this needed? Try to avoid this.
|
||||
*/
|
||||
public set name(name: string) {
|
||||
this._model.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the local objects if required
|
||||
* @param section
|
||||
*/
|
||||
public updateDependencies(update: BaseViewModel): void {
|
||||
if (update instanceof ViewGroup) {
|
||||
if (this.section.read_groups_id.includes(update.id)) {
|
||||
const groupIndex = this.read_groups.findIndex(group => group.id === update.id);
|
||||
if (groupIndex < 0) {
|
||||
this.read_groups.push(update);
|
||||
} else {
|
||||
this.read_groups[groupIndex] = update;
|
||||
}
|
||||
} else if (this.section.write_groups_id.includes(update.id)) {
|
||||
const groupIndex = this.write_groups.findIndex(group => group.id === update.id);
|
||||
if (groupIndex < 0) {
|
||||
this.write_groups.push(update);
|
||||
} else {
|
||||
this.write_groups[groupIndex] = update;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
interface IMotionCommentSectionRelations {
|
||||
read_groups: ViewGroup[];
|
||||
write_groups: ViewGroup[];
|
||||
}
|
||||
export interface ViewMotionCommentSection extends MotionCommentSection, IMotionCommentSectionRelations {}
|
||||
|
@ -2,7 +2,7 @@ import { _ } from 'app/core/translate/translation-marker';
|
||||
import { ConfigService } from 'app/core/ui-services/config.service';
|
||||
import { DiffLinesInParagraph } from 'app/core/ui-services/diff.service';
|
||||
import { SearchProperty, SearchRepresentation } from 'app/core/ui-services/search.service';
|
||||
import { Motion, MotionComment } from 'app/shared/models/motions/motion';
|
||||
import { Motion, MotionComment, MotionWithoutNestedModels } from 'app/shared/models/motions/motion';
|
||||
import { PersonalNoteContent } from 'app/shared/models/users/personal-note';
|
||||
import { TitleInformationWithAgendaItem } from 'app/site/base/base-view-model-with-agenda-item';
|
||||
import { BaseViewModelWithAgendaItemAndListOfSpeakers } from 'app/site/base/base-view-model-with-agenda-item-and-list-of-speakers';
|
||||
@ -71,176 +71,26 @@ export class ViewMotion extends BaseViewModelWithAgendaItemAndListOfSpeakers<Mot
|
||||
return this._model;
|
||||
}
|
||||
|
||||
public get category(): ViewCategory | null {
|
||||
return this._category;
|
||||
}
|
||||
|
||||
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 || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Where is this needed. Try to avoid this..
|
||||
*/
|
||||
public set supporters(users: ViewUser[]) {
|
||||
this._supporters = users;
|
||||
this._model.supporters_id = users.map(user => user.id);
|
||||
}
|
||||
|
||||
public get motion_block(): ViewMotionBlock | null {
|
||||
return this._motion_block;
|
||||
}
|
||||
|
||||
public get attachments(): ViewMediafile[] {
|
||||
return this._attachments || [];
|
||||
}
|
||||
|
||||
public get tags(): ViewTag[] {
|
||||
return this._tags || [];
|
||||
}
|
||||
|
||||
public get parent(): ViewMotion | null {
|
||||
return this._parent;
|
||||
}
|
||||
|
||||
public get amendments(): ViewMotion[] {
|
||||
return this._amendments || [];
|
||||
}
|
||||
|
||||
public get identifier(): string {
|
||||
return this.motion.identifier;
|
||||
}
|
||||
|
||||
public get title(): string {
|
||||
return this.motion.title;
|
||||
return (this.submitters || []).map(submitter => submitter.user);
|
||||
}
|
||||
|
||||
public get identifierOrTitle(): string {
|
||||
return this.identifier ? this.identifier : this.title;
|
||||
}
|
||||
|
||||
public get text(): string {
|
||||
return this.motion.text;
|
||||
}
|
||||
|
||||
public get reason(): string {
|
||||
return this.motion.reason;
|
||||
}
|
||||
|
||||
public get modified_final_version(): string {
|
||||
return this.motion.modified_final_version;
|
||||
}
|
||||
|
||||
public set modified_final_version(value: string) {
|
||||
if (this.motion) {
|
||||
this.motion.modified_final_version = value;
|
||||
}
|
||||
}
|
||||
|
||||
public get weight(): number {
|
||||
return this.motion.weight;
|
||||
}
|
||||
|
||||
public get sort_parent_id(): number {
|
||||
return this.motion.sort_parent_id;
|
||||
}
|
||||
|
||||
public get category_id(): number {
|
||||
return this.motion.category_id;
|
||||
}
|
||||
|
||||
public get category_weight(): number {
|
||||
return this.motion.category_weight;
|
||||
}
|
||||
|
||||
public get sorted_submitters_id(): number[] {
|
||||
return this.motion.sorted_submitters_id;
|
||||
}
|
||||
|
||||
public get supporters_id(): number[] {
|
||||
return this.motion.supporters_id;
|
||||
}
|
||||
|
||||
public get workflow(): ViewWorkflow {
|
||||
return this._workflow;
|
||||
}
|
||||
|
||||
public get workflow_id(): number {
|
||||
return this.motion.workflow_id;
|
||||
}
|
||||
|
||||
public get changeRecommendations(): ViewMotionChangeRecommendation[] {
|
||||
return this._changeRecommendations;
|
||||
}
|
||||
|
||||
public get change_recommendations_id(): number[] {
|
||||
return this.motion.change_recommendations_id;
|
||||
}
|
||||
|
||||
public get state_id(): number {
|
||||
return this.motion.state_id;
|
||||
}
|
||||
|
||||
public get recommendation_id(): number {
|
||||
return this.motion.recommendation_id;
|
||||
}
|
||||
|
||||
public get statute_paragraph_id(): number {
|
||||
return this.motion.statute_paragraph_id;
|
||||
}
|
||||
|
||||
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 agenda_type(): number | null {
|
||||
return this.item ? this.item.type : null;
|
||||
}
|
||||
|
||||
public get agenda_type(): number {
|
||||
return this.agendaItem ? this.agendaItem.type : null;
|
||||
}
|
||||
|
||||
public get motion_block_id(): number {
|
||||
return this.motion.motion_block_id;
|
||||
}
|
||||
|
||||
public get speakerAmount(): number {
|
||||
public get speakerAmount(): number | null {
|
||||
return this.listOfSpeakers ? this.listOfSpeakers.waitingSpeakerAmount : null;
|
||||
}
|
||||
|
||||
public get parent_id(): number {
|
||||
return this.motion.parent_id;
|
||||
}
|
||||
|
||||
public get amendment_paragraphs(): string[] {
|
||||
return this.motion.amendment_paragraphs ? this.motion.amendment_paragraphs : [];
|
||||
}
|
||||
|
||||
public get tags_id(): number[] {
|
||||
return this.motion.tags_id;
|
||||
}
|
||||
|
||||
public get attachments_id(): number[] {
|
||||
return this.motion.attachments_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the creation date as Date object
|
||||
*/
|
||||
@ -331,20 +181,6 @@ export class ViewMotion extends BaseViewModelWithAgendaItemAndListOfSpeakers<Mot
|
||||
return this.state ? this.state.css_class : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* getter to access diff lines
|
||||
*/
|
||||
public get diffLines(): DiffLinesInParagraph[] {
|
||||
if (!this.parent_id) {
|
||||
throw new Error('No parent No diff');
|
||||
}
|
||||
return this._diffLines;
|
||||
}
|
||||
|
||||
public set diffLines(value: DiffLinesInParagraph[]) {
|
||||
this._diffLines = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a motion has a parent at all
|
||||
*/
|
||||
@ -359,20 +195,6 @@ export class ViewMotion extends BaseViewModelWithAgendaItemAndListOfSpeakers<Mot
|
||||
return !!this.amendments && !!this.amendments.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the motion has parents, is a parent of neither
|
||||
*/
|
||||
public get amendmentType(): number {
|
||||
if (this.hasAmendments) {
|
||||
return AmendmentType.Parent;
|
||||
} else if (this.hasParent) {
|
||||
return AmendmentType.Amendment;
|
||||
} else {
|
||||
// not any amendment
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of the first diff line, in case a motion is an amendment
|
||||
*/
|
||||
@ -386,19 +208,6 @@ export class ViewMotion extends BaseViewModelWithAgendaItemAndListOfSpeakers<Mot
|
||||
public static COLLECTIONSTRING = Motion.COLLECTIONSTRING;
|
||||
protected _collectionString = Motion.COLLECTIONSTRING;
|
||||
|
||||
protected _category?: ViewCategory;
|
||||
protected _submitters?: ViewSubmitter[];
|
||||
protected _supporters?: ViewUser[];
|
||||
protected _workflow?: ViewWorkflow;
|
||||
protected _state?: ViewState;
|
||||
protected _recommendation?: ViewState;
|
||||
protected _motion_block?: ViewMotionBlock;
|
||||
protected _attachments?: ViewMediafile[];
|
||||
protected _tags?: ViewTag[];
|
||||
protected _parent?: ViewMotion;
|
||||
protected _amendments?: ViewMotion[];
|
||||
protected _changeRecommendations?: ViewMotionChangeRecommendation[];
|
||||
protected _diffLines?: DiffLinesInParagraph[];
|
||||
public personalNote?: PersonalNoteContent;
|
||||
|
||||
// This is set by the repository
|
||||
@ -412,7 +221,7 @@ export class ViewMotion extends BaseViewModelWithAgendaItemAndListOfSpeakers<Mot
|
||||
* @return The lines of the amendment
|
||||
*/
|
||||
public getChangeLines(): string {
|
||||
if (!!this.diffLines) {
|
||||
if (this.diffLines) {
|
||||
return this.diffLines
|
||||
.map(diffLine => {
|
||||
if (diffLine.diffLineTo === diffLine.diffLineFrom + 1) {
|
||||
@ -521,7 +330,7 @@ export class ViewMotion extends BaseViewModelWithAgendaItemAndListOfSpeakers<Mot
|
||||
* specified by amendment_paragraphs-array
|
||||
*/
|
||||
public isParagraphBasedAmendment(): boolean {
|
||||
return this.amendment_paragraphs.length > 0;
|
||||
return this.amendment_paragraphs && this.amendment_paragraphs.length > 0;
|
||||
}
|
||||
|
||||
public getSlide(configService: ConfigService): ProjectorElementBuildDeskriptor {
|
||||
@ -554,11 +363,22 @@ export class ViewMotion extends BaseViewModelWithAgendaItemAndListOfSpeakers<Mot
|
||||
getDialogTitle: this.getAgendaSlideTitle
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate this motion into a copy of itself
|
||||
*/
|
||||
public copy(): ViewMotion {
|
||||
return new ViewMotion(this._model);
|
||||
}
|
||||
interface TIMotionRelations {
|
||||
category?: ViewCategory;
|
||||
submitters: ViewSubmitter[];
|
||||
supporters?: ViewUser[];
|
||||
workflow?: ViewWorkflow;
|
||||
state?: ViewState;
|
||||
recommendation?: ViewState;
|
||||
motion_block?: ViewMotionBlock;
|
||||
attachments?: ViewMediafile[];
|
||||
tags?: ViewTag[];
|
||||
parent?: ViewMotion;
|
||||
amendments?: ViewMotion[];
|
||||
changeRecommendations?: ViewMotionChangeRecommendation[];
|
||||
diffLines?: DiffLinesInParagraph[];
|
||||
}
|
||||
|
||||
export interface ViewMotion extends MotionWithoutNestedModels, TIMotionRelations {}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { MergeAmendment, State } from 'app/shared/models/motions/state';
|
||||
import { State } from 'app/shared/models/motions/state';
|
||||
import { BaseViewModel } from '../../base/base-view-model';
|
||||
import { ViewWorkflow } from './view-workflow';
|
||||
|
||||
@ -14,83 +14,22 @@ export class ViewState extends BaseViewModel<State> implements StateTitleInforma
|
||||
public static COLLECTIONSTRING = State.COLLECTIONSTRING;
|
||||
protected _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;
|
||||
return (
|
||||
!this.next_states_id ||
|
||||
!this.next_states_id.length ||
|
||||
(this.next_states_id.length === 1 && this.next_states_id[0] === 0)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public get previous_states(): ViewState[] {
|
||||
if (!this.workflow) {
|
||||
return [];
|
||||
}
|
||||
return this.workflow.states.filter(state => {
|
||||
return state.next_states_id.includes(this.id);
|
||||
});
|
||||
}
|
||||
interface IStateRelations {
|
||||
next_states?: ViewState[];
|
||||
previous_states?: ViewState[];
|
||||
workflow?: ViewWorkflow;
|
||||
}
|
||||
export interface ViewState extends State, IStateRelations {}
|
||||
|
@ -23,18 +23,6 @@ export class ViewStatuteParagraph extends BaseViewModel<StatuteParagraph>
|
||||
return this._model;
|
||||
}
|
||||
|
||||
public get title(): string {
|
||||
return this.statuteParagraph.title;
|
||||
}
|
||||
|
||||
public get text(): string {
|
||||
return this.statuteParagraph.text;
|
||||
}
|
||||
|
||||
public get weight(): number {
|
||||
return this.statuteParagraph.weight;
|
||||
}
|
||||
|
||||
public formatForSearch(): SearchRepresentation {
|
||||
return { properties: [{ key: 'Title', value: this.getTitle() }], searchValue: [this.getTitle()] };
|
||||
}
|
||||
@ -43,3 +31,4 @@ export class ViewStatuteParagraph extends BaseViewModel<StatuteParagraph>
|
||||
return '/motions/statute-paragraphs';
|
||||
}
|
||||
}
|
||||
export interface ViewStatuteParagraph extends StatuteParagraph {}
|
||||
|
@ -6,37 +6,15 @@ export class ViewSubmitter extends BaseViewModel<Submitter> {
|
||||
public static COLLECTIONSTRING = Submitter.COLLECTIONSTRING;
|
||||
protected _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 getTitle = () => {
|
||||
return this.user ? this.user.getTitle() : '';
|
||||
};
|
||||
|
||||
public getListTitle = () => {
|
||||
return this.getTitle();
|
||||
};
|
||||
}
|
||||
interface ISubmitterRelations {
|
||||
user: ViewUser;
|
||||
}
|
||||
export interface ViewSubmitter extends Submitter, ISubmitterRelations {}
|
||||
|
@ -14,30 +14,12 @@ export class ViewWorkflow extends BaseViewModel<Workflow> implements WorkflowTit
|
||||
public static COLLECTIONSTRING = Workflow.COLLECTIONSTRING;
|
||||
protected _collectionString = Workflow.COLLECTIONSTRING;
|
||||
|
||||
private _states?: ViewState[];
|
||||
private _first_state?: ViewState;
|
||||
|
||||
public get workflow(): Workflow {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
return this.workflow.name;
|
||||
}
|
||||
|
||||
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 first_state(): ViewState | null {
|
||||
return this._first_state;
|
||||
}
|
||||
interface IWorkflowRelations {
|
||||
states?: ViewState[];
|
||||
first_state?: ViewState;
|
||||
}
|
||||
export interface ViewWorkflow extends Workflow, IWorkflowRelations {}
|
||||
|
@ -110,7 +110,7 @@ export class AmendmentListComponent extends BaseListViewComponent<ViewMotion> im
|
||||
|
||||
public ngOnInit(): void {
|
||||
// determine if a paramter exists.
|
||||
if (!!this.route.snapshot.paramMap.get('id')) {
|
||||
if (this.route.snapshot.paramMap.get('id')) {
|
||||
// set the parentMotion observable. This will "only" fire
|
||||
// if there is a subscription to the parent motion
|
||||
this.parentMotion = this.route.paramMap.pipe(
|
||||
@ -133,7 +133,7 @@ export class AmendmentListComponent extends BaseListViewComponent<ViewMotion> im
|
||||
*/
|
||||
public getAmendmentSummary(amendment: ViewMotion): string {
|
||||
const diffLines = amendment.diffLines;
|
||||
if (!!diffLines) {
|
||||
if (diffLines) {
|
||||
return diffLines
|
||||
.map(diffLine => {
|
||||
return this.linenumberingService.stripLineNumbers(diffLine.text);
|
||||
|
@ -220,7 +220,7 @@ export class CategoryDetailComponent extends BaseViewComponent implements OnInit
|
||||
const title = this.translate.instant('Are you sure you want to renumber all motions of this category?');
|
||||
const content = this.selectedCategory.getTitle();
|
||||
if (await this.promptService.open(title, content)) {
|
||||
await this.repo.numberMotionsInCategory(this.selectedCategory).then(null, this.raiseError);
|
||||
await this.repo.numberMotionsInCategory(this.selectedCategory).catch(this.raiseError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -118,11 +118,11 @@
|
||||
<os-projector-button *ngIf="block" [object]="block" [menuItem]="true"></os-projector-button>
|
||||
|
||||
<div *osPerms="'agenda.can_manage'">
|
||||
<button mat-menu-item (click)="addToAgenda()" *ngIf="block && !block.agendaItem">
|
||||
<button mat-menu-item (click)="addToAgenda()" *ngIf="block && !block.item">
|
||||
<mat-icon>add</mat-icon>
|
||||
<span translate>Add to agenda</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="removeFromAgenda()" *ngIf="block && block.agendaItem">
|
||||
<button mat-menu-item (click)="removeFromAgenda()" *ngIf="block && block.item">
|
||||
<mat-icon>remove</mat-icon>
|
||||
<span translate>Remove from agenda</span>
|
||||
</button>
|
||||
|
@ -254,10 +254,10 @@ export class MotionBlockDetailComponent extends BaseListViewComponent<ViewMotion
|
||||
}
|
||||
|
||||
public addToAgenda(): void {
|
||||
this.itemRepo.addItemToAgenda(this.block).then(null, this.raiseError);
|
||||
this.itemRepo.addItemToAgenda(this.block).catch(this.raiseError);
|
||||
}
|
||||
|
||||
public removeFromAgenda(): void {
|
||||
this.itemRepo.removeFromAgenda(this.block.agendaItem).then(null, this.raiseError);
|
||||
this.itemRepo.removeFromAgenda(this.block.item).catch(this.raiseError);
|
||||
}
|
||||
}
|
||||
|
@ -53,6 +53,6 @@ export class MotionCommentSectionSortComponent extends BaseViewComponent impleme
|
||||
* @param commentsInOrder
|
||||
*/
|
||||
public onSortingChange(commentsInOrder: ViewMotionCommentSection[]): void {
|
||||
this.repo.sortCommentSections(commentsInOrder).then(null, this.raiseError);
|
||||
this.repo.sortCommentSections(commentsInOrder).catch(this.raiseError);
|
||||
}
|
||||
}
|
||||
|
@ -282,7 +282,7 @@ export class MotionDetailDiffComponent extends BaseViewComponent implements Afte
|
||||
* @param {boolean} internal
|
||||
*/
|
||||
public setInternal(change: ViewMotionChangeRecommendation, internal: boolean): void {
|
||||
this.recoRepo.setInternal(change, internal).then(null, this.raiseError);
|
||||
this.recoRepo.setInternal(change, internal).catch(this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -297,7 +297,7 @@ export class MotionDetailDiffComponent extends BaseViewComponent implements Afte
|
||||
$event.preventDefault();
|
||||
const title = this.translate.instant('Are you sure you want to delete this change recommendation?');
|
||||
if (await this.promptService.open(title)) {
|
||||
this.recoRepo.delete(reco).then(null, this.raiseError);
|
||||
this.recoRepo.delete(reco).catch(this.raiseError);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,11 +73,11 @@
|
||||
></os-projector-button>
|
||||
<!-- Add/remove to/from agenda -->
|
||||
<div *osPerms="'agenda.can_manage'">
|
||||
<button mat-menu-item (click)="addToAgenda()" *ngIf="motion && !motion.agendaItem">
|
||||
<button mat-menu-item (click)="addToAgenda()" *ngIf="motion && !motion.item">
|
||||
<mat-icon>add</mat-icon>
|
||||
<span translate>Add to agenda</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="removeFromAgenda()" *ngIf="motion && motion.agendaItem">
|
||||
<button mat-menu-item (click)="removeFromAgenda()" *ngIf="motion && motion.item">
|
||||
<mat-icon>remove</mat-icon>
|
||||
<span translate>Remove from agenda</span>
|
||||
</button>
|
||||
@ -614,7 +614,6 @@
|
||||
autofocus
|
||||
placeholder="{{ 'Identifier' | translate }}"
|
||||
formControlName="identifier"
|
||||
[value]="motionCopy.identifier || ''"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@ -626,7 +625,6 @@
|
||||
matInput
|
||||
placeholder="{{ 'Title' | translate }}"
|
||||
formControlName="title"
|
||||
[value]="motionCopy.title"
|
||||
required
|
||||
/>
|
||||
<mat-error>{{ 'The title is required' | translate }}</mat-error>
|
||||
@ -844,7 +842,6 @@
|
||||
matInput
|
||||
placeholder="{{ 'Origin' | translate }}"
|
||||
formControlName="origin"
|
||||
[value]="motionCopy.origin"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
@ -203,11 +203,6 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
*/
|
||||
public amendmentsEnabled: boolean;
|
||||
|
||||
/**
|
||||
* Copy of the motion that the user might edit
|
||||
*/
|
||||
public motionCopy: ViewMotion;
|
||||
|
||||
/**
|
||||
* All change recommendations to this motion
|
||||
*/
|
||||
@ -645,12 +640,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
// new motion
|
||||
this.newMotion = true;
|
||||
this.editMotion = true;
|
||||
// prevent 'undefined' to appear in the ui
|
||||
const defaultMotion: Partial<CreateMotion> = {
|
||||
title: '',
|
||||
origin: '',
|
||||
identifier: ''
|
||||
};
|
||||
const defaultMotion: Partial<CreateMotion> = {};
|
||||
if (this.route.snapshot.queryParams.parent) {
|
||||
this.amendmentEdit = true;
|
||||
const parentMotion = this.repo.getViewModel(this.route.snapshot.queryParams.parent);
|
||||
@ -676,7 +666,6 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
}
|
||||
}
|
||||
this.motion = new ViewCreateMotion(new CreateMotion(defaultMotion));
|
||||
this.motionCopy = new ViewCreateMotion(new CreateMotion(defaultMotion));
|
||||
}
|
||||
}
|
||||
|
||||
@ -836,7 +825,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
*/
|
||||
private updateMotionFromForm(): void {
|
||||
const newMotionValues = { ...this.contentForm.value };
|
||||
this.updateMotion(newMotionValues, this.motionCopy).then(() => {
|
||||
this.updateMotion(newMotionValues, this.motion).then(() => {
|
||||
this.editMotion = false;
|
||||
this.amendmentEdit = false;
|
||||
}, this.raiseError);
|
||||
@ -1155,8 +1144,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
public setEditMode(mode: boolean): void {
|
||||
this.editMotion = mode;
|
||||
if (mode) {
|
||||
this.motionCopy = this.motion.copy();
|
||||
this.patchForm(this.motionCopy);
|
||||
this.patchForm(this.motion);
|
||||
this.editNotificationSubscription = this.listenToEditNotification();
|
||||
this.sendEditNotification(MotionEditNotificationType.TYPE_BEGIN_EDITING_MOTION);
|
||||
}
|
||||
@ -1247,14 +1235,14 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
* Supports the motion (as requested user)
|
||||
*/
|
||||
public support(): void {
|
||||
this.repo.support(this.motion).then(null, this.raiseError);
|
||||
this.repo.support(this.motion).catch(this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsupports the motion
|
||||
*/
|
||||
public unsupport(): void {
|
||||
this.repo.unsupport(this.motion).then(null, this.raiseError);
|
||||
this.repo.unsupport(this.motion).catch(this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1271,7 +1259,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
* @param id Motion state id
|
||||
*/
|
||||
public setState(id: number): void {
|
||||
this.repo.setState(this.motion, id).then(null, this.raiseError);
|
||||
this.repo.setState(this.motion, id).catch(this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1489,7 +1477,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
* @param unsubscriptionReason The reason for the unsubscription.
|
||||
*/
|
||||
private unsubscribeEditNotifications(unsubscriptionReason: MotionEditNotificationType): void {
|
||||
if (!!this.editNotificationSubscription && !this.editNotificationSubscription.closed) {
|
||||
if (this.editNotificationSubscription && !this.editNotificationSubscription.closed) {
|
||||
this.sendEditNotification(unsubscriptionReason);
|
||||
this.closeSnackBar();
|
||||
this.editNotificationSubscription.unsubscribe();
|
||||
@ -1529,7 +1517,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
} else {
|
||||
this.motion.personalNote.star = !this.motion.personalNote.star;
|
||||
}
|
||||
this.personalNoteService.savePersonalNote(this.motion, this.motion.personalNote).then(null, this.raiseError);
|
||||
this.personalNoteService.savePersonalNote(this.motion, this.motion.personalNote).catch(this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1616,10 +1604,10 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
}
|
||||
|
||||
public addToAgenda(): void {
|
||||
this.itemRepo.addItemToAgenda(this.motion).then(null, this.raiseError);
|
||||
this.itemRepo.addItemToAgenda(this.motion).catch(this.raiseError);
|
||||
}
|
||||
|
||||
public removeFromAgenda(): void {
|
||||
this.itemRepo.removeFromAgenda(this.motion.agendaItem).then(null, this.raiseError);
|
||||
this.itemRepo.removeFromAgenda(this.motion.item).catch(this.raiseError);
|
||||
}
|
||||
}
|
||||
|
@ -289,7 +289,7 @@ export class MotionListComponent extends BaseListViewComponent<ViewMotion> imple
|
||||
const localCategories = new Set<ViewCategory>();
|
||||
|
||||
for (const motion of motions) {
|
||||
if (!motion.category_id) {
|
||||
if (!motion.category) {
|
||||
motionsWithoutCategory++;
|
||||
} else {
|
||||
localCategories.add(motion.category.oldestParent);
|
||||
@ -429,7 +429,7 @@ export class MotionListComponent extends BaseListViewComponent<ViewMotion> imple
|
||||
};
|
||||
// TODO: "only update if different" was another repo-todo
|
||||
if (!Object.keys(partialUpdate).every(key => partialUpdate[key] === undefined)) {
|
||||
await this.motionRepo.update(partialUpdate, motion).then(null, this.raiseError);
|
||||
await this.motionRepo.update(partialUpdate, motion).catch(this.raiseError);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -177,7 +177,7 @@ export class MotionExportDialogComponent implements OnInit {
|
||||
* @param nextState The next state the button will assume.
|
||||
*/
|
||||
private changeStateOfButton(button: MatButtonToggle, nextState: boolean): void {
|
||||
if (!!button) {
|
||||
if (button) {
|
||||
button.disabled = nextState;
|
||||
}
|
||||
}
|
||||
@ -245,7 +245,7 @@ export class MotionExportDialogComponent implements OnInit {
|
||||
|
||||
// restore selection or set default
|
||||
this.store.get<MotionExportInfo>('motion_export_selection').then(restored => {
|
||||
if (!!restored) {
|
||||
if (restored) {
|
||||
this.exportForm.patchValue(restored);
|
||||
} else {
|
||||
this.exportForm.patchValue(this.defaults);
|
||||
|
@ -90,7 +90,7 @@ export class AmendmentFilterListService extends MotionFilterListService {
|
||||
* Function to define a new storage key by parent id
|
||||
*/
|
||||
private updateStorageKey(): void {
|
||||
if (!!this._parentMotionId) {
|
||||
if (this._parentMotionId) {
|
||||
this.storageKey = `${this.keyPrefix}_parentId_${this._parentMotionId}`;
|
||||
} else {
|
||||
this.storageKey = this.keyPrefix;
|
||||
@ -104,7 +104,7 @@ export class AmendmentFilterListService extends MotionFilterListService {
|
||||
*/
|
||||
protected preFilter(motions: ViewMotion[]): ViewMotion[] {
|
||||
return motions.filter(motion => {
|
||||
if (!!this._parentMotionId) {
|
||||
if (this._parentMotionId) {
|
||||
return motion.parent_id === this._parentMotionId;
|
||||
} else {
|
||||
return !!motion.parent_id;
|
||||
|
@ -46,7 +46,7 @@ export class MotionExportService {
|
||||
if (!exportInfo) {
|
||||
return;
|
||||
}
|
||||
if (!!exportInfo.format) {
|
||||
if (exportInfo.format) {
|
||||
if (exportInfo.format === ExportFileFormat.PDF) {
|
||||
try {
|
||||
this.pdfExport.exportMotionCatalog(data, exportInfo);
|
||||
|
@ -201,7 +201,7 @@ export class MotionMultiselectService {
|
||||
let requestData = null;
|
||||
if (selectedChoice.action === choices[0]) {
|
||||
requestData = motions.map(motion => {
|
||||
let submitterIds = [...motion.sorted_submitters_id, ...(selectedChoice.items as number[])];
|
||||
let submitterIds = [...motion.sorted_submitter_ids, ...(selectedChoice.items as number[])];
|
||||
submitterIds = submitterIds.filter((id, index, self) => self.indexOf(id) === index); // remove duplicates
|
||||
return {
|
||||
id: motion.id,
|
||||
@ -211,7 +211,7 @@ export class MotionMultiselectService {
|
||||
} else if (selectedChoice.action === choices[1]) {
|
||||
requestData = motions.map(motion => {
|
||||
const submitterIdsToRemove = selectedChoice.items as number[];
|
||||
const submitterIds = motion.sorted_submitters_id.filter(id => !submitterIdsToRemove.includes(id));
|
||||
const submitterIds = motion.sorted_submitter_ids.filter(id => !submitterIdsToRemove.includes(id));
|
||||
return {
|
||||
id: motion.id,
|
||||
submitters: submitterIds
|
||||
|
@ -312,7 +312,7 @@ export class MotionPdfService {
|
||||
// category
|
||||
if (motion.category && (!infoToExport || infoToExport.includes('category'))) {
|
||||
let categoryText = '';
|
||||
if (!!motion.category.parent) {
|
||||
if (motion.category.parent) {
|
||||
categoryText = `${motion.category.parent.toString()}\n${this.translate.instant(
|
||||
'Subcategory'
|
||||
)}: ${motion.category.toString()}`;
|
||||
|
@ -147,11 +147,11 @@ export class MotionXlsxExportService {
|
||||
data.push(
|
||||
...properties.map(property => {
|
||||
const motionProp = motion[property];
|
||||
if (property === 'speakers') {
|
||||
/*if (property === 'speakers') {
|
||||
return motion.listOfSpeakers && motion.listOfSpeakers.waitingSpeakerAmount > 0
|
||||
? motion.listOfSpeakers.waitingSpeakerAmount
|
||||
: '';
|
||||
}
|
||||
}*/
|
||||
if (!motionProp) {
|
||||
return '';
|
||||
}
|
||||
|
@ -139,6 +139,6 @@ export class PresentationControlComponent extends BaseViewComponent {
|
||||
|
||||
private updateElement(element: MediafileProjectorElement): void {
|
||||
const idElement = this.slideManager.getIdentifialbeProjectorElement(element);
|
||||
this.projectorService.updateElement(this.projector.projector, idElement).then(null, this.raiseError);
|
||||
this.projectorService.updateElement(this.projector.projector, idElement).catch(this.raiseError);
|
||||
}
|
||||
}
|
||||
|
@ -125,7 +125,7 @@ export class ProjectorDetailComponent extends BaseViewComponent implements OnIni
|
||||
* @param step (optional) The amount of steps to make.
|
||||
*/
|
||||
public scroll(direction: ScrollScaleDirection, step: number = 1): void {
|
||||
this.repo.scroll(this.projector, direction, step).then(null, this.raiseError);
|
||||
this.repo.scroll(this.projector, direction, step).catch(this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -135,29 +135,29 @@ export class ProjectorDetailComponent extends BaseViewComponent implements OnIni
|
||||
* @param step (optional) The amount of steps to make.
|
||||
*/
|
||||
public scale(direction: ScrollScaleDirection, step: number = 1): void {
|
||||
this.repo.scale(this.projector, direction, step).then(null, this.raiseError);
|
||||
this.repo.scale(this.projector, direction, step).catch(this.raiseError);
|
||||
}
|
||||
|
||||
public projectNextSlide(): void {
|
||||
this.projectorService.projectNextSlide(this.projector.projector).then(null, this.raiseError);
|
||||
this.projectorService.projectNextSlide(this.projector.projector).catch(this.raiseError);
|
||||
}
|
||||
|
||||
public projectPreviousSlide(): void {
|
||||
this.projectorService.projectPreviousSlide(this.projector.projector).then(null, this.raiseError);
|
||||
this.projectorService.projectPreviousSlide(this.projector.projector).catch(this.raiseError);
|
||||
}
|
||||
|
||||
public onSortingChange(event: CdkDragDrop<ProjectorElement>): void {
|
||||
moveItemInArray(this.projector.elements_preview, event.previousIndex, event.currentIndex);
|
||||
this.projectorService.savePreview(this.projector.projector).then(null, this.raiseError);
|
||||
this.projectorService.savePreview(this.projector.projector).catch(this.raiseError);
|
||||
}
|
||||
|
||||
public removePreviewElement(elementIndex: number): void {
|
||||
this.projector.elements_preview.splice(elementIndex, 1);
|
||||
this.projectorService.savePreview(this.projector.projector).then(null, this.raiseError);
|
||||
this.projectorService.savePreview(this.projector.projector).catch(this.raiseError);
|
||||
}
|
||||
|
||||
public projectNow(elementIndex: number): void {
|
||||
this.projectorService.projectPreviewSlide(this.projector.projector, elementIndex).then(null, this.raiseError);
|
||||
this.projectorService.projectPreviewSlide(this.projector.projector, elementIndex).catch(this.raiseError);
|
||||
}
|
||||
|
||||
public getSlideTitle(element: ProjectorElement): string {
|
||||
@ -182,7 +182,7 @@ export class ProjectorDetailComponent extends BaseViewComponent implements OnIni
|
||||
|
||||
public unprojectCurrent(element: ProjectorElement): void {
|
||||
const idElement = this.slideManager.getIdentifialbeProjectorElement(element);
|
||||
this.projectorService.removeFrom(this.projector.projector, idElement).then(null, this.raiseError);
|
||||
this.projectorService.removeFrom(this.projector.projector, idElement).catch(this.raiseError);
|
||||
}
|
||||
|
||||
public isClosProjected(stable: boolean): boolean {
|
||||
|
@ -244,7 +244,7 @@ export class ProjectorListEntryComponent extends BaseViewComponent implements On
|
||||
public async onDeleteButton(): Promise<void> {
|
||||
const title = this.translate.instant('Are you sure you want to delete this projector?');
|
||||
if (await this.promptService.open(title, this.projector.name)) {
|
||||
this.repo.delete(this.projector).then(null, this.raiseError);
|
||||
this.repo.delete(this.projector).catch(this.raiseError);
|
||||
}
|
||||
}
|
||||
|
||||
@ -262,7 +262,7 @@ export class ProjectorListEntryComponent extends BaseViewComponent implements On
|
||||
width: width
|
||||
};
|
||||
updateProjector.height = Math.round(width / aspectRatios[aspectRatioKey]);
|
||||
this.repo.update(updateProjector, this.projector).then(null, this.raiseError);
|
||||
this.repo.update(updateProjector, this.projector).catch(this.raiseError);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5,7 +5,7 @@
|
||||
</div>
|
||||
</os-head-bar>
|
||||
|
||||
<mat-card *ngIf="!projectorToCreate && projectors && projectors.length > 1">
|
||||
<mat-card *ngIf="!showCreateForm && projectors && projectors.length > 1">
|
||||
<span translate> Reference projector for current list of speakers: </span>
|
||||
<mat-form-field>
|
||||
<mat-select
|
||||
@ -20,7 +20,7 @@
|
||||
</mat-form-field>
|
||||
</mat-card>
|
||||
|
||||
<mat-card *ngIf="projectorToCreate">
|
||||
<mat-card *ngIf="showCreateForm">
|
||||
<mat-card-title translate>New Projector</mat-card-title>
|
||||
<mat-card-content>
|
||||
<form [formGroup]="createForm" (keydown)="keyDownFunction($event)">
|
||||
@ -38,7 +38,7 @@
|
||||
<button mat-button (click)="create()">
|
||||
<span translate>Create</span>
|
||||
</button>
|
||||
<button mat-button (click)="projectorToCreate = null">
|
||||
<button mat-button (click)="showCreateForm = false">
|
||||
<span translate>Cancel</span>
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
|
@ -35,7 +35,7 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit,
|
||||
/**
|
||||
* This member is set, if the user is creating a new projector.
|
||||
*/
|
||||
public projectorToCreate: Projector | null;
|
||||
public showCreateForm = false;
|
||||
|
||||
/**
|
||||
* The create form.
|
||||
@ -126,8 +126,8 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit,
|
||||
* Opens the create form.
|
||||
*/
|
||||
public onPlusButton(): void {
|
||||
if (!this.projectorToCreate) {
|
||||
this.projectorToCreate = new Projector();
|
||||
if (!this.showCreateForm) {
|
||||
this.showCreateForm = true;
|
||||
this.createForm.setValue({ name: '' });
|
||||
}
|
||||
}
|
||||
@ -136,13 +136,13 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit,
|
||||
* Creates the comment section from the create form.
|
||||
*/
|
||||
public create(): void {
|
||||
if (this.createForm.valid && this.projectorToCreate) {
|
||||
this.projectorToCreate.patchValues(this.createForm.value as Projector);
|
||||
this.projectorToCreate.patchValues({
|
||||
if (this.createForm.valid && this.showCreateForm) {
|
||||
const projector: Partial<Projector> = {
|
||||
name: this.createForm.value.name,
|
||||
reference_projector_id: this.projectors[0].reference_projector_id
|
||||
});
|
||||
this.repo.create(this.projectorToCreate).then(() => {
|
||||
this.projectorToCreate = null;
|
||||
};
|
||||
this.repo.create(projector).then(() => {
|
||||
this.showCreateForm = false;
|
||||
this.cd.detectChanges();
|
||||
}, this.raiseError);
|
||||
}
|
||||
@ -158,7 +158,7 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit,
|
||||
this.create();
|
||||
}
|
||||
if (event.key === 'Escape') {
|
||||
this.projectorToCreate = null;
|
||||
this.showCreateForm = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,6 +169,6 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit,
|
||||
const promises = this.projectors.map(projector => {
|
||||
return this.repo.update(update, projector);
|
||||
});
|
||||
Promise.all(promises).then(null, this.raiseError);
|
||||
Promise.all(promises).catch(this.raiseError);
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user