Add sort service for amendments

Adds the posibility to sort amendments by the parents identifier
and line number.

Patches the amendment model by their diff lines in runtime
This commit is contained in:
Sean Engelhardt 2019-09-03 15:11:54 +02:00
parent 8ff03044de
commit 91329a8338
16 changed files with 245 additions and 87 deletions

View File

@ -82,12 +82,18 @@ export class RelationManagerService {
);
viewModel['_' + relation.ownKey] = foreignViewModels;
this.sortByRelation(relation, viewModel);
if (relation.afterSetRelation) {
relation.afterSetRelation(viewModel, 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);
}
}
} else if (isReverseRelationDefinition(relation) && !initialLoading) {
if (relation.type === 'M2M') {
@ -203,12 +209,19 @@ export class RelationManagerService {
) {
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.
ownViewModel['_' + relation.ownKey] = <any>this.viewModelStoreService.get(collection, changedId);
const foreignViewModel = this.viewModelStoreService.get(collection, changedId);
ownViewModel['_' + relation.ownKey] = <any>foreignViewModel;
if (relation.afterDependencyChange) {
relation.afterDependencyChange(ownViewModel, foreignViewModel);
}
return true;
}
}

View File

@ -39,6 +39,8 @@ 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;
}
/**
@ -56,16 +58,19 @@ 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> =

View File

@ -133,12 +133,6 @@ const MotionRelations: RelationDefinition[] = [
ownKey: 'tags',
foreignViewModel: ViewTag
},
{
type: 'M2O',
ownIdKey: 'parent_id',
ownKey: 'parent',
foreignViewModel: ViewMotion
},
{
type: 'M2M',
ownIdKey: 'change_recommendations_id',
@ -171,6 +165,11 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
*/
protected sortProperty: SortProperty;
/**
* Line length of a motion
*/
private motionLineLength: number;
/**
* Creates a MotionRepository
*
@ -206,10 +205,15 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
this.sortProperty = conf;
this.setConfigSortFn();
});
config.get<number>('motions_line_length').subscribe(lineLength => {
this.motionLineLength = lineLength;
});
}
/**
* 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({
@ -228,6 +232,22 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
return true;
}
});
this.relationDefinitions.push({
type: 'M2O',
ownIdKey: 'parent_id',
ownKey: 'parent',
foreignViewModel: ViewMotion,
afterSetRelation: (motion: ViewMotion, foreignViewModel: ViewMotion | null) => {
if (foreignViewModel) {
motion.diffLines = this.getAmendmentParagraphs(motion, this.motionLineLength, false);
}
},
afterDependencyChange: (motion: ViewMotion, parent: ViewMotion) => {
if (motion.parent) {
motion.diffLines = this.getAmendmentParagraphs(motion, this.motionLineLength, false);
}
}
});
super.groupRelationsByCollections();
}
@ -284,8 +304,10 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
protected createViewModelWithTitles(model: Motion, initialLoading: boolean): ViewMotion {
const viewModel = super.createViewModelWithTitles(model, initialLoading);
viewModel.getIdentifierOrTitle = () => this.getIdentifierOrTitle(viewModel);
viewModel.getProjectorTitle = () => this.getAgendaSlideTitle(viewModel);
return viewModel;
}
@ -639,16 +661,6 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
return range.to;
}
/**
* Given an amendment, this returns the motion affected by this amendments
*
* @param {ViewMotion} amendment
* @returns {ViewMotion}
*/
public getAmendmentBaseMotion(amendment: ViewMotion): ViewMotion {
return this.getViewModel(amendment.parent_id);
}
/**
* Splits a motion into paragraphs, optionally adding line numbers
*
@ -700,7 +712,7 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
lineLength: number,
includeUnchanged: boolean
): DiffLinesInParagraph[] {
const motion = this.getAmendmentBaseMotion(amendment);
const motion = amendment.parent;
const baseParagraphs = this.getTextParagraphs(motion, true, lineLength);
return amendment.amendment_paragraphs
@ -744,7 +756,7 @@ export class MotionRepositoryService extends BaseIsAgendaItemAndListOfSpeakersCo
* @returns {ViewMotionAmendedParagraph[]}
*/
public getAmendmentAmendedParagraphs(amendment: ViewMotion, lineLength: number): ViewMotionAmendedParagraph[] {
const motion = this.getAmendmentBaseMotion(amendment);
const motion = amendment.parent;
const baseParagraphs = this.getTextParagraphs(motion, true, lineLength);
return amendment.amendment_paragraphs

View File

@ -243,8 +243,6 @@ export abstract class BaseFilterListService<V extends BaseViewModel> {
if (storedFilter && storedFilter.length && newDefinitions && newDefinitions.length) {
for (const newDef of newDefinitions) {
console.log('set filter');
// for some weird angular bugs, newDef can actually be undefined
if (newDef) {
let count = 0;

View File

@ -55,6 +55,11 @@ export abstract class BaseSortListService<V extends BaseViewModel> {
*/
private sortDefinition: OsSortingDefinition<V>;
/**
* The key to access stored valued
*/
protected abstract readonly storageKey: string;
/**
* The sorting function according to current settings.
*/
@ -105,13 +110,15 @@ export abstract class BaseSortListService<V extends BaseViewModel> {
* @returns wether sorting is active or not
*/
public get isActive(): boolean {
return this.sortDefinition && this.sortOptions.length > 0;
return this.sortOptions && this.sortOptions.length > 0;
}
/**
* Enforce children to implement sortOptions
*/
public abstract sortOptions: OsSortingOption<V>[];
public get sortOptions(): OsSortingOption<V>[] {
const sortOptions = this.getSortOptions();
if (sortOptions && sortOptions.length) {
return sortOptions;
}
}
/**
* Constructor.
@ -121,12 +128,16 @@ export abstract class BaseSortListService<V extends BaseViewModel> {
* @param store to save and load sorting preferences
*/
public constructor(
protected name: string,
protected translate: TranslateService,
private store: StorageService,
private OSStatus: OpenSlidesStatusService
) {}
/**
* Enforce children to implement a function that returns their sorting options
*/
protected abstract getSortOptions(): OsSortingOption<V>[];
/**
* Enforce children to implement a method that returns the fault sorting
*/
@ -148,7 +159,7 @@ export abstract class BaseSortListService<V extends BaseViewModel> {
if (this.OSStatus.isInHistoryMode) {
this.sortDefinition = null;
} else {
this.sortDefinition = await this.store.get<OsSortingDefinition<V> | null>('sorting_' + this.name);
this.sortDefinition = await this.store.get<OsSortingDefinition<V> | null>('sorting_' + this.storageKey);
}
if (this.sortDefinition && this.sortDefinition.sortProperty) {
@ -183,11 +194,15 @@ export abstract class BaseSortListService<V extends BaseViewModel> {
* @param option
* @returns the name of the sorting icon, fit to material icon ligatures
*/
public getSortIcon(option: OsSortingOption<V>): string {
if (this.sortProperty !== option.property) {
return '';
public getSortIcon(option: OsSortingOption<V>): string | null {
if (this.sortDefinition) {
if (this.sortProperty && this.sortProperty !== option.property) {
return '';
}
return this.ascending ? 'arrow_upward' : 'arrow_downward';
} else {
return null;
}
return this.ascending ? 'arrow_upward' : 'arrow_downward';
}
/**
@ -210,7 +225,7 @@ export abstract class BaseSortListService<V extends BaseViewModel> {
private updateSortDefinitions(): void {
this.updateSortedData();
if (!this.OSStatus.isInHistoryMode) {
this.store.set('sorting_' + this.name, this.sortDefinition);
this.store.set('sorting_' + this.storageKey, this.sortDefinition);
}
}
@ -227,7 +242,7 @@ export abstract class BaseSortListService<V extends BaseViewModel> {
* every time the sorting (property, ascending/descending) or the language changes
*/
protected updateSortedData(): void {
if (this.inputData) {
if (this.inputData && this.sortDefinition) {
const property = this.sortProperty as string;
const intl = new Intl.Collator(this.translate.currentLang, {

View File

@ -208,8 +208,9 @@ export class SortFilterBarComponent<V extends BaseViewModel> {
* Retrieves the currently active icon for an option.
* @param option
*/
public getSortIcon(option: OsSortingOption<V>): string {
return this.sortService.getSortIcon(option);
public getSortIcon(option: OsSortingOption<V>): string | null {
const icon = this.sortService.getSortIcon(option);
return icon ? icon : null;
}
/**

View File

@ -14,10 +14,15 @@ import { ViewAssignment } from '../models/view-assignment';
providedIn: 'root'
})
export class AssignmentSortListService extends BaseSortListService<ViewAssignment> {
/**
* set the storage key name
*/
protected storageKey = 'AssignmentList';
/**
* Define the sort options
*/
public sortOptions: OsSortingOption<ViewAssignment>[] = [
private assignmentSortOptions: OsSortingOption<ViewAssignment>[] = [
{ property: 'title', label: 'Name' },
{ property: 'phase', label: 'Phase' },
{ property: 'candidateAmount', label: 'Number of candidates' },
@ -31,7 +36,14 @@ export class AssignmentSortListService extends BaseSortListService<ViewAssignmen
* @param storage required by parent
*/
public constructor(translate: TranslateService, storage: StorageService, OSStatus: OpenSlidesStatusService) {
super('Assignment', translate, storage, OSStatus);
super(translate, storage, OSStatus);
}
/**
* @override
*/
protected getSortOptions(): OsSortingOption<ViewAssignment>[] {
return this.assignmentSortOptions;
}
/**

View File

@ -14,7 +14,12 @@ import { ViewMediafile } from '../models/view-mediafile';
providedIn: 'root'
})
export class MediafilesSortListService extends BaseSortListService<ViewMediafile> {
public sortOptions: OsSortingOption<ViewMediafile>[] = [
/**
* set the storage key name
*/
protected storageKey = 'MediafileList';
private mediafilesSortOptions: OsSortingOption<ViewMediafile>[] = [
{ property: 'title' },
{
property: 'type',
@ -33,7 +38,14 @@ export class MediafilesSortListService extends BaseSortListService<ViewMediafile
* @param store required by parent
*/
public constructor(translate: TranslateService, store: StorageService, OSStatus: OpenSlidesStatusService) {
super('Mediafiles', translate, store, OSStatus);
super(translate, store, OSStatus);
}
/**
* @override
*/
protected getSortOptions(): OsSortingOption<ViewMediafile>[] {
return this.mediafilesSortOptions;
}
/**

View File

@ -1,5 +1,6 @@
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 { PersonalNoteContent } from 'app/shared/models/users/personal-note';
@ -76,6 +77,7 @@ export class ViewMotion extends BaseViewModelWithAgendaItemAndListOfSpeakers<Mot
protected _parent?: ViewMotion;
protected _amendments?: ViewMotion[];
protected _changeRecommendations?: ViewMotionChangeRecommendation[];
protected _diffLines?: DiffLinesInParagraph[];
public personalNote?: PersonalNoteContent;
public get motion(): Motion {
@ -342,6 +344,31 @@ 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;
}
/**
* Get the number of the first diff line, in case a motion is an amendment
*/
public get parentAndLineNumber(): string | null {
if (this.isParagraphBasedAmendment() && this.parent && this.diffLines && this.diffLines.length) {
return `${this.parent.identifier} ${this.diffLines[0].diffLineFrom}`;
} else {
return null;
}
}
// This is set by the repository
public getIdentifierOrTitle: () => string;

View File

@ -18,7 +18,7 @@
<os-list-view-table
[repo]="motionRepo"
[sortService]="motionSortService"
[sortService]="amendmentSortService"
[filterService]="amendmentFilterService"
[columns]="tableColumnDefinition"
[filterProps]="filterProps"
@ -70,6 +70,7 @@
<!-- Summary -->
<div *pblNgridCellDef="'summary'; row as motion" class="cell-slot fill">
<a class="detail-link" [routerLink]="motion.getDetailStateURL()"></a>
<div class="innerTable">
<div class="motion-text" [innerHtml]="sanitizeText(getAmendmentSummary(motion))"></div>
</div>

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, Component, OnInit, ViewEncapsulation } from '@angular/core';
import { MatDialog, MatSnackBar } from '@angular/material';
import { DomSanitizer, SafeHtml, Title } from '@angular/platform-browser';
import { ActivatedRoute } from '@angular/router';
@ -7,10 +7,9 @@ import { TranslateService } from '@ngx-translate/core';
import { PblColumnDefinition } from '@pebula/ngrid';
import { AmendmentFilterListService } from '../../services/amendment-filter-list.service';
import { AmendmentSortListService } from '../../services/amendment-sort-list.service';
import { StorageService } from 'app/core/core-services/storage.service';
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
import { ConfigService } from 'app/core/ui-services/config.service';
import { DiffLinesInParagraph } from 'app/core/ui-services/diff.service';
import { LinenumberingService } from 'app/core/ui-services/linenumbering.service';
import { ItemVisibilityChoices } from 'app/shared/models/agenda/item';
import { largeDialogSettings } from 'app/shared/utils/dialog-settings';
@ -27,7 +26,8 @@ import { ViewMotion } from '../../models/view-motion';
selector: 'os-amendment-list',
templateUrl: './amendment-list.component.html',
styleUrls: ['./amendment-list.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None
})
export class AmendmentListComponent extends BaseListViewComponent<ViewMotion> implements OnInit {
/**
@ -40,11 +40,6 @@ export class AmendmentListComponent extends BaseListViewComponent<ViewMotion> im
*/
public itemVisibility = ItemVisibilityChoices;
/**
* To hold the motions line length
*/
private motionLineLength: number;
/**
* Column defintiion
*/
@ -88,9 +83,9 @@ export class AmendmentListComponent extends BaseListViewComponent<ViewMotion> im
route: ActivatedRoute,
public motionRepo: MotionRepositoryService,
public motionSortService: MotionSortListService,
public amendmentSortService: AmendmentSortListService,
public amendmentFilterService: AmendmentFilterListService,
private sanitizer: DomSanitizer,
private configService: ConfigService,
private dialog: MatDialog,
private motionExport: MotionExportService,
private linenumberingService: LinenumberingService
@ -106,32 +101,7 @@ export class AmendmentListComponent extends BaseListViewComponent<ViewMotion> im
}
}
/**
* Observe the line length
*/
public ngOnInit(): void {
this.configService.get<number>('motions_line_length').subscribe(lineLength => {
this.motionLineLength = lineLength;
});
if (!!this.parentMotionId) {
// this.amendmentFilterService.clearAllFilters();
}
}
/**
* Helper function to get amendment paragraphs of a given motion
*
* @param amendment the get the paragraphs from
* @returns DiffLinesInParagraph-List
*/
private getDiffLines(amendment: ViewMotion): DiffLinesInParagraph[] {
if (amendment.isParagraphBasedAmendment()) {
return this.motionRepo.getAmendmentParagraphs(amendment, this.motionLineLength, false);
} else {
return null;
}
}
public ngOnInit(): void {}
/**
* Extract the lines of the amendments
@ -141,7 +111,7 @@ export class AmendmentListComponent extends BaseListViewComponent<ViewMotion> im
* @return The lines of the amendment
*/
public getChangeLines(amendment: ViewMotion): string {
const diffLines = this.getDiffLines(amendment);
const diffLines = amendment.diffLines;
if (!!diffLines) {
return diffLines
@ -163,7 +133,7 @@ export class AmendmentListComponent extends BaseListViewComponent<ViewMotion> im
* @returns the amendments as string, if they are multiple they gonna be separated by `[...]`
*/
public getAmendmentSummary(amendment: ViewMotion): string {
const diffLines = this.getDiffLines(amendment);
const diffLines = amendment.diffLines;
if (!!diffLines) {
return diffLines
.map(diffLine => {

View File

@ -0,0 +1,12 @@
import { TestBed } from '@angular/core/testing';
import { AmendmentSortListService } from './amendment-sort-list.service';
describe('AmendmentSortListService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: AmendmentSortListService = TestBed.get(AmendmentSortListService);
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,47 @@
import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { OpenSlidesStatusService } from 'app/core/core-services/openslides-status.service';
import { StorageService } from 'app/core/core-services/storage.service';
import { OsSortingDefinition, OsSortingOption } from 'app/core/ui-services/base-sort-list.service';
import { ConfigService } from 'app/core/ui-services/config.service';
import { MotionSortListService } from './motion-sort-list.service';
import { ViewMotion } from '../models/view-motion';
@Injectable({
providedIn: 'root'
})
export class AmendmentSortListService extends MotionSortListService {
/**
* set the storage key name
*/
protected storageKey = 'AmendmentList';
private amendmentSortOptions: OsSortingOption<ViewMotion>[] = [
{
property: 'parentAndLineNumber',
label: 'Main motion and line number'
}
];
public constructor(
translate: TranslateService,
store: StorageService,
OSStatus: OpenSlidesStatusService,
config: ConfigService
) {
super(translate, store, OSStatus, config);
}
protected getSortOptions(): OsSortingOption<ViewMotion>[] {
return this.amendmentSortOptions.concat(super.getSortOptions());
}
protected async getDefaultDefinition(): Promise<OsSortingDefinition<ViewMotion>> {
return {
sortProperty: 'parentAndLineNumber',
sortAscending: true
};
}
}

View File

@ -11,7 +11,12 @@ import { ViewMotionBlock } from '../models/view-motion-block';
providedIn: 'root'
})
export class MotionBlockSortService extends BaseSortListService<ViewMotionBlock> {
public sortOptions: OsSortingOption<ViewMotionBlock>[] = [
/**
* set the storage key name
*/
protected storageKey = 'MotionBlockList';
private MotionBlockSortOptions: OsSortingOption<ViewMotionBlock>[] = [
{ property: 'title' },
{
property: 'motions',
@ -25,7 +30,14 @@ export class MotionBlockSortService extends BaseSortListService<ViewMotionBlock>
];
public constructor(translate: TranslateService, store: StorageService, OSStatus: OpenSlidesStatusService) {
super('Motion block', translate, store, OSStatus);
super(translate, store, OSStatus);
}
/**
* @override
*/
protected getSortOptions(): OsSortingOption<ViewMotionBlock>[] {
return this.MotionBlockSortOptions;
}
protected async getDefaultDefinition(): Promise<OsSortingDefinition<ViewMotionBlock>> {

View File

@ -17,6 +17,11 @@ import { ViewMotion } from '../models/view-motion';
providedIn: 'root'
})
export class MotionSortListService extends BaseSortListService<ViewMotion> {
/**
* set the storage key name
*/
protected storageKey = 'MotionList';
/**
* Hold the default motion sorting
*/
@ -30,7 +35,7 @@ export class MotionSortListService extends BaseSortListService<ViewMotion> {
/**
* Define the sort options
*/
public sortOptions: OsSortingOption<ViewMotion>[] = [
protected motionSortOptions: OsSortingOption<ViewMotion>[] = [
{ property: 'weight', label: 'Call list' },
{ property: 'identifier' },
{ property: 'title' },
@ -53,11 +58,11 @@ export class MotionSortListService extends BaseSortListService<ViewMotion> {
translate: TranslateService,
store: StorageService,
OSStatus: OpenSlidesStatusService,
private config: ConfigService
config: ConfigService
) {
super('Motion', translate, store, OSStatus);
super(translate, store, OSStatus);
this.config.get<string>('motions_motions_sorting').subscribe(defSortProp => {
config.get<string>('motions_motions_sorting').subscribe(defSortProp => {
if (defSortProp) {
this.defaultMotionSorting = defSortProp;
this.defaultSortingLoaded.resolve();
@ -65,6 +70,10 @@ export class MotionSortListService extends BaseSortListService<ViewMotion> {
});
}
protected getSortOptions(): OsSortingOption<ViewMotion>[] {
return this.motionSortOptions;
}
/**
* Required by parent
*

View File

@ -14,10 +14,15 @@ import { ViewUser } from '../models/view-user';
providedIn: 'root'
})
export class UserSortListService extends BaseSortListService<ViewUser> {
/**
* set the storage key name
*/
protected storageKey = 'UserList';
/**
* Define the sort options
*/
public sortOptions: OsSortingOption<ViewUser>[] = [
private userSortOptions: OsSortingOption<ViewUser>[] = [
{ property: 'first_name', label: 'Given name' },
{ property: 'last_name', label: 'Surname' },
{ property: 'is_present', label: 'Presence' },
@ -36,7 +41,14 @@ export class UserSortListService extends BaseSortListService<ViewUser> {
* @param store requires by parent
*/
public constructor(translate: TranslateService, store: StorageService, OSStatus: OpenSlidesStatusService) {
super('User', translate, store, OSStatus);
super(translate, store, OSStatus);
}
/**
* @override
*/
protected getSortOptions(): OsSortingOption<ViewUser>[] {
return this.userSortOptions;
}
/**