Merge pull request #4411 from MaximilianKrambach/sort_alternative

fix sort performance issues for dropdowns
This commit is contained in:
Sean 2019-03-15 09:58:32 +01:00 committed by GitHub
commit 398ffc30ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 326 additions and 231 deletions

View File

@ -2,6 +2,8 @@ import { Injectable } from '@angular/core';
import { tap, map } from 'rxjs/operators'; import { tap, map } from 'rxjs/operators';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { TranslateService } from '@ngx-translate/core';
import { BaseAgendaContentObjectRepository } from '../base-agenda-content-object-repository'; import { BaseAgendaContentObjectRepository } from '../base-agenda-content-object-repository';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
import { BaseAgendaViewModel } from 'app/site/base/base-agenda-view-model'; import { BaseAgendaViewModel } from 'app/site/base/base-agenda-view-model';
@ -13,7 +15,6 @@ import { DataStoreService } from '../../core-services/data-store.service';
import { HttpService } from 'app/core/core-services/http.service'; import { HttpService } from 'app/core/core-services/http.service';
import { Item } from 'app/shared/models/agenda/item'; import { Item } from 'app/shared/models/agenda/item';
import { OSTreeSortEvent } from 'app/shared/components/sorting-tree/sorting-tree.component'; import { OSTreeSortEvent } from 'app/shared/components/sorting-tree/sorting-tree.component';
import { TranslateService } from '@ngx-translate/core';
import { TreeService } from 'app/core/ui-services/tree.service'; import { TreeService } from 'app/core/ui-services/tree.service';
import { ViewItem } from 'app/site/agenda/models/view-item'; import { ViewItem } from 'app/site/agenda/models/view-item';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
@ -39,15 +40,15 @@ export class ItemRepositoryService extends BaseRepository<ViewItem, Item> {
*/ */
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
dataSend: DataSendService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService, viewModelStoreService: ViewModelStoreService,
protected dataSend: DataSendService, translate: TranslateService,
private httpService: HttpService, private httpService: HttpService,
private config: ConfigService, private config: ConfigService,
private treeService: TreeService, private treeService: TreeService
private translate: TranslateService
) { ) {
super(DS, dataSend, mapperService, viewModelStoreService, Item); super(DS, dataSend, mapperService, viewModelStoreService, translate, Item);
} }
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {
@ -85,7 +86,7 @@ export class ItemRepositoryService extends BaseRepository<ViewItem, Item> {
const repo = this.collectionStringMapperService.getRepository( const repo = this.collectionStringMapperService.getRepository(
viewItem.item.content_object.collection viewItem.item.content_object.collection
) as BaseAgendaContentObjectRepository<any, any>; ) as BaseAgendaContentObjectRepository<any, any>;
return numberPrefix + repo.getAgendaTitleWithType(viewItem); return numberPrefix + repo.getAgendaTitleWithType(viewItem.title_information);
} }
}; };
viewItem.getListTitle = viewItem.getTitle; viewItem.getListTitle = viewItem.getTitle;

View File

@ -30,12 +30,12 @@ export class TopicRepositoryService extends BaseAgendaContentObjectRepository<Vi
*/ */
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
dataSend: DataSendService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService, viewModelStoreService: ViewModelStoreService,
protected dataSend: DataSendService, translate: TranslateService
private translate: TranslateService
) { ) {
super(DS, dataSend, mapperService, viewModelStoreService, Topic, [Mediafile, Item]); super(DS, dataSend, mapperService, viewModelStoreService, translate, Topic, [Mediafile, Item]);
} }
public getAgendaTitle = (topic: Partial<Topic> | Partial<ViewTopic>) => { public getAgendaTitle = (topic: Partial<Topic> | Partial<ViewTopic>) => {

View File

@ -36,9 +36,9 @@ export class AssignmentRepositoryService extends BaseAgendaContentObjectReposito
dataSend: DataSendService, dataSend: DataSendService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService, viewModelStoreService: ViewModelStoreService,
private translate: TranslateService translate: TranslateService
) { ) {
super(DS, dataSend, mapperService, viewModelStoreService, Assignment, [User, Item, Tag]); super(DS, dataSend, mapperService, viewModelStoreService, translate, Assignment, [User, Item, Tag]);
} }
public getAgendaTitle = (assignment: Partial<Assignment> | Partial<ViewAssignment>) => { public getAgendaTitle = (assignment: Partial<Assignment> | Partial<ViewAssignment>) => {

View File

@ -1,3 +1,5 @@
import { TranslateService } from '@ngx-translate/core';
import { BaseViewModel } from '../../site/base/base-view-model'; import { BaseViewModel } from '../../site/base/base-view-model';
import { BaseModel, ModelConstructor } from '../../shared/models/base/base-model'; import { BaseModel, ModelConstructor } from '../../shared/models/base/base-model';
import { CollectionStringMapperService } from '../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../core-services/collectionStringMapper.service';
@ -25,9 +27,18 @@ export abstract class BaseAgendaContentObjectRepository<
dataSend: DataSendService, dataSend: DataSendService,
collectionStringMapperService: CollectionStringMapperService, collectionStringMapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService, viewModelStoreService: ViewModelStoreService,
translate: TranslateService,
baseModelCtor: ModelConstructor<M>, baseModelCtor: ModelConstructor<M>,
depsModelCtors?: ModelConstructor<BaseModel>[] depsModelCtors?: ModelConstructor<BaseModel>[]
) { ) {
super(DS, dataSend, collectionStringMapperService, viewModelStoreService, baseModelCtor, depsModelCtors); super(
DS,
dataSend,
collectionStringMapperService,
viewModelStoreService,
translate,
baseModelCtor,
depsModelCtors
);
} }
} }

View File

