Add referenced motions by the server (fixes #4383)

This commit is contained in:
FinnStutzenstein 2019-02-26 14:43:51 +01:00
parent 23c1857fa6
commit 4cde0431ab
8 changed files with 113 additions and 48 deletions

View File

@ -92,6 +92,22 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository<V
]); ]);
} }
public getTitle = (motion: Partial<Motion> | Partial<ViewMotion>) => {
if (motion.identifier) {
return motion.identifier + ': ' + motion.title;
} else {
return motion.title;
}
};
public getIdentifierOrTitle = (motion: Partial<Motion> | Partial<ViewMotion>) => {
if (motion.identifier) {
return motion.identifier;
} else {
return motion.title;
}
};
public getAgendaTitle = (motion: Partial<Motion> | Partial<ViewMotion>) => { public getAgendaTitle = (motion: Partial<Motion> | Partial<ViewMotion>) => {
// if the identifier is set, the title will be 'Motion <identifier>'. // if the identifier is set, the title will be 'Motion <identifier>'.
if (motion.identifier) { if (motion.identifier) {
@ -149,6 +165,8 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository<V
tags, tags,
parent parent
); );
viewMotion.getIdentifierOrTitle = () => this.getIdentifierOrTitle(viewMotion);
viewMotion.getTitle = () => this.getTitle(viewMotion);
viewMotion.getVerboseName = this.getVerboseName; viewMotion.getVerboseName = this.getVerboseName;
viewMotion.getAgendaTitle = () => this.getAgendaTitle(viewMotion); viewMotion.getAgendaTitle = () => this.getAgendaTitle(viewMotion);
viewMotion.getProjectorTitle = viewMotion.getAgendaTitle; viewMotion.getProjectorTitle = viewMotion.getAgendaTitle;
@ -759,7 +777,7 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository<V
} }
let state = this.translate.instant(motion.state.name); let state = this.translate.instant(motion.state.name);
if (motion.stateExtension && motion.state.show_state_extension_field) { if (motion.stateExtension && motion.state.show_state_extension_field) {
state += ' ' + this.solveExtensionPlaceHolder(motion.stateExtension); state += ' ' + this.parseMotionPlaceholders(motion.stateExtension);
} }
return state; return state;
} }
@ -775,7 +793,7 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository<V
if (motion.recommendation) { if (motion.recommendation) {
let rec = this.translate.instant(motion.recommendation.recommendation_label); let rec = this.translate.instant(motion.recommendation.recommendation_label);
if (motion.recommendationExtension && motion.recommendation.show_recommendation_extension_field) { if (motion.recommendationExtension && motion.recommendation.show_recommendation_extension_field) {
rec += ' ' + this.solveExtensionPlaceHolder(motion.recommendationExtension); rec += ' ' + this.parseMotionPlaceholders(motion.recommendationExtension);
} }
return rec; return rec;
} }
@ -785,20 +803,16 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository<V
* Replaces any motion placeholder (`[motion:id]`) with the motion's title(s) * Replaces any motion placeholder (`[motion:id]`) with the motion's title(s)
* *
* @param value * @param value
* @returns the string with the motion titles replacing the placeholders, '??' strings for errors * @returns the string with the motion titles replacing the placeholders
*/ */
public solveExtensionPlaceHolder(value: string): string { public parseMotionPlaceholders(value: string): string {
const beg = value.indexOf('[motion:'); return value.replace(/\[motion:(\d+)\]/g, (match, id) => {
const end = value.indexOf(']'); const motion = this.getViewModel(id);
if (beg > -1 && (end > -1 && end > beg)) { if (motion) {
const id = Number(value.substring(beg + 8, end)); return motion.getIdentifierOrTitle();
const referedMotion = Number.isNaN(id) ? null : this.getViewModel(id); } else {
const title = referedMotion ? referedMotion.identifier : '??'; return this.translate.instant('<unknown motion>');
value = value.substring(0, beg) + title + value.substring(end + 1); }
// recursively check for additional occurrences });
return this.solveExtensionPlaceHolder(value);
} else {
return value;
}
} }
} }

View File

@ -278,11 +278,11 @@
*ngIf="motion.recommendation && motion.recommendation.show_recommendation_extension_field" *ngIf="motion.recommendation && motion.recommendation.show_recommendation_extension_field"
class="spacer-top-10" class="spacer-top-10"
> >
<form [formGroup]="searchMotionForm"> <form [formGroup]="recommendationExtensionForm">
<mat-form-field> <mat-form-field>
<input <input
matInput matInput
[formControl]="searchMotionForm.get('recoExtension')" [formControl]="recommendationExtensionForm.get('recoExtension')"
placeholder="{{ 'Extension' | translate }}" placeholder="{{ 'Extension' | translate }}"
/> />
</mat-form-field> </mat-form-field>
@ -291,8 +291,8 @@
</button> </button>
<os-search-value-selector <os-search-value-selector
ngDefaultControl ngDefaultControl
[form]="searchMotionForm" [form]="recommendationExtensionForm"
[formControl]="searchMotionForm.get('motion_id')" [formControl]="recommendationExtensionForm.get('motion_id')"
[multiple]="false" [multiple]="false"
listname="{{ 'Motions' | translate }}" listname="{{ 'Motions' | translate }}"
[InputListValues]="motionObserver" [InputListValues]="motionObserver"

