Separates the field of state and recommendation
- New component for similar situations - Prevents overwriting and discarding of changes, if an 'autoupdate' is triggered
This commit is contained in:
parent
3cd9c5497c
commit
0d52aaaa45
@ -0,0 +1,54 @@
|
||||
<!-- Chip -->
|
||||
<div>
|
||||
<h4 *ngIf="title">{{ title }}</h4>
|
||||
<mat-menu #triggerMenu="matMenu">
|
||||
<ng-container
|
||||
[ngTemplateOutlet]="triggerTemplate">
|
||||
</ng-container>
|
||||
</mat-menu>
|
||||
<os-icon-container
|
||||
iconTooltip="{{ 'Edit' | translate }}"
|
||||
icon="create"
|
||||
[swap]="true"
|
||||
[showIcon]="!editMode && canBeEdited && hasExtension"
|
||||
(iconAction)="changeEditMode()">
|
||||
<mat-basic-chip *ngIf="canBeEdited" [matMenuTriggerFor]="triggerMenu" [ngClass]="classes" class="pointer" disableRipple>
|
||||
{{ chipValue || '–' }}
|
||||
</mat-basic-chip>
|
||||
<mat-basic-chip *ngIf="!canBeEdited" [ngClass]="classes" disableRipple>
|
||||
{{ chipValue }}
|
||||
</mat-basic-chip>
|
||||
</os-icon-container>
|
||||
</div>
|
||||
|
||||
<!-- Extension field -->
|
||||
<div
|
||||
*ngIf="hasExtension && editMode"
|
||||
class="spacer-top-10 extension-container"
|
||||
>
|
||||
<mat-form-field>
|
||||
<input
|
||||
matInput
|
||||
[(ngModel)]="inputControl"
|
||||
placeholder="{{ extensionLabel }}"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<os-search-value-selector
|
||||
*ngIf="searchList"
|
||||
ngDefaultControl
|
||||
[form]="extensionFieldForm"
|
||||
[formControl]="extensionFieldForm.get('list')"
|
||||
[fullWidth]="true"
|
||||
[multiple]="false"
|
||||
[InputListValues]="searchList"
|
||||
[listname]="searchListLabel"
|
||||
></os-search-value-selector>
|
||||
|
||||
<button mat-button (click)="changeEditMode(true)">{{ 'Save' | translate }}</button>
|
||||
<button mat-button (click)="changeEditMode()">{{ 'Cancel' | translate }}</button>
|
||||
</div>
|
||||
|
||||
<!-- Optional template for the menu -->
|
||||
<ng-template #triggerTemplate>
|
||||
<ng-content select=".trigger-menu"></ng-content>
|
||||
</ng-template>
|
@ -0,0 +1,3 @@
|
||||
.extension-container .mat-form-field {
|
||||
display: block;
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ExtensionFieldComponent } from './extension-field.component';
|
||||
import { E2EImportsModule } from 'e2e-imports.module';
|
||||
|
||||
describe('ExtensionFieldComponent', () => {
|
||||
let component: ExtensionFieldComponent;
|
||||
let fixture: ComponentFixture<ExtensionFieldComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ExtensionFieldComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,177 @@
|
||||
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { FormGroup, FormBuilder } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'os-extension-field',
|
||||
templateUrl: './extension-field.component.html',
|
||||
styleUrls: ['./extension-field.component.scss']
|
||||
})
|
||||
export class ExtensionFieldComponent implements OnInit {
|
||||
/**
|
||||
* Optional additional classes for the `mat-chip`.
|
||||
*/
|
||||
@Input()
|
||||
public classes: string | string[] | object = 'bluegrey';
|
||||
|
||||
/**
|
||||
* Title for this component.
|
||||
*/
|
||||
@Input()
|
||||
public title: string;
|
||||
|
||||
/**
|
||||
* Value of the chip.
|
||||
*/
|
||||
@Input()
|
||||
public chipValue: string;
|
||||
|
||||
/**
|
||||
* Boolean, whether the extension should be shown.
|
||||
*/
|
||||
@Input()
|
||||
public hasExtension = false;
|
||||
|
||||
/**
|
||||
* Optional label for the input.
|
||||
*/
|
||||
@Input()
|
||||
public extensionLabel: string;
|
||||
|
||||
/**
|
||||
* Optional label for the search-list.
|
||||
*/
|
||||
@Input()
|
||||
public searchListLabel: string;
|
||||
|
||||
/**
|
||||
* BehaviourSubject for the search-list.
|
||||
*/
|
||||
@Input()
|
||||
public searchList: BehaviorSubject<object[]>;
|
||||
|
||||
/**
|
||||
* Boolean, whether the input and the search-list can be changed.
|
||||
*/
|
||||
@Input()
|
||||
public canBeEdited = true;
|
||||
|
||||
/**
|
||||
* Boolean, whether the list should fire events, if it changes.
|
||||
*/
|
||||
@Input()
|
||||
public listSubmitOnChange = false;
|
||||
|
||||
/**
|
||||
* Boolean, whether to append the value from list to the input.
|
||||
*/
|
||||
@Input()
|
||||
public appendValueToInput = true;
|
||||
|
||||
/**
|
||||
* Prefix, if the value from list should be appended to the input.
|
||||
*/
|
||||
@Input()
|
||||
public listValuePrefix = '';
|
||||
|
||||
/**
|
||||
* Suffix, if the value from list should be appended to the input.
|
||||
*/
|
||||
@Input()
|
||||
public listValueSuffix = '';
|
||||
|
||||
/**
|
||||
* Initial value of the input-field.
|
||||
*/
|
||||
@Input()
|
||||
public inputValue: string;
|
||||
|
||||
/**
|
||||
* EventEmitter, when clicking on the 'save'-button.
|
||||
*/
|
||||
@Output()
|
||||
public success: EventEmitter<string | object> = new EventEmitter();
|
||||
|
||||
/**
|
||||
* EventEmitter, if the list has changed.
|
||||
*/
|
||||
@Output()
|
||||
public listChange: EventEmitter<number> = new EventEmitter();
|
||||
|
||||
/**
|
||||
* Model for the input-field.
|
||||
*/
|
||||
public inputControl = '';
|
||||
|
||||
/**
|
||||
* FormGroup for the search-list.
|
||||
*/
|
||||
public extensionFieldForm: FormGroup;
|
||||
|
||||
/**
|
||||
* Boolean to decide, whether to open the extension-input and search-list.
|
||||
*/
|
||||
public editMode = false;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param fb The FormBuilder
|
||||
*/
|
||||
public constructor(private fb: FormBuilder) {}
|
||||
|
||||
/**
|
||||
* OnInit-method.
|
||||
*/
|
||||
public ngOnInit(): void {
|
||||
this.initInput();
|
||||
this.extensionFieldForm = this.fb.group({
|
||||
list: this.searchList ? [[]] : undefined
|
||||
});
|
||||
|
||||
this.extensionFieldForm.get('list').valueChanges.subscribe((value: number) => {
|
||||
if (this.listSubmitOnChange) {
|
||||
this.listChange.emit(value);
|
||||
}
|
||||
if (this.appendValueToInput) {
|
||||
this.inputControl = this.inputControl.concat(
|
||||
`[${this.listValuePrefix}${value}${this.listValueSuffix}]`
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to switch to or from editing-mode.
|
||||
*
|
||||
* @param save Boolean, whether the changes should be saved or resetted.
|
||||
*/
|
||||
public changeEditMode(save: boolean = false): void {
|
||||
if (save) {
|
||||
this.sendSuccess();
|
||||
} else {
|
||||
this.initInput();
|
||||
}
|
||||
this.editMode = !this.editMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the value of the input.
|
||||
*/
|
||||
public initInput(): void {
|
||||
this.inputControl = this.inputValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to execute, when the values are saved.
|
||||
*/
|
||||
public sendSuccess(): void {
|
||||
if (this.success) {
|
||||
const submitMessage =
|
||||
this.listSubmitOnChange || this.appendValueToInput || !this.searchList
|
||||
? this.inputControl
|
||||
: { extensionInput: this.inputControl, extensionList: this.extensionFieldForm.get('list').value };
|
||||
this.success.emit(submitMessage);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,13 @@
|
||||
<mat-icon *ngIf="!swap">{{ icon }}</mat-icon>
|
||||
<mat-icon
|
||||
*ngIf="!swap && showIcon"
|
||||
[matTooltip]="iconTooltip"
|
||||
[class]="iconAction ? 'pointer' : ''"
|
||||
(click)="iconClick()">{{ icon }}</mat-icon>
|
||||
<span class="content-node">
|
||||
<ng-content></ng-content>
|
||||
</span>
|
||||
<mat-icon *ngIf="swap">{{ icon }}</mat-icon>
|
||||
<mat-icon
|
||||
*ngIf="swap && showIcon"
|
||||
[matTooltip]="iconTooltip"
|
||||
[class]="iconAction ? 'pointer' : ''"
|
||||
(click)="iconClick()">{{ icon }}</mat-icon>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Component, Input, HostBinding } from '@angular/core';
|
||||
import { Component, Input, HostBinding, Output, EventEmitter } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'os-icon-container',
|
||||
@ -37,4 +37,29 @@ export class IconContainerComponent {
|
||||
*/
|
||||
@Input()
|
||||
public swap = false;
|
||||
|
||||
/**
|
||||
* Boolean to decide, when to show the icon.
|
||||
*/
|
||||
@Input()
|
||||
public showIcon = true;
|
||||
|
||||
/**
|
||||
* Optional string as tooltip for icon.
|
||||
*/
|
||||
@Input()
|
||||
public iconTooltip: string;
|
||||
|
||||
/**
|
||||
* Optional action for clicking on the icon.
|
||||
*/
|
||||
@Output()
|
||||
public iconAction: EventEmitter<any> = new EventEmitter();
|
||||
|
||||
/**
|
||||
* Function executed, when the icon is clicked.
|
||||
*/
|
||||
public iconClick(): void {
|
||||
this.iconAction.emit();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<mat-form-field [formGroup]="form">
|
||||
<mat-form-field [formGroup]="form" [style.display]="fullWidth ? 'block' : 'inline-block'">
|
||||
<mat-select [formControl]="formControl" placeholder="{{ listname | translate }}" [multiple]="multiple" #thisSelector>
|
||||
<ngx-mat-select-search [formControl]="filterControl"></ngx-mat-select-search>
|
||||
<div *ngIf="!multiple && includeNone">
|
||||
|
@ -76,12 +76,21 @@ export class SearchValueSelectorComponent implements OnInit, OnDestroy {
|
||||
@Input()
|
||||
public includeNone = false;
|
||||
|
||||
/**
|
||||
* Boolean, whether the component should be rendered with full width.
|
||||
*/
|
||||
@Input()
|
||||
public fullWidth = false;
|
||||
|
||||
/**
|
||||
* The inputlist subject. Subscribes to it and updates the selector, if the subject
|
||||
* changes its values.
|
||||
*/
|
||||
@Input()
|
||||
public set InputListValues(value: BehaviorSubject<Selectable[]>) {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
// unsubscribe to old subscription.
|
||||
if (this._inputListSubscription) {
|
||||
this._inputListSubscription.unsubscribe();
|
||||
|
@ -91,6 +91,7 @@ import { BlockTileComponent } from './components/block-tile/block-tile.component
|
||||
import { IconContainerComponent } from './components/icon-container/icon-container.component';
|
||||
import { ListViewTableComponent } from './components/list-view-table/list-view-table.component';
|
||||
import { AgendaContentObjectFormComponent } from './components/agenda-content-object-form/agenda-content-object-form.component';
|
||||
import { ExtensionFieldComponent } from './components/extension-field/extension-field.component';
|
||||
|
||||
/**
|
||||
* Share Module for all "dumb" components and pipes.
|
||||
@ -224,7 +225,8 @@ import { AgendaContentObjectFormComponent } from './components/agenda-content-ob
|
||||
PblNgridModule,
|
||||
PblNgridMaterialModule,
|
||||
ListViewTableComponent,
|
||||
AgendaContentObjectFormComponent
|
||||
AgendaContentObjectFormComponent,
|
||||
ExtensionFieldComponent
|
||||
],
|
||||
declarations: [
|
||||
PermsDirective,
|
||||
@ -259,7 +261,8 @@ import { AgendaContentObjectFormComponent } from './components/agenda-content-ob
|
||||
BlockTileComponent,
|
||||
IconContainerComponent,
|
||||
ListViewTableComponent,
|
||||
AgendaContentObjectFormComponent
|
||||
AgendaContentObjectFormComponent,
|
||||
ExtensionFieldComponent
|
||||
],
|
||||
providers: [
|
||||
{ provide: DateAdapter, useClass: OpenSlidesDateAdapter },
|
||||
|
@ -242,12 +242,20 @@
|
||||
|
||||
<!-- Set State -->
|
||||
<div *ngIf="!editMotion">
|
||||
<h4 translate>State</h4>
|
||||
<mat-menu #stateMenu="matMenu">
|
||||
<os-extension-field
|
||||
title="{{ 'State' | translate }}"
|
||||
[canBeEdited]="perms.isAllowed('change_state', motion)"
|
||||
[hasExtension]="motion.state && motion.state.show_state_extension_field"
|
||||
[chipValue]="stateLabel"
|
||||
[inputValue]="newStateExtension"
|
||||
[classes]="motion.stateCssColor"
|
||||
extensionLabel="{{ 'Extension' | translate }}"
|
||||
(success)="setStateExtension($event)">
|
||||
<span class="trigger-menu">
|
||||
<button *ngFor="let state of motion.nextStates" mat-menu-item (click)="setState(state.id)">
|
||||
{{ state.name | translate }} <span *ngIf="state.show_state_extension_field"> ...</span>
|
||||
</button>
|
||||
<div *ngIf="perms.isAllowed('change_metadata', motion)">
|
||||
<div>
|
||||
<mat-divider *ngIf="motion.nextStates.length > 0"></mat-divider>
|
||||
<button *ngFor="let state of motion.previousStates" mat-menu-item (click)="setState(state.id)">
|
||||
<mat-icon>arrow_back</mat-icon> {{ state.name | translate }}
|
||||
@ -257,27 +265,24 @@
|
||||
<mat-icon>replay</mat-icon> {{ 'Reset state' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</mat-menu>
|
||||
<div *ngIf="perms.isAllowed('change_state', motion)">
|
||||
<mat-basic-chip [matMenuTriggerFor]="stateMenu" [ngClass]="motion.stateCssColor" disableRipple>
|
||||
{{ stateLabel }}
|
||||
</mat-basic-chip>
|
||||
<div *ngIf="motion.state && motion.state.show_state_extension_field" class="spacer-top-10">
|
||||
<mat-form-field>
|
||||
<input matInput placeholder="{{ 'Extension' | translate }}" [(ngModel)]="newStateExtension" />
|
||||
</mat-form-field>
|
||||
<button mat-icon-button (click)="setStateExtension()"><mat-icon>check</mat-icon></button>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="!perms.isAllowed('change_state', motion)">
|
||||
<mat-basic-chip [ngClass]="motion.stateCssColor" disableRipple> {{ stateLabel }} </mat-basic-chip>
|
||||
</div>
|
||||
</span>
|
||||
</os-extension-field>
|
||||
</div>
|
||||
|
||||
<!-- Recommendation -->
|
||||
<div *ngIf="recommender && !editMotion">
|
||||
<h4 *ngIf="perms.isAllowed('change_metadata', motion) || recommendationLabel">{{ recommender }}</h4>
|
||||
<mat-menu #recommendationMenu="matMenu">
|
||||
<os-extension-field
|
||||
title="{{ recommender }}"
|
||||
[inputValue]="recommendationStateExtension"
|
||||
[canBeEdited]="perms.isAllowed('change_metadata', motion)"
|
||||
[chipValue]="recommendationLabel"
|
||||
[hasExtension]="motion.recommendation && motion.recommendation.show_recommendation_extension_field"
|
||||
extensionLabel="{{ 'Extension' | translate }}"
|
||||
[searchList]="motionObserver"
|
||||
searchListLabel="{{ 'Motions' | translate }}"
|
||||
listValuePrefix="motion:"
|
||||
(success)="setRecommendationExtension($event)">
|
||||
<span class="trigger-menu">
|
||||
<button
|
||||
*ngFor="let recommendation of motion.possibleRecommendations"
|
||||
mat-menu-item
|
||||
@ -289,45 +294,13 @@
|
||||
<mat-divider></mat-divider>
|
||||
<button
|
||||
mat-menu-item
|
||||
*ngIf="perms.isAllowed('change_metadata', motion)"
|
||||
(click)="setRecommendation(null)"
|
||||
>
|
||||
<mat-icon>replay</mat-icon> {{ 'Reset recommendation' | translate }}
|
||||
</button>
|
||||
</mat-menu>
|
||||
<div *ngIf="perms.isAllowed('change_metadata', motion)">
|
||||
<mat-basic-chip [matMenuTriggerFor]="recommendationMenu" class="bluegrey" disableRipple>
|
||||
{{ recommendationLabel || '–' }}
|
||||
</mat-basic-chip>
|
||||
<div
|
||||
*ngIf="motion.recommendation && motion.recommendation.show_recommendation_extension_field"
|
||||
class="spacer-top-10"
|
||||
>
|
||||
<form [formGroup]="recommendationExtensionForm">
|
||||
<mat-form-field>
|
||||
<input
|
||||
matInput
|
||||
[formControl]="recommendationExtensionForm.get('recoExtension')"
|
||||
placeholder="{{ 'Extension' | translate }}"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<button mat-icon-button (click)="setRecommendationExtension()">
|
||||
<mat-icon>check</mat-icon>
|
||||
</button>
|
||||
<os-search-value-selector
|
||||
ngDefaultControl
|
||||
[form]="recommendationExtensionForm"
|
||||
[formControl]="recommendationExtensionForm.get('motion_id')"
|
||||
[multiple]="false"
|
||||
listname="{{ 'Motions' | translate }}"
|
||||
[InputListValues]="motionObserver"
|
||||
></os-search-value-selector>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="!perms.isAllowed('change_metadata', motion) && recommendationLabel">
|
||||
<mat-basic-chip class="bluegrey" disableRipple> {{ recommendationLabel }} </mat-basic-chip>
|
||||
</div>
|
||||
</span>
|
||||
</os-extension-field>
|
||||
|
||||
<button
|
||||
mat-stroked-button
|
||||
*ngIf="canFollowRecommendation()"
|
||||
|
@ -79,11 +79,6 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
*/
|
||||
public contentForm: FormGroup;
|
||||
|
||||
/**
|
||||
* To search other motions as extension via search value selector
|
||||
*/
|
||||
public recommendationExtensionForm: FormGroup;
|
||||
|
||||
/**
|
||||
* Determine if the motion is edited
|
||||
*/
|
||||
@ -354,6 +349,11 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
*/
|
||||
public newStateExtension = '';
|
||||
|
||||
/**
|
||||
* State extension label for the recommendation.
|
||||
*/
|
||||
public recommendationStateExtension = '';
|
||||
|
||||
/**
|
||||
* Constant to identify the notification-message.
|
||||
*/
|
||||
@ -616,6 +616,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
super.setTitle(title);
|
||||
this.motion = motion;
|
||||
this.newStateExtension = this.motion.stateExtension;
|
||||
this.recommendationStateExtension = this.motion.recommendationExtension;
|
||||
if (!this.editMotion) {
|
||||
this.patchForm(this.motion);
|
||||
}
|
||||
@ -707,7 +708,6 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
const statuteAmendmentFieldName = 'statute_amendment';
|
||||
contentPatch[statuteAmendmentFieldName] = formMotion.isStatuteAmendment();
|
||||
this.contentForm.patchValue(contentPatch);
|
||||
this.recommendationExtensionForm.get('recoExtension').setValue(this.motion.recommendationExtension);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -752,17 +752,6 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
return value.match(/[^\d]/) !== null || parseInt(value, 10) >= maxLineNumber;
|
||||
}
|
||||
})();
|
||||
|
||||
// create the search motion form
|
||||
this.recommendationExtensionForm = this.formBuilder.group({
|
||||
motion_id: [],
|
||||
recoExtension: []
|
||||
});
|
||||
|
||||
// Detect changes in in search motion form
|
||||
this.recommendationExtensionForm.get('motion_id').valueChanges.subscribe(change => {
|
||||
this.addMotionExtension(change);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1301,18 +1290,8 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
* triggers the update this motion's state extension according to the current string
|
||||
* in {@link newStateExtension}
|
||||
*/
|
||||
public setStateExtension(): void {
|
||||
this.repo.setStateExtension(this.motion, this.newStateExtension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an extension in the shape: [Motion:id] to the recoExtension form control
|
||||
*
|
||||
* @param id the ID of a selected motion returned by a search value selector
|
||||
*/
|
||||
public addMotionExtension(id: number): void {
|
||||
const recoExtensionValue = this.recommendationExtensionForm.get('recoExtension').value || '';
|
||||
this.recommendationExtensionForm.get('recoExtension').setValue(`${recoExtensionValue}[motion:${id}]`);
|
||||
public setStateExtension(nextExtension: string): void {
|
||||
this.repo.setStateExtension(this.motion, nextExtension);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1328,8 +1307,8 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
* triggers the update this motion's recommendation extension according to the current string
|
||||
* in {@link newRecommendationExtension}
|
||||
*/
|
||||
public setRecommendationExtension(): void {
|
||||
this.repo.setRecommendationExtension(this.motion, this.recommendationExtensionForm.get('recoExtension').value);
|
||||
public setRecommendationExtension(nextExtension: string): void {
|
||||
this.repo.setRecommendationExtension(this.motion, nextExtension);
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user