@ -1,4 +1,5 @@
import { BehaviorSubject, Observable, Subject } from 'rxjs'; import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { TranslateService } from '@ngx-translate/core';
import { BaseViewModel } from '../../site/base/base-view-model'; import { BaseViewModel } from '../../site/base/base-view-model';
import { BaseModel, ModelConstructor } from '../../shared/models/base/base-model'; import { BaseModel, ModelConstructor } from '../../shared/models/base/base-model';
@ -24,17 +25,35 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
protected viewModelSubjects: { [modelId: number]: BehaviorSubject<V> } = {}; protected viewModelSubjects: { [modelId: number]: BehaviorSubject<V> } = {};
/** /**
* Observable subject for the whole list * Observable subject for the whole list. These entries are unsorted an not piped through
* autodTime. Just use this internally.
*
* It's used to debounce messages on the sortedViewModelListSubject
*/ */
protected readonly viewModelListSubject: BehaviorSubject<V[]> = new BehaviorSubject<V[]>([]); private readonly viewModelListSubject: BehaviorSubject<V[]> = new BehaviorSubject<V[]>([]);
protected readonly viewModelListAuditSubject: BehaviorSubject<V[]> = new BehaviorSubject<V[]>([]); /**
* Observable subject for the sorted view model list.
*
* All data is piped through an auditTime of 1ms. This is to prevent massive
* updates, if e.g. an autoupdate with a lot motions come in. The result is just one
* update of the new list instead of many unnecessary updates.
*/
protected readonly sortedViewModelListSubject: BehaviorSubject<V[]> = new BehaviorSubject<V[]>([]);
/** /**
* Observable subject for any changes of view models. * Observable subject for any changes of view models.
*/ */
protected readonly generalViewModelSubject: Subject<V> = new Subject<V>(); protected readonly generalViewModelSubject: Subject<V> = new Subject<V>();
/**
* Can be used by the sort functions.
*/
protected languageCollator: Intl.Collator;
/**
* The collection string of the managed model.
*/
private _collectionString: string; private _collectionString: string;
public get collectionString(): string { public get collectionString(): string {
@ -65,16 +84,28 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
protected dataSend: DataSendService, protected dataSend: DataSendService,
protected collectionStringMapperService: CollectionStringMapperService, protected collectionStringMapperService: CollectionStringMapperService,
protected viewModelStoreService: ViewModelStoreService, protected viewModelStoreService: ViewModelStoreService,
protected translate: TranslateService,
protected baseModelCtor: ModelConstructor<M>, protected baseModelCtor: ModelConstructor<M>,
protected depsModelCtors?: ModelConstructor<BaseModel>[] protected depsModelCtors?: ModelConstructor<BaseModel>[]
) { ) {
this._collectionString = baseModelCtor.COLLECTIONSTRING; this._collectionString = baseModelCtor.COLLECTIONSTRING;
this.getViewModelListObservable().subscribe(x => this.viewModelListAuditSubject.next(x)); // All data is piped through an auditTime of 1ms. This is to prevent massive
// updates, if e.g. an autoupdate with a lot motions come in. The result is just one
// update of the new list instead of many unnecessary updates.
this.viewModelListSubject.pipe(auditTime(1)).subscribe(models => {
this.sortedViewModelListSubject.next(models.sort(this.viewModelSortFn));
});
this.languageCollator = new Intl.Collator(this.translate.currentLang);
} }
public onAfterAppsLoaded(): void { public onAfterAppsLoaded(): void {
this.DS.clearObservable.subscribe(() => this.clear()); this.DS.clearObservable.subscribe(() => this.clear());
this.translate.onLangChange.subscribe(change => {
this.languageCollator = new Intl.Collator(change.lang);
this.updateViewModelListObservable();
});
// Populate the local viewModelStore with ViewModel Objects. // Populate the local viewModelStore with ViewModel Objects.
this.DS.getAll(this.baseModelCtor).forEach((model: M) => { this.DS.getAll(this.baseModelCtor).forEach((model: M) => {
@ -202,6 +233,19 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
*/ */
protected clear(): void { protected clear(): void {
this.viewModelStore = {}; this.viewModelStore = {};
}
/**
* The function used for sorting the data of this repository. The defualt sorts by ID.
*/
protected viewModelSortFn: (a: V, b: V) => number = (a: V, b: V) => a.id - b.id;
/**
* Setter for a sort function. Updates the sorting.
*
* @param fn a sort function
*/
public setSortFunction(fn: (a: V, b: V) => number): void {
this.viewModelSortFn = fn;
this.updateViewModelListObservable(); this.updateViewModelListObservable();
} }
@ -213,14 +257,24 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
} }
/** /**
* helper function to return the viewModel as array * @returns all view models stored in this repository. Sorting is not guaranteed
*/ */
public getViewModelList(): V[] { public getViewModelList(): V[] {
return Object.values(this.viewModelStore); return Object.values(this.viewModelStore);
} }
/** /**
* returns the current observable for one viewModel * Get a sorted ViewModelList. This passes through a (1ms short) delay,
* thus may not be accurate, especially on application loading.
*
* @returns all sorted view models stored in this repository.
*/
public getSortedViewModelList(): V[] {
return this.sortedViewModelListSubject.getValue();
}
/**
* @returns the current observable for one viewModel
*/ */
public getViewModelObservable(id: number): Observable<V> { public getViewModelObservable(id: number): Observable<V> {
if (!this.viewModelSubjects[id]) { if (!this.viewModelSubjects[id]) {
@ -230,14 +284,10 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
} }
/** /**
* Return the Observable of the whole store. * @returns the (sorted) Observable of the whole store.
*
* All data is piped through an auditTime of 1ms. This is to prevent massive
* updates, if e.g. an autoupdate with a lot motions come in. The result is just one
* update of the new list instead of many unnecessary updates.
*/ */
public getViewModelListObservable(): Observable<V[]> { public getViewModelListObservable(): Observable<V[]> {
return this.viewModelListSubject.asObservable().pipe(auditTime(1)); return this.sortedViewModelListSubject.asObservable();
} }
/** /**
@ -247,7 +297,7 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
* @returns A subject that holds the model list * @returns A subject that holds the model list
*/ */
public getViewModelListBehaviorSubject(): BehaviorSubject<V[]> { public getViewModelListBehaviorSubject(): BehaviorSubject<V[]> {
return this.viewModelListAuditSubject; return this.sortedViewModelListSubject;
} }
/** /**
@ -268,7 +318,7 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
} }
/** /**
* update the observable of the list * update the observable of the list. Also updates the sorting of the view model list.
*/ */
protected updateViewModelListObservable(): void { protected updateViewModelListObservable(): void {
this.viewModelListSubject.next(this.getViewModelList()); this.viewModelListSubject.next(this.getViewModelList());

View File

@ -17,9 +17,9 @@ export class ChatMessageRepositoryService extends BaseRepository<ViewChatMessage
dataSend: DataSendService, dataSend: DataSendService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService, viewModelStoreService: ViewModelStoreService,
private translate: TranslateService translate: TranslateService
) { ) {
super(DS, dataSend, mapperService, viewModelStoreService, ChatMessage); super(DS, dataSend, mapperService, viewModelStoreService, translate, ChatMessage);
} }
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {

View File

@ -100,11 +100,11 @@ export class ConfigRepositoryService extends BaseRepository<ViewConfig, Config>
dataSend: DataSendService, dataSend: DataSendService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService, viewModelStoreService: ViewModelStoreService,
translate: TranslateService,
private constantsService: ConstantsService, private constantsService: ConstantsService,
private http: HttpService, private http: HttpService
private translate: TranslateService
) { ) {
super(DS, dataSend, mapperService, viewModelStoreService, Config); super(DS, dataSend, mapperService, viewModelStoreService, translate, Config);
this.constantsService.get('OpenSlidesConfigVariables').subscribe(constant => { this.constantsService.get('OpenSlidesConfigVariables').subscribe(constant => {
this.createConfigStructure(constant); this.createConfigStructure(constant);

View File

@ -35,11 +35,11 @@ export class HistoryRepositoryService extends BaseRepository<ViewHistory, Histor
dataSend: DataSendService, dataSend: DataSendService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService, viewModelStoreService: ViewModelStoreService,
translate: TranslateService,
private httpService: HttpService, private httpService: HttpService,
private timeTravel: TimeTravelService, private timeTravel: TimeTravelService
private translate: TranslateService
) { ) {
super(DS, dataSend, mapperService, viewModelStoreService, History, [User]); super(DS, dataSend, mapperService, viewModelStoreService, translate, History, [User]);
} }
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {

View File

@ -32,11 +32,12 @@ export class MediafileRepositoryService extends BaseRepository<ViewMediafile, Me
DS: DataStoreService, DS: DataStoreService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService, viewModelStoreService: ViewModelStoreService,
translate: TranslateService,
protected dataSend: DataSendService, protected dataSend: DataSendService,
private httpService: HttpService, private httpService: HttpService
private translate: TranslateService
) { ) {
super(DS, dataSend, mapperService, viewModelStoreService, Mediafile, [User]); super(DS, dataSend, mapperService, viewModelStoreService, translate, Mediafile, [User]);
this.initSorting();
} }
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {
@ -69,4 +70,13 @@ export class MediafileRepositoryService extends BaseRepository<ViewMediafile, Me
const emptyHeader = new HttpHeaders(); const emptyHeader = new HttpHeaders();
return this.httpService.post<Identifiable>(restPath, file, {}, emptyHeader); return this.httpService.post<Identifiable>(restPath, file, {}, emptyHeader);
} }
/**
* Sets the default sorting (e.g. in dropdowns and for new users) to 'title'
*/
private initSorting(): void {
this.setSortFunction((a: ViewMediafile, b: ViewMediafile) => {
return this.languageCollator.compare(a.title, b.title);
});
}
} }

View File

@ -1,6 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
@ -13,6 +12,8 @@ import { HttpService } from '../../core-services/http.service';
import { ViewCategory } from 'app/site/motions/models/view-category'; import { ViewCategory } from 'app/site/motions/models/view-category';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
type SortProperty = 'prefix' | 'name';
/** /**
* Repository Services for Categories * Repository Services for Categories
* *
@ -27,6 +28,8 @@ import { ViewModelStoreService } from 'app/core/core-services/view-model-store.s
providedIn: 'root' providedIn: 'root'
}) })
export class CategoryRepositoryService extends BaseRepository<ViewCategory, Category> { export class CategoryRepositoryService extends BaseRepository<ViewCategory, Category> {
private sortProperty: SortProperty;
/** /**
* Creates a CategoryRepository * Creates a CategoryRepository
* Converts existing and incoming category to ViewCategories * Converts existing and incoming category to ViewCategories
@ -40,15 +43,21 @@ export class CategoryRepositoryService extends BaseRepository<ViewCategory, Cate
* @param translate translationService to get the currently selected locale * @param translate translationService to get the currently selected locale
*/ */
public constructor( public constructor(
protected DS: DataStoreService, DS: DataStoreService,
dataSend: DataSendService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService, viewModelStoreService: ViewModelStoreService,
protected dataSend: DataSendService, translate: TranslateService,
private httpService: HttpService, private httpService: HttpService,
private configService: ConfigService, private configService: ConfigService
private translate: TranslateService
) { ) {
super(DS, dataSend, mapperService, viewModelStoreService, Category); super(DS, dataSend, mapperService, viewModelStoreService, translate, Category);
this.sortProperty = this.configService.instant('motions_category_sorting');
this.configService.get<SortProperty>('motions_category_sorting').subscribe(conf => {
this.sortProperty = conf;
this.setConfigSortFn();
});
} }
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {
@ -66,8 +75,7 @@ export class CategoryRepositoryService extends BaseRepository<ViewCategory, Cate
* @param category_id category ID * @param category_id category ID
*/ */
public getCategoryByID(category_id: number): Category { public getCategoryByID(category_id: number): Category {
const catList = this.DS.getAll(Category); return this.DS.find<Category>(Category, cat => cat.id === category_id);
return catList.find(category => category.id === category_id);
} }
/** /**
@ -81,36 +89,19 @@ export class CategoryRepositoryService extends BaseRepository<ViewCategory, Cate
} }
/** /**
* @ returns the observable for categories sorted according to configuration * Triggers an update for the sort function responsible for the default sorting of data items
*/ */
public getSortedViewModelListObservable(): Observable<ViewCategory[]> { public setConfigSortFn(): void {
const subject = new BehaviorSubject<ViewCategory[]>([]); this.setSortFunction((a: ViewCategory, b: ViewCategory) => {
this.getViewModelListObservable().subscribe(categories => { if (a[this.sortProperty] && b[this.sortProperty]) {
subject.next(this.sortViewCategoriesByConfig(categories)); return this.languageCollator.compare(a[this.sortProperty], b[this.sortProperty]);
}); } else if (this.sortProperty === 'prefix') {
return subject.asObservable();
}
/**
* Sort viewCategories by the configured settings
*
* @param categories
* @returns the categories sorted by prefix or name, according to the config setting
*
* TODO: That operation is HEAVY
*/
public sortViewCategoriesByConfig(categories: ViewCategory[]): ViewCategory[] {
const sort = this.configService.instant<'prefix' | 'name'>('motions_category_sorting') || 'prefix';
return categories.sort((a, b) => {
if (a[sort] && b[sort]) {
return a[sort].localeCompare(b[sort], this.translate.currentLang);
} else if (sort === 'prefix') {
if (a.prefix) { if (a.prefix) {
return 1; return 1;
} else if (b.prefix) { } else if (b.prefix) {
return -1; return -1;
} else { } else {
return a.name.localeCompare(b.name); return this.languageCollator.compare(a.name, b.name);
} }
} }
}); });

View File

@ -45,12 +45,12 @@ export class ChangeRecommendationRepositoryService extends BaseRepository<
*/ */
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
dataSend: DataSendService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService, viewModelStoreService: ViewModelStoreService,
protected dataSend: DataSendService, translate: TranslateService
private translate: TranslateService
) { ) {
super(DS, dataSend, mapperService, viewModelStoreService, MotionChangeRecommendation, [ super(DS, dataSend, mapperService, viewModelStoreService, translate, MotionChangeRecommendation, [
Category, Category,
User, User,
Workflow Workflow
@ -85,7 +85,7 @@ export class ChangeRecommendationRepositoryService extends BaseRepository<
* return the Observable of all change recommendations belonging to the given motion * return the Observable of all change recommendations belonging to the given motion
*/ */
public getChangeRecosOfMotionObservable(motion_id: number): Observable<ViewMotionChangeRecommendation[]> { public getChangeRecosOfMotionObservable(motion_id: number): Observable<ViewMotionChangeRecommendation[]> {
return this.viewModelListSubject.asObservable().pipe( return this.getViewModelListObservable().pipe(
map((recos: ViewMotionChangeRecommendation[]) => { map((recos: ViewMotionChangeRecommendation[]) => {
return recos.filter(reco => reco.motion_id === motion_id); return recos.filter(reco => reco.motion_id === motion_id);
}) })

View File

@ -36,14 +36,14 @@ export class MotionBlockRepositoryService extends BaseAgendaContentObjectReposit
*/ */
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
dataSend: DataSendService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService, viewModelStoreService: ViewModelStoreService,
protected dataSend: DataSendService, translate: TranslateService,
private motionRepo: MotionRepositoryService, private motionRepo: MotionRepositoryService,
private httpService: HttpService, private httpService: HttpService
private translate: TranslateService
) { ) {
super(DS, dataSend, mapperService, viewModelStoreService, MotionBlock, [Item]); super(DS, dataSend, mapperService, viewModelStoreService, translate, MotionBlock, [Item]);
} }
public getAgendaTitle = (motionBlock: Partial<MotionBlock> | Partial<ViewMotionBlock>) => { public getAgendaTitle = (motionBlock: Partial<MotionBlock> | Partial<ViewMotionBlock>) => {

View File

@ -42,13 +42,13 @@ export class MotionCommentSectionRepositoryService extends BaseRepository<
*/ */
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
dataSend: DataSendService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService, viewModelStoreService: ViewModelStoreService,
protected dataSend: DataSendService, translate: TranslateService,
private http: HttpService, private http: HttpService
private translate: TranslateService
) { ) {
super(DS, dataSend, mapperService, viewModelStoreService, MotionCommentSection, [Group]); super(DS, dataSend, mapperService, viewModelStoreService, translate, MotionCommentSection, [Group]);
} }
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {

View File

@ -7,6 +7,8 @@ import { tap, map } from 'rxjs/operators';
import { Category } from 'app/shared/models/motions/category'; import { Category } from 'app/shared/models/motions/category';
import { ChangeRecoMode, ViewMotion } from 'app/site/motions/models/view-motion'; import { ChangeRecoMode, ViewMotion } from 'app/site/motions/models/view-motion';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service';
import { ConfigService } from 'app/core/ui-services/config.service';
import { DataSendService } from '../../core-services/data-send.service'; import { DataSendService } from '../../core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';
import { DiffLinesInParagraph, DiffService, LineRange, ModificationType } from '../../ui-services/diff.service'; import { DiffLinesInParagraph, DiffService, LineRange, ModificationType } from '../../ui-services/diff.service';
@ -42,6 +44,8 @@ import { PersonalNote, PersonalNoteContent } from 'app/shared/models/users/perso
import { ViewPersonalNote } from 'app/site/users/models/view-personal-note'; import { ViewPersonalNote } from 'app/site/users/models/view-personal-note';
import { OperatorService } from 'app/core/core-services/operator.service'; import { OperatorService } from 'app/core/core-services/operator.service';
type SortProperty = 'callListWeight' | 'identifier';
/** /**
* Repository Services for motions (and potentially categories) * Repository Services for motions (and potentially categories)
* *
@ -56,6 +60,11 @@ import { OperatorService } from 'app/core/core-services/operator.service';
providedIn: 'root' providedIn: 'root'
}) })
export class MotionRepositoryService extends BaseAgendaContentObjectRepository<ViewMotion, Motion> { export class MotionRepositoryService extends BaseAgendaContentObjectRepository<ViewMotion, Motion> {
/**
* The property the incoming data is sorted by
*/
protected sortProperty: SortProperty;
/** /**
* Creates a MotionRepository * Creates a MotionRepository
* *
@ -68,20 +77,23 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository<V
* @param httpService OpenSlides own Http service * @param httpService OpenSlides own Http service
* @param lineNumbering Line numbering for motion text * @param lineNumbering Line numbering for motion text
* @param diff Display changes in motion text as diff. * @param diff Display changes in motion text as diff.
* @param personalNoteService service fo personal notes
* @param config ConfigService (subscribe to sorting config)
*/ */
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
dataSend: DataSendService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService, viewModelStoreService: ViewModelStoreService,
protected dataSend: DataSendService, translate: TranslateService,
config: ConfigService,
private httpService: HttpService, private httpService: HttpService,
private readonly lineNumbering: LinenumberingService, private readonly lineNumbering: LinenumberingService,
private readonly diff: DiffService, private readonly diff: DiffService,
private treeService: TreeService, private treeService: TreeService,
private translate: TranslateService,
private operator: OperatorService private operator: OperatorService
) { ) {
super(DS, dataSend, mapperService, viewModelStoreService, Motion, [ super(DS, dataSend, mapperService, viewModelStoreService, translate, Motion, [
Category, Category,
User, User,
Workflow, Workflow,
@ -92,6 +104,10 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository<V
MotionChangeRecommendation, MotionChangeRecommendation,
PersonalNote PersonalNote
]); ]);
config.get<SortProperty>('motions_motions_sorting').subscribe(conf => {
this.sortProperty = conf;
this.setConfigSortFn();
});
} }
public getTitle = (motion: Partial<Motion> | Partial<ViewMotion>) => { public getTitle = (motion: Partial<Motion> | Partial<ViewMotion>) => {
@ -839,4 +855,30 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository<V
} }
}); });
} }
/**
* Triggers an update for the sort function responsible for the default sorting of data items
*/
public setConfigSortFn(): void {
this.setSortFunction((a: ViewMotion, b: ViewMotion) => {
if (a[this.sortProperty] && b[this.sortProperty]) {
if (a[this.sortProperty] === b[this.sortProperty]) {
return this.languageCollator.compare(a.title, b.title);
} else {
if (this.sortProperty === 'callListWeight') {
// handling numerical values
return a.callListWeight - b.callListWeight;
} else {
return this.languageCollator.compare(a[this.sortProperty], b[this.sortProperty]);
}
}
} else if (a[this.sortProperty]) {
return -1;
} else if (b[this.sortProperty]) {
return 1;
} else {
return this.languageCollator.compare(a.title, b.title);
}
});
}
} }

View File

@ -31,12 +31,12 @@ export class StatuteParagraphRepositoryService extends BaseRepository<ViewStatut
*/ */
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
dataSend: DataSendService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService, viewModelStoreService: ViewModelStoreService,
protected dataSend: DataSendService, translate: TranslateService
private translate: TranslateService
) { ) {
super(DS, dataSend, mapperService, viewModelStoreService, StatuteParagraph); super(DS, dataSend, mapperService, viewModelStoreService, translate, StatuteParagraph);
} }
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {

View File

@ -1,5 +1,4 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { auditTime } from 'rxjs/operators';
import { Workflow } from 'app/shared/models/motions/workflow'; import { Workflow } from 'app/shared/models/motions/workflow';
import { ViewWorkflow } from 'app/site/motions/models/view-workflow'; import { ViewWorkflow } from 'app/site/motions/models/view-workflow';
@ -43,14 +42,15 @@ export class WorkflowRepositoryService extends BaseRepository<ViewWorkflow, Work
*/ */
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
dataSend: DataSendService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService, viewModelStoreService: ViewModelStoreService,
protected dataSend: DataSendService, translate: TranslateService,
private httpService: HttpService, private httpService: HttpService
private translate: TranslateService
) { ) {
super(DS, dataSend, mapperService, viewModelStoreService, Workflow); super(DS, dataSend, mapperService, viewModelStoreService, translate, Workflow);
this.viewModelListSubject.pipe(auditTime(1)).subscribe(models => {
this.sortedViewModelListSubject.subscribe(models => {
if (models && models.length > 0) { if (models && models.length > 0) {
this.initSorting(models); this.initSorting(models);
} }

View File

@ -15,13 +15,13 @@ import { ServertimeService } from 'app/core/core-services/servertime.service';
export class CountdownRepositoryService extends BaseRepository<ViewCountdown, Countdown> { export class CountdownRepositoryService extends BaseRepository<ViewCountdown, Countdown> {
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
dataSend: DataSendService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService, viewModelStoreService: ViewModelStoreService,
protected dataSend: DataSendService, translate: TranslateService,
private translate: TranslateService,
private servertimeService: ServertimeService private servertimeService: ServertimeService
) { ) {
super(DS, dataSend, mapperService, viewModelStoreService, Countdown); super(DS, dataSend, mapperService, viewModelStoreService, translate, Countdown);
} }
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {

View File

@ -14,12 +14,12 @@ import { DataSendService } from 'app/core/core-services/data-send.service';
export class ProjectorMessageRepositoryService extends BaseRepository<ViewProjectorMessage, ProjectorMessage> { export class ProjectorMessageRepositoryService extends BaseRepository<ViewProjectorMessage, ProjectorMessage> {
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
dataSend: DataSendService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService, viewModelStoreService: ViewModelStoreService,
protected dataSend: DataSendService, translate: TranslateService
private translate: TranslateService
) { ) {
super(DS, dataSend, mapperService, viewModelStoreService, ProjectorMessage); super(DS, dataSend, mapperService, viewModelStoreService, translate, ProjectorMessage);
} }
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {

View File

@ -37,13 +37,13 @@ export class ProjectorRepositoryService extends BaseRepository<ViewProjector, Pr
*/ */
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
dataSend: DataSendService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService, viewModelStoreService: ViewModelStoreService,
protected dataSend: DataSendService, translate: TranslateService,
private http: HttpService, private http: HttpService
private translate: TranslateService
) { ) {
super(DS, dataSend, mapperService, viewModelStoreService, Projector, [Projector]); super(DS, dataSend, mapperService, viewModelStoreService, translate, Projector, [Projector]);
} }
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {

View File

@ -34,12 +34,12 @@ export class TagRepositoryService extends BaseRepository<ViewTag, Tag> {
*/ */
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
dataSend: DataSendService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService, viewModelStoreService: ViewModelStoreService,
protected dataSend: DataSendService, translate: TranslateService
private translate: TranslateService
) { ) {
super(DS, dataSend, mapperService, viewModelStoreService, Tag); super(DS, dataSend, mapperService, viewModelStoreService, translate, Tag);
} }
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {

View File

@ -49,13 +49,13 @@ export class GroupRepositoryService extends BaseRepository<ViewGroup, Group> {
*/ */
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
dataSend: DataSendService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService, viewModelStoreService: ViewModelStoreService,
protected dataSend: DataSendService, translate: TranslateService,
private constants: ConstantsService, private constants: ConstantsService
private translate: TranslateService
) { ) {
super(DS, dataSend, mapperService, viewModelStoreService, Group); super(DS, dataSend, mapperService, viewModelStoreService, translate, Group);
this.sortPermsPerApp(); this.sortPermsPerApp();
} }

View File

@ -1,13 +1,14 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { DataSendService } from 'app/core/core-services/data-send.service';
import { DataStoreService } from '../../core-services/data-store.service'; import { DataStoreService } from '../../core-services/data-store.service';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service'; import { CollectionStringMapperService } from '../../core-services/collectionStringMapper.service';
import { PersonalNote } from 'app/shared/models/users/personal-note'; import { PersonalNote } from 'app/shared/models/users/personal-note';
import { ViewPersonalNote } from 'app/site/users/models/view-personal-note'; import { ViewPersonalNote } from 'app/site/users/models/view-personal-note';
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service'; import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
import { TranslateService } from '@ngx-translate/core';
import { DataSendService } from 'app/core/core-services/data-send.service';
/** /**
*/ */
@ -24,9 +25,9 @@ export class PersonalNoteRepositoryService extends BaseRepository<ViewPersonalNo
dataSend: DataSendService, dataSend: DataSendService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService, viewModelStoreService: ViewModelStoreService,
private translate: TranslateService translate: TranslateService
) { ) {
super(DS, dataSend, mapperService, viewModelStoreService, PersonalNote); super(DS, dataSend, mapperService, viewModelStoreService, translate, PersonalNote);
} }
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable, BehaviorSubject } from 'rxjs';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BaseRepository } from '../base-repository'; import { BaseRepository } from '../base-repository';
@ -22,6 +22,8 @@ import { ViewModelStoreService } from 'app/core/core-services/view-model-store.s
*/ */
type StringNamingSchema = 'lastCommaFirst' | 'firstSpaceLast'; type StringNamingSchema = 'lastCommaFirst' | 'firstSpaceLast';
type SortProperty = 'first_name' | 'last_name' | 'number';
/** /**
* Repository service for users * Repository service for users
* *
@ -31,23 +33,36 @@ type StringNamingSchema = 'lastCommaFirst' | 'firstSpaceLast';
providedIn: 'root' providedIn: 'root'
}) })
export class UserRepositoryService extends BaseRepository<ViewUser, User> { export class UserRepositoryService extends BaseRepository<ViewUser, User> {
/**
* The property the incoming data is sorted by
*/
protected sortProperty: SortProperty;
/** /**
* Constructor for the user repo * Constructor for the user repo
* *
* @param DS The DataStore * @param DS The DataStore
* @param mapperService Maps collection strings to classes * @param mapperService Maps collection strings to classes
* @param dataSend sending changed objects * @param dataSend sending changed objects
* @param translate
* @param httpService
* @param configService
*/ */
public constructor( public constructor(
DS: DataStoreService, DS: DataStoreService,
dataSend: DataSendService,
mapperService: CollectionStringMapperService, mapperService: CollectionStringMapperService,
viewModelStoreService: ViewModelStoreService, viewModelStoreService: ViewModelStoreService,
protected dataSend: DataSendService, translate: TranslateService,
private translate: TranslateService,
private httpService: HttpService, private httpService: HttpService,
private configService: ConfigService private configService: ConfigService
) { ) {
super(DS, dataSend, mapperService, viewModelStoreService, User, [Group]); super(DS, dataSend, mapperService, viewModelStoreService, translate, User, [Group]);
this.sortProperty = this.configService.instant('users_sort_by');
this.configService.get<SortProperty>('users_sort_by').subscribe(conf => {
this.sortProperty = conf;
this.setConfigSortFn();
});
} }
public getVerboseName = (plural: boolean = false) => { public getVerboseName = (plural: boolean = false) => {
@ -219,7 +234,7 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {
* Searches and returns Users by full name * Searches and returns Users by full name
* *
* @param name * @param name
* @returns all users matching that name * @returns all users matching that name (unsorted)
*/ */
public getUsersByName(name: string): ViewUser[] { public getUsersByName(name: string): ViewUser[] {
return this.getViewModelList().filter(user => { return this.getViewModelList().filter(user => {
@ -301,43 +316,22 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {
} }
/** /**
* @returns the observable for users sorted according to configuration * Triggers an update for the sort function responsible for the default sorting of data items
*
* TODO: This is leading to heavy operations
*/ */
public getSortedViewModelListObservable(): Observable<ViewUser[]> { public setConfigSortFn(): void {
const subject = new BehaviorSubject<ViewUser[]>([]); this.setSortFunction((a: ViewUser, b: ViewUser) => {
this.getViewModelListObservable().subscribe(users => { if (a[this.sortProperty] && b[this.sortProperty]) {
subject.next(this.sortViewUsersByConfig(users)); if (a[this.sortProperty] === b[this.sortProperty]) {
}); return this.languageCollator.compare(a.short_name, b.short_name);
return subject.asObservable();
}
/**
* Sort viewUsers by the configured settings
*
* @param users
* @returns the users sorted by first name, last name or number, according
* to the config setting. Fallthrough and identical cases will be sorted by
* 'short_name'
*
* TODO: That operation is HEAVY
*/
public sortViewUsersByConfig(users: ViewUser[]): ViewUser[] {
const sort = this.configService.instant<'first_name' | 'last_name' | 'number'>('users_sort_by') || 'last_name';
return users.sort((a, b) => {
if (a[sort] && b[sort]) {
if (a[sort] === b[sort]) {
return a.short_name.localeCompare(b.short_name, this.translate.currentLang);
} else { } else {
return a[sort].localeCompare(b[sort], this.translate.currentLang); return this.languageCollator.compare(a[this.sortProperty], b[this.sortProperty]);
} }
} else if (a[sort] && !b[sort]) { } else if (a[this.sortProperty] && !b[this.sortProperty]) {
return -1; return -1;
} else if (b[sort]) { } else if (b[this.sortProperty]) {
return 1; return 1;
} else { } else {
return a.short_name.localeCompare(b.short_name); return this.languageCollator.compare(a.short_name, b.short_name);
} }
}); });
} }

View File

@ -118,7 +118,7 @@ export abstract class BaseFilterListService<M extends BaseModel, V extends BaseV
public filter(): Observable<V[]> { public filter(): Observable<V[]> {
this.repo this.repo
.getViewModelListObservable() .getViewModelListObservable()
.pipe(auditTime(100)) .pipe(auditTime(10))
.subscribe(data => { .subscribe(data => {
this.currentRawData = data; this.currentRawData = data;
this.filteredData = this.filterData(data); this.filteredData = this.filterData(data);

View File

@ -50,6 +50,11 @@ export abstract class BaseSortListService<V extends BaseViewModel> {
*/ */
private sortFn: (a: V, b: V) => number; private sortFn: (a: V, b: V) => number;
/**
* default sorting to use if the client was not initialized before
*/
protected defaultSorting: keyof V = 'id';
/** /**
* Constructor. Does nothing. TranslateService is used for localeCompeare. * Constructor. Does nothing. TranslateService is used for localeCompeare.
*/ */
@ -113,7 +118,7 @@ export abstract class BaseSortListService<V extends BaseViewModel> {
* get the property of the viewModel the sorting is based on. * get the property of the viewModel the sorting is based on.
*/ */
public get sortProperty(): string { public get sortProperty(): string {
return this.sortOptions.sortProperty as string; return this.sortOptions ? (this.sortOptions.sortProperty as string) : '';
} }
public get isActive(): boolean { public get isActive(): boolean {
@ -138,7 +143,7 @@ export abstract class BaseSortListService<V extends BaseViewModel> {
* @param option * @param option
*/ */
public getSortIcon(option: OsSortingItem<V>): string { public getSortIcon(option: OsSortingItem<V>): string {
if (this.sortProperty !== (option.property as string)) { if (!this.sortProperty || this.sortProperty !== (option.property as string)) {
return ''; return '';
} }
return this.ascending ? 'arrow_downward' : 'arrow_upward'; return this.ascending ? 'arrow_downward' : 'arrow_upward';
@ -156,20 +161,21 @@ export abstract class BaseSortListService<V extends BaseViewModel> {
* Retrieve the currently saved sorting definition from the borwser's * Retrieve the currently saved sorting definition from the borwser's
* store * store
*/ */
private loadStorageDefinition(): void { private async loadStorageDefinition(): Promise<void> {
const me = this; const sorting: OsSortingDefinition<V> | null = await this.store.get('sorting_' + this.name);
this.store.get('sorting_' + this.name).then(function(sorting: OsSortingDefinition<V> | null): void {
if (sorting) { if (sorting) {
if (sorting.sortProperty) { if (sorting.sortProperty) {
me.sortOptions.sortProperty = sorting.sortProperty; this.sortOptions.sortProperty = sorting.sortProperty;
if (sorting.sortAscending !== undefined) { if (sorting.sortAscending !== undefined) {
me.sortOptions.sortAscending = sorting.sortAscending; this.sortOptions.sortAscending = sorting.sortAscending;
} }
} }
} else {
this.sortOptions.sortProperty = this.defaultSorting;
this.sortOptions.sortAscending = true;
} }
me.updateSortFn(); this.updateSortFn();
me.doAsyncSorting(); this.doAsyncSorting();
});
} }
/** /**
@ -197,10 +203,10 @@ export abstract class BaseSortListService<V extends BaseViewModel> {
* Recreates the sorting function. Is supposed to be called on init and * Recreates the sorting function. Is supposed to be called on init and
* every time the sorting (property, ascending/descending) or the language changes * every time the sorting (property, ascending/descending) or the language changes
*/ */
private updateSortFn(): void { protected updateSortFn(): void {
const property = this.sortProperty as string; const property = this.sortProperty as string;
const ascending = this.ascending; const ascending = this.ascending;
const lang = this.translate.currentLang; // TODO: observe and update sorting on change const intl = new Intl.Collator(this.translate.currentLang); // TODO: observe and update sorting on language change
this.sortFn = function(itemA: V, itemB: V): number { this.sortFn = function(itemA: V, itemB: V): number {
const firstProperty = ascending ? itemA[property] : itemB[property]; const firstProperty = ascending ? itemA[property] : itemB[property];
@ -234,16 +240,16 @@ export abstract class BaseSortListService<V extends BaseViewModel> {
if (!firstProperty) { if (!firstProperty) {
return 1; return 1;
} }
return firstProperty.localeCompare(secondProperty, lang); return intl.compare(firstProperty, secondProperty);
case 'function': case 'function':
const a = firstProperty(); const a = firstProperty();
const b = secondProperty(); const b = secondProperty();
return a.localeCompare(b, lang); return intl.compare(a, b);
case 'object': case 'object':
if (firstProperty instanceof Date) { if (firstProperty instanceof Date) {
return firstProperty > secondProperty ? 1 : -1; return firstProperty > secondProperty ? 1 : -1;
} else { } else {
return firstProperty.toString().localeCompare(secondProperty.toString(), lang); return intl.compare(firstProperty.toString(), secondProperty.toString());
} }
case 'undefined': case 'undefined':
return 1; return 1;

View File

@ -140,7 +140,7 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
this.addSpeakerForm = new FormGroup({ user_id: new FormControl([]) }); this.addSpeakerForm = new FormGroup({ user_id: new FormControl([]) });
if (this.currentListOfSpeakers) { if (this.currentListOfSpeakers) {
this.projectors = projectorRepo.getViewModelList(); this.projectors = projectorRepo.getSortedViewModelList();
this.updateClosProjector(); this.updateClosProjector();
projectorRepo.getViewModelListObservable().subscribe(newProjectors => { projectorRepo.getViewModelListObservable().subscribe(newProjectors => {
this.projectors = newProjectors; this.projectors = newProjectors;
@ -160,11 +160,6 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
public ngOnInit(): void { public ngOnInit(): void {
// load and observe users // load and observe users
this.users = this.userRepository.getViewModelListBehaviorSubject(); this.users = this.userRepository.getViewModelListBehaviorSubject();
this.userRepository.getViewModelListBehaviorSubject().subscribe(newUsers => {
if (this.viewItem) {
this.setSpeakerList(this.viewItem.id);
}
});
// detect changes in the form // detect changes in the form
this.addSpeakerForm.valueChanges.subscribe(formResult => { this.addSpeakerForm.valueChanges.subscribe(formResult => {

View File

@ -13,13 +13,13 @@ export class ViewItem extends BaseViewModel {
/** /**
* virtual weight defined by the order in the agenda tree, representing a shortcut to sorting by * virtual weight defined by the order in the agenda tree, representing a shortcut to sorting by
* weight, parent_id and the parents' weight(s) * weight, parent_id and the parents' weight(s)
* TODO will be accurate if the viewMotion is observed via {@link getViewModelListObservable}, else, it will be undefined * TODO will be accurate if the viewMotion is observed via {@link getSortedViewModelListObservable}, else, it will be undefined
*/ */
public agendaListWeight: number; public agendaListWeight: number;
/** /**
* The amount of parents in the agenda list tree. * The amount of parents in the agenda list tree.
* TODO will be accurate if the viewMotion is observed via {@link getViewModelListObservable}, else, it will be undefined * TODO will be accurate if the viewMotion is observed via {@link getSortedViewModelListObservable}, else, it will be undefined
*/ */
public agendaListLevel: number; public agendaListLevel: number;
@ -39,6 +39,10 @@ export class ViewItem extends BaseViewModel {
return this.item.item_number; return this.item.item_number;
} }
public get title_information(): object {
return this.item.title_information;
}
public get duration(): number { public get duration(): number {
return this.item.duration; return this.item.duration;
} }

View File

@ -98,9 +98,13 @@ export abstract class ListViewBaseComponent<V extends BaseViewModel, M extends B
* Standard filtering function. Sufficient for most list views but can be overwritten * Standard filtering function. Sufficient for most list views but can be overwritten
*/ */
protected onFilter(): void { protected onFilter(): void {
if (this.sortService) {
this.subscriptions.push( this.subscriptions.push(
this.filterService.filter().subscribe(filteredData => (this.sortService.data = filteredData)) this.filterService.filter().subscribe(filteredData => (this.sortService.data = filteredData))
); );
} else {
this.filterService.filter().subscribe(filteredData => (this.dataSource.data = filteredData));
}
} }
/** /**

View File

@ -101,7 +101,7 @@ export class CategoryListComponent extends BaseViewComponent implements OnInit {
*/ */
public ngOnInit(): void { public ngOnInit(): void {
super.setTitle('Category'); super.setTitle('Category');
this.repo.getSortedViewModelListObservable().subscribe(newViewCategories => { this.repo.getViewModelListObservable().subscribe(newViewCategories => {
this.categories = newViewCategories; this.categories = newViewCategories;
}); });
} }
@ -187,21 +187,7 @@ export class CategoryListComponent extends BaseViewComponent implements OnInit {
* @returns all motions in the category * @returns all motions in the category
*/ */
public motionsInCategory(category: Category): ViewMotion[] { public motionsInCategory(category: Category): ViewMotion[] {
const coll = new Intl.Collator(this.translate.currentLang); return this.motionRepo.getSortedViewModelList().filter(m => m.category_id === category.id);
return this.motionRepo
.getViewModelList()
.filter(m => m.category_id === category.id)
.sort((motion1, motion2) => {
if (motion1.identifier && motion2.identifier) {
return coll.compare(motion1.identifier, motion2.identifier);
} else if (motion1.identifier) {
return 1;
} else if (motion2.identifier) {
return -1;
} else {
return coll.compare(motion1.getTitle(), motion2.getTitle());
}
});
} }
/** /**

View File

@ -107,7 +107,6 @@ export class MotionBlockListComponent extends ListViewBaseComponent<ViewMotionBl
this.items = this.itemRepo.getViewModelListBehaviorSubject(); this.items = this.itemRepo.getViewModelListBehaviorSubject();
// TODO: Should fall under generic sorting in PR 4411
this.repo.getViewModelListObservable().subscribe(newMotionblocks => { this.repo.getViewModelListObservable().subscribe(newMotionblocks => {
newMotionblocks.sort((a, b) => (a > b ? 1 : -1)); newMotionblocks.sort((a, b) => (a > b ? 1 : -1));
this.dataSource.data = newMotionblocks; this.dataSource.data = newMotionblocks;

View File

@ -18,6 +18,7 @@ import { itemVisibilityChoices } from 'app/shared/models/agenda/item';
import { LinenumberingService } from 'app/core/ui-services/linenumbering.service'; import { LinenumberingService } from 'app/core/ui-services/linenumbering.service';
import { LocalPermissionsService } from 'app/site/motions/services/local-permissions.service'; import { LocalPermissionsService } from 'app/site/motions/services/local-permissions.service';
import { Mediafile } from 'app/shared/models/mediafiles/mediafile'; import { Mediafile } from 'app/shared/models/mediafiles/mediafile';
import { MediafileRepositoryService } from 'app/core/repositories/mediafiles/mediafile-repository.service';
import { Motion } from 'app/shared/models/motions/motion'; import { Motion } from 'app/shared/models/motions/motion';
import { import {
MotionChangeRecommendationComponentData, MotionChangeRecommendationComponentData,
@ -49,7 +50,6 @@ import { ViewStatuteParagraph } from 'app/site/motions/models/view-statute-parag
import { ViewTag } from 'app/site/tags/models/view-tag'; import { ViewTag } from 'app/site/tags/models/view-tag';
import { ViewUnifiedChange } from 'app/shared/models/motions/view-unified-change'; import { ViewUnifiedChange } from 'app/shared/models/motions/view-unified-change';
import { TagRepositoryService } from 'app/core/repositories/tags/tag-repository.service'; import { TagRepositoryService } from 'app/core/repositories/tags/tag-repository.service';
import { MediafileRepositoryService } from 'app/core/repositories/mediafiles/mediafile-repository.service';
import { WorkflowRepositoryService } from 'app/core/repositories/motions/workflow-repository.service'; import { WorkflowRepositoryService } from 'app/core/repositories/motions/workflow-repository.service';
import { MotionBlockRepositoryService } from 'app/core/repositories/motions/motion-block-repository.service'; import { MotionBlockRepositoryService } from 'app/core/repositories/motions/motion-block-repository.service';
@ -368,6 +368,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
* @param agendaRepo Read out agenda variables * @param agendaRepo Read out agenda variables
* @param changeRecoRepo Change Recommendation Repository * @param changeRecoRepo Change Recommendation Repository
* @param statuteRepo: Statute Paragraph Repository * @param statuteRepo: Statute Paragraph Repository
* @param mediafileRepo Mediafile Repository
* @param DS The DataStoreService * @param DS The DataStoreService
* @param configService The configuration provider * @param configService The configuration provider
* @param sanitizer For making HTML SafeHTML * @param sanitizer For making HTML SafeHTML
@ -863,7 +864,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
const content = this.motion.getTitle(); const content = this.motion.getTitle();
if (await this.promptService.open(title, content)) { if (await this.promptService.open(title, content)) {
await this.repo.delete(this.motion); await this.repo.delete(this.motion);
this.router.navigate(['./motions/']); this.router.navigate(['../motions/']);
} }
} }
@ -1112,18 +1113,6 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
* then appending motion without identifiers sorted by title * then appending motion without identifiers sorted by title
*/ */
public setSurroundingMotions(): void { public setSurroundingMotions(): void {
// TODO: that operation is HEAVY
this.motionObserver.value.sort((a, b) => {
if (a.identifier && b.identifier) {
return a.identifier.localeCompare(b.identifier, this.translate.currentLang);
} else if (a.identifier) {
return 1;
} else if (b.identifier) {
return -1;
} else {
return a.title.localeCompare(b.title, this.translate.currentLang);
}
});
const indexOfCurrent = this.motionObserver.value.findIndex(motion => { const indexOfCurrent = this.motionObserver.value.findIndex(motion => {
return motion === this.motion; return motion === this.motion;
}); });

View File

@ -61,7 +61,7 @@ export class MotionExportDialogComponent implements OnInit {
* @returns a list of availavble commentSections * @returns a list of availavble commentSections
*/ */
public get commentsToExport(): ViewMotionCommentSection[] { public get commentsToExport(): ViewMotionCommentSection[] {
return this.commentRepo.getViewModelList(); return this.commentRepo.getSortedViewModelList();
} }
/** /**
* Hold the default lnMode. Will be set by the constructor. * Hold the default lnMode. Will be set by the constructor.

View File

@ -128,7 +128,7 @@ export class MotionListComponent extends ListViewBaseComponent<ViewMotion, Motio
.get<string>('motions_recommendations_by') .get<string>('motions_recommendations_by')
.subscribe(recommender => (this.recomendationEnabled = !!recommender)); .subscribe(recommender => (this.recomendationEnabled = !!recommender));
this.motionBlockRepo.getViewModelListObservable().subscribe(mBs => (this.motionBlocks = mBs)); this.motionBlockRepo.getViewModelListObservable().subscribe(mBs => (this.motionBlocks = mBs));
this.categoryRepo.getSortedViewModelListObservable().subscribe(cats => (this.categories = cats)); this.categoryRepo.getViewModelListObservable().subscribe(cats => (this.categories = cats));
this.tagRepo.getViewModelListObservable().subscribe(tags => (this.tags = tags)); this.tagRepo.getViewModelListObservable().subscribe(tags => (this.tags = tags));
this.workflowRepo.getViewModelListObservable().subscribe(wfs => (this.workflows = wfs)); this.workflowRepo.getViewModelListObservable().subscribe(wfs => (this.workflows = wfs));
this.setFulltextFilter(); this.setFulltextFilter();

View File

@ -182,7 +182,7 @@ export class MotionFilterListService extends BaseFilterListService<Motion, ViewM
* Subscibes to changing Categories, and updates the filter accordingly * Subscibes to changing Categories, and updates the filter accordingly
*/ */
private subscribeCategories(): void { private subscribeCategories(): void {
this.categoryRepo.getSortedViewModelListObservable().subscribe(categories => { this.categoryRepo.getViewModelListObservable().subscribe(categories => {
const categoryOptions: OsFilterOptions = categories.map(cat => ({ const categoryOptions: OsFilterOptions = categories.map(cat => ({
condition: cat.id, condition: cat.id,
label: cat.prefixedName, label: cat.prefixedName,

View File

@ -74,7 +74,7 @@ export class MotionMultiselectService {
*/ */
public async moveToItem(motions: ViewMotion[]): Promise<void> { public async moveToItem(motions: ViewMotion[]): Promise<void> {
const title = this.translate.instant('This will move all selected motions as childs to:'); const title = this.translate.instant('This will move all selected motions as childs to:');
const choices: (Displayable & Identifiable)[] = this.agendaRepo.getViewModelList(); const choices: (Displayable & Identifiable)[] = this.agendaRepo.getSortedViewModelList();
const selectedChoice = await this.choiceService.open(title, choices); const selectedChoice = await this.choiceService.open(title, choices);
if (selectedChoice) { if (selectedChoice) {
const requestData = { const requestData = {
@ -139,7 +139,7 @@ export class MotionMultiselectService {
const clearChoice = this.translate.instant('No category'); const clearChoice = this.translate.instant('No category');
const selectedChoice = await this.choiceService.open( const selectedChoice = await this.choiceService.open(
title, title,
this.categoryRepo.sortViewCategoriesByConfig(this.categoryRepo.getViewModelList()), this.categoryRepo.getSortedViewModelList(),
false, false,
null, null,
clearChoice clearChoice
@ -164,7 +164,12 @@ export class MotionMultiselectService {
'This will add or remove the following submitters for all selected motions:' 'This will add or remove the following submitters for all selected motions:'
); );
const choices = [this.translate.instant('Add'), this.translate.instant('Remove')]; const choices = [this.translate.instant('Add'), this.translate.instant('Remove')];
const selectedChoice = await this.choiceService.open(title, this.userRepo.getViewModelList(), true, choices); const selectedChoice = await this.choiceService.open(
title,
this.userRepo.getSortedViewModelList(),
true,
choices
);
if (selectedChoice && selectedChoice.action === choices[0]) { if (selectedChoice && selectedChoice.action === choices[0]) {
const requestData = motions.map(motion => { const requestData = motions.map(motion => {
let submitterIds = [...motion.sorted_submitters_id, ...(selectedChoice.items as number[])]; let submitterIds = [...motion.sorted_submitters_id, ...(selectedChoice.items as number[])];
@ -200,7 +205,12 @@ export class MotionMultiselectService {
this.translate.instant('Remove'), this.translate.instant('Remove'),
this.translate.instant('Clear tags') this.translate.instant('Clear tags')
]; ];
const selectedChoice = await this.choiceService.open(title, this.tagRepo.getViewModelList(), true, choices); const selectedChoice = await this.choiceService.open(
title,
this.tagRepo.getSortedViewModelList(),
true,
choices
);
if (selectedChoice && selectedChoice.action === choices[0]) { if (selectedChoice && selectedChoice.action === choices[0]) {
const requestData = motions.map(motion => { const requestData = motions.map(motion => {
let tagIds = [...motion.tags_id, ...(selectedChoice.items as number[])]; let tagIds = [...motion.tags_id, ...(selectedChoice.items as number[])];
@ -242,7 +252,7 @@ export class MotionMultiselectService {
const clearChoice = this.translate.instant('Clear motion block'); const clearChoice = this.translate.instant('Clear motion block');
const selectedChoice = await this.choiceService.open( const selectedChoice = await this.choiceService.open(
title, title,
this.motionBlockRepo.getViewModelList(), this.motionBlockRepo.getSortedViewModelList(),
false, false,
null, null,
clearChoice clearChoice
@ -280,7 +290,7 @@ export class MotionMultiselectService {
// insert after chosen // insert after chosen
const olderSibling = this.repo.getViewModel(selectedChoice.items as number); const olderSibling = this.repo.getViewModel(selectedChoice.items as number);
const parentId = olderSibling ? olderSibling.sort_parent_id : null; const parentId = olderSibling ? olderSibling.sort_parent_id : null;
const siblings = this.repo.getViewModelList().filter(motion => motion.sort_parent_id === parentId); const siblings = allMotions.filter(motion => motion.sort_parent_id === parentId);
const idx = siblings.findIndex(sib => sib.id === olderSibling.id); const idx = siblings.findIndex(sib => sib.id === olderSibling.id);
const before = siblings.slice(0, idx + 1); const before = siblings.slice(0, idx + 1);
const after = siblings.slice(idx + 1); const after = siblings.slice(idx + 1);

View File

@ -48,7 +48,7 @@ export class MotionPdfService {
* *
* @param translate handle translations * @param translate handle translations
* @param motionRepo get parent motions * @param motionRepo get parent motions
* @param statureRepo To get formated stature paragraphs * @param statuteRepo To get formated stature paragraphs
* @param changeRecoRepo to get the change recommendations * @param changeRecoRepo to get the change recommendations
* @param configService Read config variables * @param configService Read config variables
* @param htmlToPdfService To convert HTML text into pdfmake doc def * @param htmlToPdfService To convert HTML text into pdfmake doc def
@ -59,7 +59,7 @@ export class MotionPdfService {
public constructor( public constructor(
private translate: TranslateService, private translate: TranslateService,
private motionRepo: MotionRepositoryService, private motionRepo: MotionRepositoryService,
private statureRepo: StatuteParagraphRepositoryService, private statuteRepo: StatuteParagraphRepositoryService,
private changeRecoRepo: ChangeRecommendationRepositoryService, private changeRecoRepo: ChangeRecommendationRepositoryService,
private configService: ConfigService, private configService: ConfigService,
private htmlToPdfService: HtmlToPdfService, private htmlToPdfService: HtmlToPdfService,
@ -123,7 +123,7 @@ export class MotionPdfService {
} }
if (infoToExport && infoToExport.includes('allcomments')) { if (infoToExport && infoToExport.includes('allcomments')) {
commentsToExport = this.commentRepo.getViewModelList().map(vm => vm.id); commentsToExport = this.commentRepo.getSortedViewModelList().map(vm => vm.id);
} }
if (commentsToExport) { if (commentsToExport) {
motionPdfContent.push(this.createComments(motion, commentsToExport)); motionPdfContent.push(this.createComments(motion, commentsToExport));
@ -495,7 +495,7 @@ export class MotionPdfService {
} }
} else if (motion.isStatuteAmendment()) { } else if (motion.isStatuteAmendment()) {
// statute amendments // statute amendments
const statutes = this.statureRepo.getViewModelList(); const statutes = this.statuteRepo.getViewModelList();
motionText = this.motionRepo.formatStatuteAmendment(statutes, motion, lineLength); motionText = this.motionRepo.formatStatuteAmendment(statutes, motion, lineLength);
} else { } else {
// lead motion or normal amendments // lead motion or normal amendments

View File

@ -37,6 +37,6 @@ export class MotionSortListService extends BaseSortListService<ViewMotion> {
*/ */
public constructor(translate: TranslateService, store: StorageService, config: ConfigService) { public constructor(translate: TranslateService, store: StorageService, config: ConfigService) {
super(translate, store); super(translate, store);
this.sortOptions.sortProperty = config.instant<keyof ViewMotion>('motions_motions_sorting'); this.defaultSorting = config.instant<keyof ViewMotion>('motions_motions_sorting');
} }
} }

View File

@ -19,7 +19,7 @@
Reference projector for current list of speakers: Reference projector for current list of speakers:
</span>&nbsp; </span>&nbsp;
<mat-form-field> <mat-form-field>
<mat-select [disabled]="!!editId" [value]="projectors[0].reference_projector_id" (selectionChange)="onSelectReferenceProjector($event)"> <mat-select [disabled]="!!editId" [value]="projectors.length ? projectors[0].reference_projector_id : null" (selectionChange)="onSelectReferenceProjector($event)">
<mat-option *ngFor="let projector of projectors" [value]="projector.id"> <mat-option *ngFor="let projector of projectors" [value]="projector.id">
{{ projector.getTitle() | translate }} {{ projector.getTitle() | translate }}
</mat-option> </mat-option>

View File

@ -120,6 +120,7 @@ export class ProjectorListComponent extends BaseViewComponent implements OnInit
*/ */
public ngOnInit(): void { public ngOnInit(): void {
super.setTitle('Projectors'); super.setTitle('Projectors');
this.projectors = this.repo.getViewModelList();
this.repo.getViewModelListObservable().subscribe(projectors => (this.projectors = projectors)); this.repo.getViewModelListObservable().subscribe(projectors => (this.projectors = projectors));
} }

View File

@ -64,7 +64,7 @@ export class ProjectorMessageListComponent extends BaseViewComponent implements
*/ */
public ngOnInit(): void { public ngOnInit(): void {
super.setTitle('Messages'); super.setTitle('Messages');
this.messages = this.repo.getViewModelList(); this.messages = this.repo.getSortedViewModelList();
this.repo.getViewModelListObservable().subscribe(messages => (this.messages = messages)); this.repo.getViewModelListObservable().subscribe(messages => (this.messages = messages));
} }

View File

@ -139,7 +139,7 @@ export class UserDetailComponent extends BaseViewComponent implements OnInit {
} }
this.createForm(); this.createForm();
this.groups = this.groupRepo.getViewModelList().filter(group => group.id !== 1); this.groups = this.groupRepo.getSortedViewModelList().filter(group => group.id !== 1);
this.groupRepo this.groupRepo
.getViewModelListObservable() .getViewModelListObservable()
.subscribe(groups => (this.groups = groups.filter(group => group.id !== 1))); .subscribe(groups => (this.groups = groups.filter(group => group.id !== 1)));

View File

@ -167,7 +167,8 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser, User> imp
this.setFulltextFilter(); this.setFulltextFilter();
// Initialize the groups // Initialize the groups
this.groups = this.groupRepo.getViewModelList().filter(group => group.id !== 1); this.groups = this.groupRepo.getSortedViewModelList().filter(group => group.id !== 1);
this.groupRepo.getViewModelListObservable().subscribe(groups => (this.groups = groups));
} }
/** /**

View File

@ -36,6 +36,6 @@ export class UserSortListService extends BaseSortListService<ViewUser> {
*/ */
public constructor(translate: TranslateService, store: StorageService, config: ConfigService) { public constructor(translate: TranslateService, store: StorageService, config: ConfigService) {
super(translate, store); super(translate, store);
this.sortOptions.sortProperty = config.instant<keyof ViewUser>('motions_motions_sorting'); this.defaultSorting = config.instant<keyof ViewUser>('users_sort_by');
} }
} }