View File

@ -70,7 +70,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
/** /**
* To search other motions as extension via search value selector * To search other motions as extension via search value selector
*/ */
public searchMotionForm: FormGroup; public recommendationExtensionForm: FormGroup;
/** /**
* Determine if the motion is edited * Determine if the motion is edited
@ -636,7 +636,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
const statuteAmendmentFieldName = 'statute_amendment'; const statuteAmendmentFieldName = 'statute_amendment';
contentPatch[statuteAmendmentFieldName] = formMotion.isStatuteAmendment(); contentPatch[statuteAmendmentFieldName] = formMotion.isStatuteAmendment();
this.contentForm.patchValue(contentPatch); this.contentForm.patchValue(contentPatch);
this.searchMotionForm.get('recoExtension').setValue(this.motion.recommendationExtension); this.recommendationExtensionForm.get('recoExtension').setValue(this.motion.recommendationExtension);
} }
/** /**
@ -679,13 +679,13 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
}(); }();
// create the search motion form // create the search motion form
this.searchMotionForm = this.formBuilder.group({ this.recommendationExtensionForm = this.formBuilder.group({
motion_id: [], motion_id: [],
recoExtension: [] recoExtension: []
}); });
// Detect changes in in search motion form // Detect changes in in search motion form
this.searchMotionForm.get('motion_id').valueChanges.subscribe(change => { this.recommendationExtensionForm.get('motion_id').valueChanges.subscribe(change => {
this.addMotionExtension(change); this.addMotionExtension(change);
}); });
} }
@ -1167,8 +1167,8 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
* @param id the ID of a selected motion returned by a search value selector * @param id the ID of a selected motion returned by a search value selector
*/ */
public addMotionExtension(id: number): void { public addMotionExtension(id: number): void {
const recoExtensionValue = this.searchMotionForm.get('recoExtension').value; const recoExtensionValue = this.recommendationExtensionForm.get('recoExtension').value || '';
this.searchMotionForm.get('recoExtension').setValue(`${recoExtensionValue}[motion:${id}]`); this.recommendationExtensionForm.get('recoExtension').setValue(`${recoExtensionValue}[motion:${id}]`);
} }
/** /**
@ -1185,7 +1185,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
* in {@link newRecommendationExtension} * in {@link newRecommendationExtension}
*/ */
public setRecommendationExtension(): void { public setRecommendationExtension(): void {
this.repo.setRecommendationExtension(this.motion, this.searchMotionForm.get('recoExtension').value); this.repo.setRecommendationExtension(this.motion, this.recommendationExtensionForm.get('recoExtension').value);
} }
/** /**

View File

@ -323,17 +323,27 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
/** /**
* This is set by the repository * This is set by the repository
*/ */
public getAgendaTitle; public getTitle: () => string;
/** /**
* This is set by the repository * This is set by the repository
*/ */
public getAgendaTitleWithType; public getIdentifierOrTitle: () => string;
/** /**
* This is set by the repository * This is set by the repository
*/ */
public getVerboseName; public getAgendaTitle: () => string;
/**
* This is set by the repository
*/
public getAgendaTitleWithType: () => string;
/**
* This is set by the repository
*/
public getVerboseName: () => string;
public constructor( public constructor(
motion: Motion, motion: Motion,
@ -362,14 +372,6 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
this._parent = parent; this._parent = parent;
} }
public getTitle = () => {
if (this.identifier) {
return this.identifier + ': ' + this.title;
} else {
return this.title;
}
};
public getAgendaItem(): ViewItem { public getAgendaItem(): ViewItem {
return this.item; return this.item;
} }

View File

@ -1,6 +1,9 @@
export interface MotionBlockSlideMotionRepresentation { export interface MotionTitleInformation {
title: string; title: string;
identifier?: string; identifier?: string;
}
export interface MotionBlockSlideMotionRepresentation extends MotionTitleInformation {
recommendation?: { recommendation?: {
name: string; name: string;
css_class: string; css_class: string;
@ -11,4 +14,5 @@ export interface MotionBlockSlideMotionRepresentation {
export interface MotionBlockSlideData { export interface MotionBlockSlideData {
title: string; title: string;
motions: MotionBlockSlideMotionRepresentation[]; motions: MotionBlockSlideMotionRepresentation[];
referenced_motions: { [id: number]: MotionTitleInformation };
} }

View File

@ -6,7 +6,7 @@
<div *ngFor="let motion of data.data.motions"> <div *ngFor="let motion of data.data.motions">
<div class="ellipsis-overflow"> <div class="ellipsis-overflow">
{{ motion.identifier }}: {{ motion.title }} {{ getMotionTitle(motion) }}
</div> </div>
<div class="white ellipsis-overflow"> <div class="white ellipsis-overflow">
<mat-basic-chip *ngIf="motion.recommendation" disableRipple [ngClass]="getStateCssColor(motion)"> <mat-basic-chip *ngIf="motion.recommendation" disableRipple [ngClass]="getStateCssColor(motion)">

View File

@ -1,8 +1,11 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { BaseSlideComponent } from 'app/slides/base-slide-component'; import { BaseSlideComponent } from 'app/slides/base-slide-component';
import { MotionBlockSlideData, MotionBlockSlideMotionRepresentation } from './motion-block-slide-data'; import {
import { Motion } from 'app/shared/models/motions/motion'; MotionBlockSlideData,
MotionBlockSlideMotionRepresentation,
MotionTitleInformation
} from './motion-block-slide-data';
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service'; import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
import { StateCssClassMapping } from 'app/site/motions/models/view-workflow'; import { StateCssClassMapping } from 'app/site/motions/models/view-workflow';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
@ -17,8 +20,8 @@ export class MotionBlockSlideComponent extends BaseSlideComponent<MotionBlockSli
super(); super();
} }
public getMotionTitle(motion: Partial<Motion>): string { public getMotionTitle(motion: MotionTitleInformation): string {
return this.motionRepo.getAgendaTitle(motion); return this.motionRepo.getTitle(motion);
} }
public getStateCssColor(motion: MotionBlockSlideMotionRepresentation): string { public getStateCssColor(motion: MotionBlockSlideMotionRepresentation): string {
@ -28,7 +31,16 @@ export class MotionBlockSlideComponent extends BaseSlideComponent<MotionBlockSli
public getRecommendationLabel(motion: MotionBlockSlideMotionRepresentation): string { public getRecommendationLabel(motion: MotionBlockSlideMotionRepresentation): string {
let recommendation = this.translate.instant(motion.recommendation.name); let recommendation = this.translate.instant(motion.recommendation.name);
if (motion.recommendation_extension) { if (motion.recommendation_extension) {
recommendation += ' ' + this.motionRepo.solveExtensionPlaceHolder(motion.recommendation_extension); const extension = motion.recommendation_extension.replace(/\[motion:(\d+)\]/g, (match, id) => {
const titleInformation = this.data.data.referenced_motions[id];
if (titleInformation) {
return this.motionRepo.getIdentifierOrTitle(titleInformation);
} else {
return this.translate.instant('<unknown motion>');
}
});
recommendation += ' ' + extension;
} }
return recommendation; return recommendation;
} }

View File

@ -1,3 +1,4 @@
import re
from typing import Any, Dict from typing import Any, Dict
from ..users.projector import get_user_name from ..users.projector import get_user_name
@ -224,7 +225,17 @@ def motion_block_slide(
f"motion block with id {motion_block_id} does not exist" f"motion block with id {motion_block_id} does not exist"
) )
# All motions in this motion block
motions = [] motions = []
# All motions meantioned in recommendations of the above motions.
# Only title_informations will be collected. With this, the client can
# replace the placeholders in the recommendation correctly
# This maps the motion id to the title information
referenced_motions: Dict[int, Dict[str, str]] = {}
motion_placeholder_regex = re.compile(r"\[motion:(\d+)\]")
# Search motions.
for motion in all_data["motions/motion"].values(): for motion in all_data["motions/motion"].values():
if motion["motion_block_id"] == motion_block_id: if motion["motion_block_id"] == motion_block_id:
motion_object = { motion_object = {
@ -242,13 +253,35 @@ def motion_block_slide(
"css_class": recommendation["css_class"], "css_class": recommendation["css_class"],
} }
if recommendation["show_recommendation_extension_field"]: if recommendation["show_recommendation_extension_field"]:
motion_object["recommendation_extension"] = motion[ recommendation_extension = motion["recommendation_extension"]
"recommendation_extension"
motion_object["recommendation_extension"] = recommendation_extension
# Collect all meantioned motions via [motion:<id>]
referenced_ids = [
int(id)
for id in motion_placeholder_regex.findall(
recommendation_extension
)
] ]
for id in referenced_ids:
if (
id not in referenced_motions
and id in all_data["motions/motion"]
):
referenced_motions[id] = {
"title": all_data["motions/motion"][id]["title"],
"identifier": all_data["motions/motion"][id][
"identifier"
],
}
motions.append(motion_object) motions.append(motion_object)
return {"title": motion_block["title"], "motions": motions} return {
"title": motion_block["title"],
"motions": motions,
"referenced_motions": referenced_motions,
}
def register_projector_slides() -> None: def register_projector_slides() -> None: