Initial polling
This commit is contained in:
parent
1b761d31c0
commit
8d77c0495b
@ -21,15 +21,16 @@
|
||||
</div>
|
||||
|
||||
<!-- Parent item -->
|
||||
<div *ngIf="itemObserver.value.length > 0">
|
||||
<os-search-value-selector
|
||||
ngDefaultControl
|
||||
[formControl]="form.get('agenda_parent_id')"
|
||||
[multiple]="false"
|
||||
[includeNone]="true"
|
||||
listname="{{ 'Parent agenda item' | translate }}"
|
||||
[inputListValues]="itemObserver"
|
||||
></os-search-value-selector>
|
||||
<div *ngIf="itemObserver.value.length > 0" [formGroup]="form">
|
||||
<mat-form-field>
|
||||
<os-search-value-selector
|
||||
formControlName="agenda_parent_id"
|
||||
[multiple]="false"
|
||||
[includeNone]="true"
|
||||
placeholder="{{ 'Parent agenda item' | translate }}"
|
||||
[inputListValues]="itemObserver"
|
||||
></os-search-value-selector>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
@ -1,12 +1,13 @@
|
||||
<div class="attachment-container" *ngIf="controlName">
|
||||
<os-search-value-selector
|
||||
class="selector"
|
||||
ngDefaultControl
|
||||
[multiple]="true"
|
||||
listname="{{ 'Attachments' | translate }}"
|
||||
[formControl]="controlName"
|
||||
[inputListValues]="mediaFileList"
|
||||
></os-search-value-selector>
|
||||
<div class="attachment-container" *ngIf="contentForm">
|
||||
<mat-form-field>
|
||||
<os-search-value-selector
|
||||
class="selector"
|
||||
[multiple]="true"
|
||||
placeholder="{{ 'Attachments' | translate }}"
|
||||
[formControl]="contentForm"
|
||||
[inputListValues]="mediaFileList"
|
||||
></os-search-value-selector>
|
||||
</mat-form-field>
|
||||
<button type="button" mat-icon-button (click)="openUploadDialog(uploadDialog)" *osPerms="'mediafiles.can_manage'">
|
||||
<mat-icon>cloud_upload</mat-icon>
|
||||
</button>
|
||||
|
@ -1,44 +1,68 @@
|
||||
import { Component, EventEmitter, Input, OnInit, Output, TemplateRef } from '@angular/core';
|
||||
import { ControlValueAccessor, FormControl } from '@angular/forms';
|
||||
import { MatDialog } from '@angular/material';
|
||||
import { FocusMonitor } from '@angular/cdk/a11y';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
OnInit,
|
||||
Optional,
|
||||
Output,
|
||||
Self,
|
||||
TemplateRef
|
||||
} from '@angular/core';
|
||||
import { FormBuilder, NgControl } from '@angular/forms';
|
||||
import { MatDialog, MatFormFieldControl } from '@angular/material';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { MediafileRepositoryService } from 'app/core/repositories/mediafiles/mediafile-repository.service';
|
||||
import { BaseFormControlComponent } from 'app/shared/models/base/base-form-control';
|
||||
import { mediumDialogSettings } from 'app/shared/utils/dialog-settings';
|
||||
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
|
||||
|
||||
@Component({
|
||||
selector: 'os-attachment-control',
|
||||
templateUrl: './attachment-control.component.html',
|
||||
styleUrls: ['./attachment-control.component.scss']
|
||||
styleUrls: ['./attachment-control.component.scss'],
|
||||
providers: [{ provide: MatFormFieldControl, useExisting: AttachmentControlComponent }],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class AttachmentControlComponent implements OnInit, ControlValueAccessor {
|
||||
export class AttachmentControlComponent extends BaseFormControlComponent<ViewMediafile[]> implements OnInit {
|
||||
/**
|
||||
* Output for an error handler
|
||||
*/
|
||||
@Output()
|
||||
public errorHandler: EventEmitter<string> = new EventEmitter();
|
||||
|
||||
/**
|
||||
* The form-control name to access the value for the form-control
|
||||
*/
|
||||
@Input()
|
||||
public controlName: FormControl;
|
||||
|
||||
/**
|
||||
* The file list that is necessary for the `SearchValueSelector`
|
||||
*/
|
||||
public mediaFileList: Observable<ViewMediafile[]>;
|
||||
|
||||
public get empty(): boolean {
|
||||
return !this.contentForm.value.length;
|
||||
}
|
||||
public get controlType(): string {
|
||||
return 'attachment-control';
|
||||
}
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*
|
||||
* @param dialogService Reference to the `MatDialog`
|
||||
* @param mediaService Reference for the `MediaFileRepositoryService`
|
||||
*/
|
||||
public constructor(private dialogService: MatDialog, private mediaService: MediafileRepositoryService) {}
|
||||
public constructor(
|
||||
fb: FormBuilder,
|
||||
fm: FocusMonitor,
|
||||
element: ElementRef<HTMLElement>,
|
||||
@Optional() @Self() public ngControl: NgControl,
|
||||
private dialogService: MatDialog,
|
||||
private mediaService: MediafileRepositoryService
|
||||
) {
|
||||
super(fb, fm, element, ngControl);
|
||||
}
|
||||
|
||||
/**
|
||||
* On init method
|
||||
@ -64,11 +88,9 @@ export class AttachmentControlComponent implements OnInit, ControlValueAccessor
|
||||
* @param fileIDs a list with the ids of the uploaded files
|
||||
*/
|
||||
public uploadSuccess(fileIDs: number[]): void {
|
||||
if (this.controlName) {
|
||||
const newValues = [...this.controlName.value, ...fileIDs];
|
||||
this.controlName.setValue(newValues);
|
||||
this.dialogService.closeAll();
|
||||
}
|
||||
const newValues = [...this.contentForm.value, ...fileIDs];
|
||||
this.updateForm(newValues);
|
||||
this.dialogService.closeAll();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,29 +102,13 @@ export class AttachmentControlComponent implements OnInit, ControlValueAccessor
|
||||
this.errorHandler.emit(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to write a new value to the form.
|
||||
* Satisfy the interface.
|
||||
*
|
||||
* @param value The new value for this form.
|
||||
*/
|
||||
public writeValue(value: any): void {
|
||||
if (value && this.controlName) {
|
||||
this.controlName.setValue(value);
|
||||
}
|
||||
public onContainerClick(event: MouseEvent): void {
|
||||
// TODO: implement
|
||||
}
|
||||
protected initializeForm(): void {
|
||||
this.contentForm = this.fb.control([]);
|
||||
}
|
||||
protected updateForm(value: ViewMediafile[]): void {
|
||||
this.contentForm.setValue(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function executed when the control's value changed.
|
||||
*
|
||||
* @param fn the function that is executed.
|
||||
*/
|
||||
public registerOnChange(fn: any): void {}
|
||||
|
||||
/**
|
||||
* To satisfy the interface
|
||||
*
|
||||
* @param fn the registered callback function for onBlur-events.
|
||||
*/
|
||||
public registerOnTouched(fn: any): void {}
|
||||
}
|
||||
|
@ -38,14 +38,14 @@
|
||||
(keydown)="keyDownFunction($event)"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<os-search-value-selector
|
||||
*ngIf="searchList"
|
||||
ngDefaultControl
|
||||
[formControl]="extensionFieldForm.get('list')"
|
||||
[fullWidth]="true"
|
||||
[inputListValues]="searchList"
|
||||
[listname]="searchListLabel"
|
||||
></os-search-value-selector>
|
||||
<mat-form-field *ngIf="searchList">
|
||||
<os-search-value-selector
|
||||
formControlName="list"
|
||||
[fullWidth]="true"
|
||||
[inputListValues]="searchList"
|
||||
[placeholder]="searchListLabel"
|
||||
></os-search-value-selector>
|
||||
</mat-form-field>
|
||||
|
||||
<button mat-button (click)="changeEditMode(true)">{{ 'Save' | translate }}</button>
|
||||
<button mat-button (click)="changeEditMode()">{{ 'Cancel' | translate }}</button>
|
||||
|
@ -13,16 +13,17 @@
|
||||
</div>
|
||||
|
||||
<!-- Directory selector, if no external directory is provided -->
|
||||
<div *ngIf="showDirectorySelector">
|
||||
<os-search-value-selector
|
||||
ngDefaultControl
|
||||
[formControl]="directorySelectionForm.get('parent_id')"
|
||||
[multiple]="false"
|
||||
[includeNone]="true"
|
||||
[noneTitle]="'Base folder'"
|
||||
listname="{{ 'Parent directory' | translate }}"
|
||||
[inputListValues]="directoryBehaviorSubject"
|
||||
></os-search-value-selector>
|
||||
<div *ngIf="showDirectorySelector" [formGroup]="directorySelectionForm">
|
||||
<mat-form-field>
|
||||
<os-search-value-selector
|
||||
formControlName="parent_id"
|
||||
[multiple]="false"
|
||||
[includeNone]="true"
|
||||
[noneTitle]="'Base folder'"
|
||||
placeholder="{{ 'Parent directory' | translate }}"
|
||||
[inputListValues]="directoryBehaviorSubject"
|
||||
></os-search-value-selector>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@ -69,14 +70,15 @@
|
||||
<!-- Access groups -->
|
||||
<ng-container matColumnDef="access_groups">
|
||||
<th mat-header-cell *matHeaderCellDef><span translate>Access groups</span></th>
|
||||
<td mat-cell *matCellDef="let file">
|
||||
<os-search-value-selector
|
||||
ngDefaultControl
|
||||
[formControl]="file.form.get('access_groups_id')"
|
||||
[multiple]="true"
|
||||
listname="{{ 'Access groups' | translate }}"
|
||||
[inputListValues]="groupsBehaviorSubject"
|
||||
></os-search-value-selector>
|
||||
<td mat-cell *matCellDef="let file" [formGroup]="file.form">
|
||||
<mat-form-field>
|
||||
<os-search-value-selector
|
||||
formControlName="access_groups_id"
|
||||
[multiple]="true"
|
||||
placeholder="{{ 'Access groups' | translate }}"
|
||||
[inputListValues]="groupsBehaviorSubject"
|
||||
></os-search-value-selector>
|
||||
</mat-form-field>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
|
@ -90,7 +90,8 @@ export class MediaUploadContentComponent implements OnInit {
|
||||
|
||||
public get selectedDirectoryId(): number | null {
|
||||
if (this.showDirectorySelector) {
|
||||
return this.directorySelectionForm.controls.parent_id.value;
|
||||
const parent = this.directorySelectionForm.controls.parent_id;
|
||||
return !parent.value || typeof parent.value !== 'number' ? null : parent.value;
|
||||
} else {
|
||||
return this.directoryId;
|
||||
}
|
||||
@ -110,7 +111,7 @@ export class MediaUploadContentComponent implements OnInit {
|
||||
this.directoryBehaviorSubject = this.repo.getDirectoryBehaviorSubject();
|
||||
this.groupsBehaviorSubject = this.groupRepo.getViewModelListBehaviorSubject();
|
||||
this.directorySelectionForm = this.formBuilder.group({
|
||||
parent_id: []
|
||||
parent_id: null
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,19 +1,12 @@
|
||||
<mat-form-field [style.display]="fullWidth ? 'block' : 'inline-block'">
|
||||
<mat-select
|
||||
[formControl]="formControl"
|
||||
placeholder="{{ listname | translate }}"
|
||||
[multiple]="multiple"
|
||||
#thisSelector
|
||||
>
|
||||
<ngx-mat-select-search ngModel (ngModelChange)="onSearch($event)"></ngx-mat-select-search>
|
||||
<div *ngIf="!multiple && includeNone">
|
||||
<mat-option [value]="null">
|
||||
{{ noneTitle | translate }}
|
||||
</mat-option>
|
||||
<mat-divider></mat-divider>
|
||||
</div>
|
||||
<mat-option *ngFor="let selectedItem of getFilteredItems()" [value]="selectedItem.id">
|
||||
{{ selectedItem.getTitle() | translate }}
|
||||
<mat-select [formControl]="contentForm" [multiple]="multiple">
|
||||
<ngx-mat-select-search [formControl]="searchValue"></ngx-mat-select-search>
|
||||
<ng-container *ngIf="!multiple && includeNone">
|
||||
<mat-option [value]="null">
|
||||
{{ noneTitle | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-divider></mat-divider>
|
||||
</ng-container>
|
||||
<mat-option *ngFor="let selectedItem of getFilteredItems()" [value]="selectedItem.id">
|
||||
{{ selectedItem.getTitle() | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Component, ViewChild } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { FormBuilder, FormControl } from '@angular/forms';
|
||||
import { FormBuilder } from '@angular/forms';
|
||||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
@ -43,10 +43,8 @@ describe('SearchValueSelectorComponent', () => {
|
||||
hostComponent.searchValueSelectorComponent.inputListValues = subject;
|
||||
|
||||
const formBuilder: FormBuilder = TestBed.get(FormBuilder);
|
||||
const formGroup = formBuilder.group({
|
||||
testArray: []
|
||||
});
|
||||
hostComponent.searchValueSelectorComponent.formControl = <FormControl>formGroup.get('testArray');
|
||||
const formControl = formBuilder.control([]);
|
||||
hostComponent.searchValueSelectorComponent.contentForm = formControl;
|
||||
|
||||
hostFixture.detectChanges();
|
||||
expect(hostComponent.searchValueSelectorComponent).toBeTruthy();
|
||||
|
@ -1,31 +1,38 @@
|
||||
import { ChangeDetectionStrategy, Component, Input, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { MatSelect } from '@angular/material';
|
||||
import { FocusMonitor } from '@angular/cdk/a11y';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
Input,
|
||||
Optional,
|
||||
Self
|
||||
} from '@angular/core';
|
||||
import { FormBuilder, FormControl, NgControl } from '@angular/forms';
|
||||
import { MatFormFieldControl } from '@angular/material';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { Observable } from 'rxjs';
|
||||
import { auditTime } from 'rxjs/operators';
|
||||
|
||||
import { BaseFormControlComponent } from 'app/shared/models/base/base-form-control';
|
||||
import { Selectable } from '../selectable';
|
||||
|
||||
/**
|
||||
* Reusable Searchable Value Selector
|
||||
* Searchable Value Selector
|
||||
*
|
||||
* Use `multiple="true"`, `[InputListValues]=myValues`,`[formControl]="myformcontrol"` and `placeholder={{listname}}` to pass the Values and Listname
|
||||
* Use `multiple="true"`, `[inputListValues]=myValues`,`formControlName="myformcontrol"` and `placeholder={{listname}}` to pass the Values and Listname
|
||||
*
|
||||
* ## Examples:
|
||||
*
|
||||
* ### Usage of the selector:
|
||||
*
|
||||
* ngDefaultControl: https://stackoverflow.com/a/39053470
|
||||
*
|
||||
* ```html
|
||||
* <os-search-value-selector
|
||||
* ngDefaultControl
|
||||
* [multiple]="true"
|
||||
* placeholder="Placeholder"
|
||||
* [InputListValues]="myListValues"
|
||||
* [formControl]="myformcontrol">
|
||||
* [inputListValues]="myListValues"
|
||||
* formControlName="myformcontrol">
|
||||
* </os-search-value-selector>
|
||||
* ```
|
||||
*
|
||||
@ -35,24 +42,10 @@ import { Selectable } from '../selectable';
|
||||
selector: 'os-search-value-selector',
|
||||
templateUrl: './search-value-selector.component.html',
|
||||
styleUrls: ['./search-value-selector.component.scss'],
|
||||
providers: [{ provide: MatFormFieldControl, useExisting: SearchValueSelectorComponent }],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class SearchValueSelectorComponent implements OnDestroy {
|
||||
/**
|
||||
* Saves the current subscription to _inputListSubject.
|
||||
*/
|
||||
private _inputListSubscription: Subscription = null;
|
||||
|
||||
/**
|
||||
* Value of the search input
|
||||
*/
|
||||
private searchValue = '';
|
||||
|
||||
/**
|
||||
* All items
|
||||
*/
|
||||
private selectableItems: Selectable[];
|
||||
|
||||
export class SearchValueSelectorComponent extends BaseFormControlComponent<Selectable[]> {
|
||||
/**
|
||||
* Decide if this should be a single or multi-select-field
|
||||
*/
|
||||
@ -83,55 +76,41 @@ export class SearchValueSelectorComponent implements OnDestroy {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
this.selectableItems = value;
|
||||
} else {
|
||||
// unsubscribe to old subscription.
|
||||
if (this._inputListSubscription) {
|
||||
this._inputListSubscription.unsubscribe();
|
||||
}
|
||||
this._inputListSubscription = value.pipe(auditTime(10)).subscribe(items => {
|
||||
this.subscriptions.push(
|
||||
value.pipe(auditTime(10)).subscribe(items => {
|
||||
this.selectableItems = items;
|
||||
if (this.formControl) {
|
||||
!!items && items.length > 0
|
||||
? this.formControl.enable({ emitEvent: false })
|
||||
: this.formControl.disable({ emitEvent: false });
|
||||
if (this.contentForm) {
|
||||
this.disabled = !items || (!!items && !items.length);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Placeholder of the List
|
||||
*/
|
||||
@Input()
|
||||
public listname: string;
|
||||
public searchValue: FormControl;
|
||||
|
||||
public get empty(): boolean {
|
||||
return Array.isArray(this.contentForm.value) ? !this.contentForm.value.length : !this.contentForm.value;
|
||||
}
|
||||
|
||||
public controlType = 'search-value-selector';
|
||||
|
||||
/**
|
||||
* Name of the Form
|
||||
* All items
|
||||
*/
|
||||
@Input()
|
||||
public formControl: FormControl;
|
||||
|
||||
/**
|
||||
* The MultiSelect Component
|
||||
*/
|
||||
@ViewChild('thisSelector', { static: true })
|
||||
public thisSelector: MatSelect;
|
||||
private selectableItems: Selectable[];
|
||||
|
||||
/**
|
||||
* Empty constructor
|
||||
*/
|
||||
public constructor(protected translate: TranslateService) {}
|
||||
|
||||
/**
|
||||
* Unsubscribe on destroing.
|
||||
*/
|
||||
public ngOnDestroy(): void {
|
||||
if (this._inputListSubscription) {
|
||||
this._inputListSubscription.unsubscribe();
|
||||
}
|
||||
public constructor(
|
||||
protected translate: TranslateService,
|
||||
cd: ChangeDetectorRef,
|
||||
fb: FormBuilder,
|
||||
@Optional() @Self() public ngControl: NgControl,
|
||||
fm: FocusMonitor,
|
||||
element: ElementRef<HTMLElement>
|
||||
) {
|
||||
super(fb, fm, element, ngControl);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -141,29 +120,42 @@ export class SearchValueSelectorComponent implements OnDestroy {
|
||||
*/
|
||||
public getFilteredItems(): Selectable[] {
|
||||
if (this.selectableItems) {
|
||||
const searchValue: string = this.searchValue.value.toLowerCase();
|
||||
return this.selectableItems.filter(item => {
|
||||
const idString = '' + item.id;
|
||||
const foundId =
|
||||
idString
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.indexOf(this.searchValue) !== -1;
|
||||
.indexOf(searchValue) !== -1;
|
||||
|
||||
if (foundId) {
|
||||
return true;
|
||||
}
|
||||
const searchableString = this.translate.instant(item.getTitle()).toLowerCase();
|
||||
return searchableString.indexOf(this.searchValue) > -1;
|
||||
|
||||
return (
|
||||
item
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.indexOf(searchValue) > -1
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to set the search value.
|
||||
*
|
||||
* @param searchValue the new value the user is searching for.
|
||||
*/
|
||||
public onSearch(searchValue: string): void {
|
||||
this.searchValue = searchValue.toLowerCase();
|
||||
public onContainerClick(event: MouseEvent): void {
|
||||
if ((event.target as Element).tagName.toLowerCase() !== 'select') {
|
||||
// this.element.nativeElement.querySelector('select').focus();
|
||||
}
|
||||
}
|
||||
|
||||
protected initializeForm(): void {
|
||||
this.contentForm = this.fb.control([]);
|
||||
this.searchValue = this.fb.control('');
|
||||
}
|
||||
|
||||
protected updateForm(value: Selectable[] | null): void {
|
||||
const nextValue = value;
|
||||
this.contentForm.setValue(nextValue);
|
||||
}
|
||||
}
|
||||
|
161
client/src/app/shared/models/base/base-form-control.ts
Normal file
161
client/src/app/shared/models/base/base-form-control.ts
Normal file
@ -0,0 +1,161 @@
|
||||
import { FocusMonitor } from '@angular/cdk/a11y';
|
||||
import { coerceBooleanProperty } from '@angular/cdk/coercion';
|
||||
import { ElementRef, HostBinding, Input, OnDestroy, Optional, Self } from '@angular/core';
|
||||
import { ControlValueAccessor, FormBuilder, FormControl, FormGroup, NgControl } from '@angular/forms';
|
||||
import { MatFormFieldControl } from '@angular/material';
|
||||
|
||||
import { Subject, Subscription } from 'rxjs';
|
||||
|
||||
/**
|
||||
* Abstract class to implement some simple logic and provide the subclass as a controllable form-control in `MatFormField`.
|
||||
*
|
||||
* Please remember to prepare the `providers` in the `@Component`-decorator. Something like:
|
||||
*
|
||||
* ```ts
|
||||
* @Component({
|
||||
* selector: ...,
|
||||
* templateUrl: ...,
|
||||
* styleUrls: [...],
|
||||
* providers: [{ provide: MatFormFieldControl, useExisting: <TheComponent>}]
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export abstract class BaseFormControlComponent<T> extends MatFormFieldControl<T>
|
||||
implements OnDestroy, ControlValueAccessor {
|
||||
public static nextId = 0;
|
||||
|
||||
@HostBinding() public id = `base-form-control-${BaseFormControlComponent.nextId++}`;
|
||||
|
||||
@HostBinding('class.floating') public get shouldLabelFloat(): boolean {
|
||||
return this.focused || !this.empty;
|
||||
}
|
||||
|
||||
@HostBinding('attr.aria-describedby') public describedBy = '';
|
||||
|
||||
@Input()
|
||||
public set value(value: T | null) {
|
||||
this.updateForm(value);
|
||||
this.stateChanges.next();
|
||||
}
|
||||
|
||||
public get value(): T | null {
|
||||
return this.contentForm.value || null;
|
||||
}
|
||||
|
||||
@Input()
|
||||
public set placeholder(placeholder: string) {
|
||||
this._placeholder = placeholder;
|
||||
this.stateChanges.next();
|
||||
}
|
||||
|
||||
public get placeholder(): string {
|
||||
return this._placeholder;
|
||||
}
|
||||
|
||||
@Input()
|
||||
public set required(required: boolean) {
|
||||
this._required = coerceBooleanProperty(required);
|
||||
this.stateChanges.next();
|
||||
}
|
||||
|
||||
public get required(): boolean {
|
||||
return this._required;
|
||||
}
|
||||
|
||||
@Input()
|
||||
public set disabled(disable: boolean) {
|
||||
this._disabled = coerceBooleanProperty(disable);
|
||||
this._disabled ? this.contentForm.disable() : this.contentForm.enable();
|
||||
this.stateChanges.next();
|
||||
}
|
||||
|
||||
public get disabled(): boolean {
|
||||
return this._disabled;
|
||||
}
|
||||
|
||||
public abstract get empty(): boolean;
|
||||
|
||||
public abstract get controlType(): string;
|
||||
|
||||
public contentForm: FormControl | FormGroup;
|
||||
|
||||
public stateChanges = new Subject<void>();
|
||||
|
||||
public errorState = false;
|
||||
|
||||
public focused = false;
|
||||
|
||||
private _placeholder: string;
|
||||
|
||||
private _required = false;
|
||||
|
||||
private _disabled = false;
|
||||
|
||||
protected subscriptions: Subscription[] = [];
|
||||
|
||||
public constructor(
|
||||
protected fb: FormBuilder,
|
||||
protected fm: FocusMonitor,
|
||||
protected element: ElementRef<HTMLElement>,
|
||||
@Optional() @Self() public ngControl: NgControl
|
||||
) {
|
||||
super();
|
||||
|
||||
this.initializeForm();
|
||||
|
||||
if (this.ngControl !== null) {
|
||||
this.ngControl.valueAccessor = this;
|
||||
}
|
||||
|
||||
this.subscriptions.push(
|
||||
fm.monitor(element.nativeElement, true).subscribe(origin => {
|
||||
this.focused = !!origin;
|
||||
this.stateChanges.next();
|
||||
}),
|
||||
this.contentForm.valueChanges.subscribe(nextValue => this.push(nextValue))
|
||||
);
|
||||
}
|
||||
|
||||
public ngOnDestroy(): void {
|
||||
for (const subscription of this.subscriptions) {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
this.subscriptions = [];
|
||||
|
||||
this.fm.stopMonitoring(this.element.nativeElement);
|
||||
|
||||
this.stateChanges.complete();
|
||||
}
|
||||
|
||||
public writeValue(value: T): void {
|
||||
this.value = value;
|
||||
}
|
||||
public registerOnChange(fn: any): void {
|
||||
this._onChange = fn;
|
||||
}
|
||||
public registerOnTouched(fn: any): void {
|
||||
this._onTouched = fn;
|
||||
}
|
||||
public setDisabledState?(isDisabled: boolean): void {
|
||||
this.disabled = isDisabled;
|
||||
}
|
||||
|
||||
public setDescribedByIds(ids: string[]): void {
|
||||
this.describedBy = ids.join(' ');
|
||||
}
|
||||
|
||||
public abstract onContainerClick(event: MouseEvent): void;
|
||||
|
||||
protected _onChange = (value: T) => {};
|
||||
|
||||
protected _onTouched = (value: T) => {};
|
||||
|
||||
protected abstract initializeForm(): void;
|
||||
|
||||
protected abstract updateForm(value: T | null): void;
|
||||
|
||||
protected push(value: T): void {
|
||||
this._onChange(value);
|
||||
this._onTouched(value);
|
||||
}
|
||||
}
|
@ -1,14 +1,18 @@
|
||||
import { BasePoll, BasePollWithoutNestedModels } from '../poll/base-poll';
|
||||
import { MotionOption } from './motion-option';
|
||||
|
||||
export enum MotionPollmethods {
|
||||
'YN' = 'YN',
|
||||
'YNA' = 'YNA'
|
||||
export enum MotionPollMethods {
|
||||
YN = 'YN',
|
||||
YNA = 'YNA'
|
||||
}
|
||||
export const MotionPollMethodsVerbose = {
|
||||
YN: 'Yes/No',
|
||||
YNA: 'Yes/No/Abstain'
|
||||
};
|
||||
|
||||
export interface MotionPollWithoutNestedModels extends BasePollWithoutNestedModels {
|
||||
motion_id: number;
|
||||
pollmethod: MotionPollmethods;
|
||||
pollmethod: MotionPollMethods;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -22,5 +26,9 @@ export class MotionPoll extends BasePoll<MotionPoll, MotionOption> {
|
||||
public constructor(input?: any) {
|
||||
super(MotionPoll.COLLECTIONSTRING, input);
|
||||
}
|
||||
|
||||
public get pollmethodVerbose(): string {
|
||||
return MotionPollMethodsVerbose[this.pollmethod];
|
||||
}
|
||||
}
|
||||
export interface MotionPoll extends MotionPollWithoutNestedModels {}
|
||||
|
@ -8,12 +8,55 @@ export enum PollState {
|
||||
Published
|
||||
}
|
||||
|
||||
export const PollStateVerbose = {
|
||||
1: 'Created',
|
||||
2: 'Started',
|
||||
3: 'Finished',
|
||||
4: 'Published'
|
||||
};
|
||||
|
||||
export enum PollType {
|
||||
Analog = 'analog',
|
||||
Named = 'named',
|
||||
Pseudoanonymous = 'pseudoanonymous'
|
||||
}
|
||||
|
||||
export const PollTypeVerbose = {
|
||||
analog: 'Analog',
|
||||
named: 'Named',
|
||||
pseudoanonymous: 'Pseudoanonymous'
|
||||
};
|
||||
|
||||
export enum PercentBase {
|
||||
YN = 'YN',
|
||||
YNA = 'YNA',
|
||||
Valid = 'valid',
|
||||
Cast = 'cast',
|
||||
Disabled = 'disabled'
|
||||
}
|
||||
|
||||
export const PercentBaseVerbose = {
|
||||
YN: 'Yes/No',
|
||||
YNA: 'Yes/No/Abstain',
|
||||
valid: 'Valid votes',
|
||||
cast: 'Casted votes',
|
||||
disabled: 'Disabled'
|
||||
};
|
||||
|
||||
export enum MajorityMethod {
|
||||
Simple = 'simple',
|
||||
TwoThirds = 'two_thirds',
|
||||
ThreeQuarters = 'three_quarters',
|
||||
Disabled = 'disabled'
|
||||
}
|
||||
|
||||
export const MajorityMethodVerbose = {
|
||||
simple: 'Simple',
|
||||
two_thirds: 'Two Thirds',
|
||||
three_quarters: 'Three Quarters',
|
||||
disabled: 'Disabled'
|
||||
};
|
||||
|
||||
export interface BasePollWithoutNestedModels {
|
||||
state: PollState;
|
||||
type: PollType;
|
||||
@ -23,6 +66,8 @@ export interface BasePollWithoutNestedModels {
|
||||
votescast: number;
|
||||
groups_id: number[];
|
||||
voted_id: number[];
|
||||
majority_method: MajorityMethod;
|
||||
onehundred_percent_base: PercentBase;
|
||||
}
|
||||
|
||||
export abstract class BasePoll<T, O extends BaseOption<any>> extends BaseDecimalModel<T> {
|
||||
@ -31,5 +76,21 @@ export abstract class BasePoll<T, O extends BaseOption<any>> extends BaseDecimal
|
||||
protected getDecimalFields(): (keyof BasePoll<T, O>)[] {
|
||||
return ['votesvalid', 'votesinvalid', 'votescast'];
|
||||
}
|
||||
|
||||
public get stateVerbose(): string {
|
||||
return PollStateVerbose[this.state];
|
||||
}
|
||||
|
||||
public get typeVerbose(): string {
|
||||
return PollTypeVerbose[this.type];
|
||||
}
|
||||
|
||||
public get majorityMethodVerbose(): string {
|
||||
return MajorityMethodVerbose[this.majority_method];
|
||||
}
|
||||
|
||||
public get percentBaseVerbose(): string {
|
||||
return PercentBaseVerbose[this.onehundred_percent_base];
|
||||
}
|
||||
}
|
||||
export interface BasePoll<T, O extends BaseOption<any>> extends BasePollWithoutNestedModels {}
|
||||
|
@ -1,4 +1,10 @@
|
||||
<os-head-bar [nav]="false" [goBack]="true" [editMode]="isSortMode" (cancelEditEvent)="onCancelSorting()" (saveEvent)="onSaveSorting()">
|
||||
<os-head-bar
|
||||
[nav]="false"
|
||||
[goBack]="true"
|
||||
[editMode]="isSortMode"
|
||||
(cancelEditEvent)="onCancelSorting()"
|
||||
(saveEvent)="onSaveSorting()"
|
||||
>
|
||||
<!-- Title -->
|
||||
<div class="title-slot">
|
||||
<h2>
|
||||
@ -8,7 +14,15 @@
|
||||
</h2>
|
||||
</div>
|
||||
<div class="menu-slot" *osPerms="['agenda.can_manage_list_of_speakers', 'core.can_manage_projector']">
|
||||
<button type="button" mat-icon-button matTooltip="{{ 'Re-add last speaker' | translate }}" (click)="readdLastSpeaker()" [disabled]="!finishedSpeakers || !finishedSpeakers.length"><mat-icon>undo</mat-icon></button>
|
||||
<button
|
||||
type="button"
|
||||
mat-icon-button
|
||||
matTooltip="{{ 'Re-add last speaker' | translate }}"
|
||||
(click)="readdLastSpeaker()"
|
||||
[disabled]="!finishedSpeakers || !finishedSpeakers.length"
|
||||
>
|
||||
<mat-icon>undo</mat-icon>
|
||||
</button>
|
||||
<button type="button" mat-icon-button [matMenuTriggerFor]="speakerMenu"><mat-icon>more_vert</mat-icon></button>
|
||||
</div>
|
||||
</os-head-bar>
|
||||
@ -77,12 +91,7 @@
|
||||
|
||||
<!-- Waiting speakers -->
|
||||
<div class="waiting-list" *ngIf="speakers && speakers.length > 0">
|
||||
<os-sorting-list
|
||||
[input]="speakers"
|
||||
[live]="!isSortMode"
|
||||
[count]="true"
|
||||
[enable]="opCanManage() && isSortMode"
|
||||
>
|
||||
<os-sorting-list [input]="speakers" [live]="!isSortMode" [count]="true" [enable]="opCanManage() && isSortMode">
|
||||
<!-- implicit speaker references into the component using ng-template slot -->
|
||||
<ng-template let-speaker>
|
||||
<span *osPerms="'agenda.can_manage_list_of_speakers'">
|
||||
@ -124,13 +133,14 @@
|
||||
<!-- Search for speakers -->
|
||||
<div *osPerms="'agenda.can_manage_list_of_speakers'">
|
||||
<form *ngIf="filteredUsers && filteredUsers.value.length > 0" [formGroup]="addSpeakerForm">
|
||||
<os-search-value-selector
|
||||
class="search-users"
|
||||
ngDefaultControl
|
||||
[formControl]="addSpeakerForm.get('user_id')"
|
||||
listname="{{ 'Select or search new speaker ...' | translate }}"
|
||||
[inputListValues]="filteredUsers"
|
||||
></os-search-value-selector>
|
||||
<mat-form-field>
|
||||
<os-search-value-selector
|
||||
class="search-users"
|
||||
formControlName="user_id"
|
||||
placeholder="{{ 'Select or search new speaker ...' | translate }}"
|
||||
[inputListValues]="filteredUsers"
|
||||
></os-search-value-selector>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
@ -145,10 +145,7 @@
|
||||
*ngIf="assignment && assignment.polls && assignment.polls.length"
|
||||
>
|
||||
<!-- TODO avoid animation/switching on update -->
|
||||
<mat-tab
|
||||
*ngFor="let poll of assignment.polls; let i = index; trackBy: trackByIndex"
|
||||
[label]="poll.title"
|
||||
>
|
||||
<mat-tab *ngFor="let poll of assignment.polls; let i = index; trackBy: trackByIndex" [label]="poll.title">
|
||||
<os-assignment-poll [assignment]="assignment" [poll]="poll"> </os-assignment-poll>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
@ -194,14 +191,15 @@
|
||||
*ngIf="hasPerms('addOthers') && filteredCandidates && filteredCandidates.value.length > 0"
|
||||
[formGroup]="candidatesForm"
|
||||
>
|
||||
<os-search-value-selector
|
||||
class="search-bar"
|
||||
ngDefaultControl
|
||||
[formControl]="candidatesForm.get('userId')"
|
||||
[multiple]="false"
|
||||
listname="{{ 'Select a new candidate' | translate }}"
|
||||
[inputListValues]="filteredCandidates"
|
||||
></os-search-value-selector>
|
||||
<mat-form-field>
|
||||
<os-search-value-selector
|
||||
class="search-bar"
|
||||
formControlName="userId"
|
||||
[multiple]="false"
|
||||
placeholder="{{ 'Select a new candidate' | translate }}"
|
||||
[inputListValues]="filteredCandidates"
|
||||
></os-search-value-selector>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@ -238,11 +236,7 @@
|
||||
<div>
|
||||
<!-- title -->
|
||||
<mat-form-field class="full-width">
|
||||
<input
|
||||
matInput
|
||||
placeholder="{{ 'Title' | translate }}"
|
||||
formControlName="title"
|
||||
/>
|
||||
<input matInput placeholder="{{ 'Title' | translate }}" formControlName="title" />
|
||||
<mat-error>{{ 'The title is required' | translate }}</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@ -256,22 +250,20 @@
|
||||
></editor>
|
||||
|
||||
<!-- searchValueSelector: tags -->
|
||||
<div class="content-field" *ngIf="tagsAvailable">
|
||||
<os-search-value-selector
|
||||
ngDefaultControl
|
||||
[formControl]="assignmentForm.get('tags_id')"
|
||||
[multiple]="true"
|
||||
[includeNone]="true"
|
||||
listname="{{ 'Tags' | translate }}"
|
||||
[inputListValues]="tagsObserver"
|
||||
></os-search-value-selector>
|
||||
</div>
|
||||
<div class="content-field" *ngIf="tagsAvailable" [formGroup]="assignmentForm">
|
||||
<mat-form-field>
|
||||
<os-search-value-selector
|
||||
formControlName="tags_id"
|
||||
[multiple]="true"
|
||||
[includeNone]="true"
|
||||
placeholder="{{ 'Tags' | translate }}"
|
||||
[inputListValues]="tagsObserver"
|
||||
></os-search-value-selector>
|
||||
</mat-form-field>
|
||||
|
||||
<!-- Attachments -->
|
||||
<div class="content-field">
|
||||
<os-attachment-control
|
||||
formControlName="attachments_id"
|
||||
(errorHandler)="raiseError($event)"
|
||||
[controlName]="assignmentForm.get('attachments_id')"
|
||||
></os-attachment-control>
|
||||
</div>
|
||||
|
||||
|
@ -13,15 +13,16 @@
|
||||
|
||||
<div class="custom-table-header">
|
||||
<div>
|
||||
<span>
|
||||
<os-search-value-selector
|
||||
ngDefaultControl
|
||||
[formControl]="modelSelectForm.get('model')"
|
||||
[multiple]="false"
|
||||
[includeNone]="false"
|
||||
listname="{{ 'Motion' | translate }}"
|
||||
[inputListValues]="collectionObserver"
|
||||
></os-search-value-selector>
|
||||
<span [formGroup]="modelSelectForm">
|
||||
<mat-form-field>
|
||||
<os-search-value-selector
|
||||
formControlName="model"
|
||||
[multiple]="false"
|
||||
[includeNone]="false"
|
||||
placeholder="{{ 'Motion' | translate }}"
|
||||
[inputListValues]="collectionObserver"
|
||||
></os-search-value-selector>
|
||||
</mat-form-field>
|
||||
</span>
|
||||
<span class="spacer-left-20">
|
||||
<button mat-button (click)="refresh()" *ngIf="currentModelId">
|
||||
|
@ -269,13 +269,14 @@
|
||||
<mat-error *ngIf="fileEditForm.invalid" translate>Required</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<os-search-value-selector
|
||||
ngDefaultControl
|
||||
[formControl]="fileEditForm.get('access_groups_id')"
|
||||
[multiple]="true"
|
||||
listname="{{ 'Access groups' | translate }}"
|
||||
[inputListValues]="groupsBehaviorSubject"
|
||||
></os-search-value-selector>
|
||||
<mat-form-field>
|
||||
<os-search-value-selector
|
||||
formControlName="access_groups_id"
|
||||
[multiple]="true"
|
||||
placeholder="{{ 'Access groups' | translate }}"
|
||||
[inputListValues]="groupsBehaviorSubject"
|
||||
></os-search-value-selector>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</div>
|
||||
<div mat-dialog-actions>
|
||||
@ -301,16 +302,17 @@
|
||||
<form class="edit-file-form" [formGroup]="newDirectoryForm">
|
||||
<p translate>Please enter a name for the new directory:</p>
|
||||
<mat-form-field>
|
||||
<input matInput osAutofocus formControlName="title" required />
|
||||
<input matInput osAutofocus formControlName="title" placeholder="{{ 'Title' | translate }}" required />
|
||||
</mat-form-field>
|
||||
|
||||
<os-search-value-selector
|
||||
ngDefaultControl
|
||||
[formControl]="newDirectoryForm.get('access_groups_id')"
|
||||
[multiple]="true"
|
||||
listname="{{ 'Access groups' | translate }}"
|
||||
[inputListValues]="groupsBehaviorSubject"
|
||||
></os-search-value-selector>
|
||||
<mat-form-field>
|
||||
<os-search-value-selector
|
||||
formControlName="access_groups_id"
|
||||
[multiple]="true"
|
||||
placeholder="{{ 'Access groups' | translate }}"
|
||||
[inputListValues]="groupsBehaviorSubject"
|
||||
></os-search-value-selector>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</div>
|
||||
<div mat-dialog-actions>
|
||||
@ -328,16 +330,17 @@
|
||||
<h1 mat-dialog-title>
|
||||
<span translate>Move into directory</span>
|
||||
</h1>
|
||||
<div class="os-form-card-mobile" mat-dialog-content>
|
||||
<div class="os-form-card-mobile" [formGroup]="moveForm" mat-dialog-content>
|
||||
<p translate>Please select the directory:</p>
|
||||
<os-search-value-selector
|
||||
ngDefaultControl
|
||||
[formControl]="moveForm.get('directory_id')"
|
||||
[includeNone]="true"
|
||||
[noneTitle]="'Base folder'"
|
||||
listname="{{ 'Parent directory' | translate }}"
|
||||
[inputListValues]="filteredDirectoryBehaviorSubject"
|
||||
></os-search-value-selector>
|
||||
<mat-form-field>
|
||||
<os-search-value-selector
|
||||
formControlName="directory_id"
|
||||
[includeNone]="true"
|
||||
[noneTitle]="'Base folder'"
|
||||
placeholder="{{ 'Parent directory' | translate }}"
|
||||
[inputListValues]="filteredDirectoryBehaviorSubject"
|
||||
></os-search-value-selector>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div mat-dialog-actions>
|
||||
<button type="submit" mat-button color="primary" [mat-dialog-close]="true">
|
||||
|
@ -18,17 +18,16 @@ export class ViewMotionPoll extends BaseProjectableViewModel<MotionPoll> impleme
|
||||
}
|
||||
|
||||
public getSlide(): ProjectorElementBuildDeskriptor {
|
||||
/*return {
|
||||
return {
|
||||
getBasicProjectorElement: options => ({
|
||||
name: Motion.COLLECTIONSTRING,
|
||||
name: MotionPoll.COLLECTIONSTRING,
|
||||
id: this.id,
|
||||
getIdentifiers: () => ['name', 'id']
|
||||
}),
|
||||
slideOptions: [],
|
||||
projectionDefaultName: 'motions',
|
||||
projectionDefaultName: 'motion-poll',
|
||||
getDialogTitle: this.getTitle
|
||||
};*/
|
||||
throw new Error('TODO');
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,22 +87,24 @@
|
||||
</mat-form-field>
|
||||
</p>
|
||||
<p>
|
||||
<os-search-value-selector
|
||||
ngDefaultControl
|
||||
[formControl]="commentFieldForm.get('read_groups_id')"
|
||||
[multiple]="true"
|
||||
listname="Groups with read permissions"
|
||||
[inputListValues]="groups"
|
||||
></os-search-value-selector>
|
||||
<mat-form-field>
|
||||
<os-search-value-selector
|
||||
formControlName="read_groups_id"
|
||||
[multiple]="true"
|
||||
placeholder="Groups with read permissions"
|
||||
[inputListValues]="groups"
|
||||
></os-search-value-selector>
|
||||
</mat-form-field>
|
||||
</p>
|
||||
<p>
|
||||
<os-search-value-selector
|
||||
ngDefaultControl
|
||||
[formControl]="commentFieldForm.get('write_groups_id')"
|
||||
[multiple]="true"
|
||||
listname="Groups with write permissions"
|
||||
[inputListValues]="groups"
|
||||
></os-search-value-selector>
|
||||
<mat-form-field>
|
||||
<os-search-value-selector
|
||||
formControlName="write_groups_id"
|
||||
[multiple]="true"
|
||||
placeholder="Groups with write permissions"
|
||||
[inputListValues]="groups"
|
||||
></os-search-value-selector>
|
||||
</mat-form-field>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -34,13 +34,14 @@
|
||||
</os-sorting-list>
|
||||
|
||||
<form *ngIf="users && users.value.length > 0" [formGroup]="addSubmitterForm">
|
||||
<os-search-value-selector
|
||||
class="search-users"
|
||||
ngDefaultControl
|
||||
[formControl]="addSubmitterForm.get('userId')"
|
||||
listname="{{ 'Select or search new submitter ...' | translate }}"
|
||||
[inputListValues]="users"
|
||||
></os-search-value-selector>
|
||||
<mat-form-field>
|
||||
<os-search-value-selector
|
||||
class="search-users"
|
||||
formControlName="userId"
|
||||
placeholder="{{ 'Select or search new submitter ...' | translate }}"
|
||||
[inputListValues]="users"
|
||||
></os-search-value-selector>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
|
||||
<p>
|
||||
|
@ -459,14 +459,15 @@
|
||||
|
||||
<!-- motion polls -->
|
||||
<div *ngIf="!editMotion" class="spacer-top-20 spacer-bottom-20">
|
||||
<os-motion-poll *ngFor="let poll of motion.motion.polls; let i = index" [rawPoll]="poll" [pollIndex]="i">
|
||||
</os-motion-poll>
|
||||
<div class="create-poll-button" *ngIf="perms.isAllowed('createpoll', motion)">
|
||||
<button mat-button (click)="createPoll()">
|
||||
<mat-icon class="main-nav-color">poll</mat-icon>
|
||||
<span translate>New vote</span>
|
||||
</button>
|
||||
</div>
|
||||
<mat-accordion>
|
||||
<os-motion-poll-preview *ngFor="let poll of motion.polls" [poll]="poll"></os-motion-poll-preview>
|
||||
</mat-accordion>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
@ -601,15 +602,16 @@
|
||||
|
||||
<!-- Submitter -->
|
||||
<div *ngIf="newMotion" class="content-field">
|
||||
<div *ngIf="perms.isAllowed('change_metadata', motion)">
|
||||
<os-search-value-selector
|
||||
ngDefaultControl
|
||||
[formControl]="contentForm.get('submitters_id')"
|
||||
[multiple]="true"
|
||||
listname="{{ 'Submitters' | translate }}"
|
||||
[inputListValues]="submitterObserver"
|
||||
></os-search-value-selector>
|
||||
</div>
|
||||
<ng-container *ngIf="perms.isAllowed('change_metadata', motion)">
|
||||
<mat-form-field>
|
||||
<os-search-value-selector
|
||||
formControlName="submitters_id"
|
||||
[multiple]="true"
|
||||
placeholder="{{ 'Submitters' | translate }}"
|
||||
[inputListValues]="submitterObserver"
|
||||
></os-search-value-selector>
|
||||
</mat-form-field>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
<div class="form-id-title">
|
||||
@ -783,13 +785,14 @@
|
||||
|
||||
<!-- Category form -->
|
||||
<div class="content-field" *ngIf="newMotion && categoryObserver.value.length > 0">
|
||||
<os-search-value-selector
|
||||
ngDefaultControl
|
||||
[formControl]="contentForm.get('category_id')"
|
||||
[includeNone]="true"
|
||||
listname="{{ 'Category' | translate }}"
|
||||
[inputListValues]="categoryObserver"
|
||||
></os-search-value-selector>
|
||||
<mat-form-field>
|
||||
<os-search-value-selector
|
||||
formControlName="category_id"
|
||||
[includeNone]="true"
|
||||
placeholder="{{ 'Category' | translate }}"
|
||||
[inputListValues]="categoryObserver"
|
||||
></os-search-value-selector>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="extra-data">
|
||||
@ -805,8 +808,8 @@
|
||||
</div>
|
||||
<div *osPerms="'motions.can_manage'; and: editMotion">
|
||||
<os-attachment-control
|
||||
formControlName="attachments_id"
|
||||
(errorHandler)="showUploadError($event)"
|
||||
[controlName]="contentForm.get('attachments_id')"
|
||||
></os-attachment-control>
|
||||
</div>
|
||||
</div>
|
||||
@ -818,25 +821,27 @@
|
||||
<!-- Supporter form -->
|
||||
<div class="content-field" *ngIf="editMotion && minSupporters">
|
||||
<div *ngIf="perms.isAllowed('change_metadata', motion)">
|
||||
<os-search-value-selector
|
||||
ngDefaultControl
|
||||
[formControl]="contentForm.get('supporters_id')"
|
||||
[multiple]="true"
|
||||
listname="{{ 'Supporters' | translate }}"
|
||||
[inputListValues]="supporterObserver"
|
||||
></os-search-value-selector>
|
||||
<mat-form-field>
|
||||
<os-search-value-selector
|
||||
formControlName="supporters_id"
|
||||
[multiple]="true"
|
||||
placeholder="{{ 'Supporters' | translate }}"
|
||||
[inputListValues]="supporterObserver"
|
||||
></os-search-value-selector>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Workflow -->
|
||||
<div class="content-field" *ngIf="editMotion && workflowObserver.value.length > 1">
|
||||
<div *ngIf="perms.isAllowed('change_metadata', motion)">
|
||||
<os-search-value-selector
|
||||
ngDefaultControl
|
||||
[formControl]="contentForm.get('workflow_id')"
|
||||
listname="{{ 'Workflow' | translate }}"
|
||||
[inputListValues]="workflowObserver"
|
||||
></os-search-value-selector>
|
||||
<mat-form-field>
|
||||
<os-search-value-selector
|
||||
formControlName="workflow_id"
|
||||
placeholder="{{ 'Workflow' | translate }}"
|
||||
[inputListValues]="workflowObserver"
|
||||
></os-search-value-selector>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -7,6 +7,7 @@ import { MotionCommentsComponent } from '../motion-comments/motion-comments.comp
|
||||
import { MotionDetailDiffComponent } from '../motion-detail-diff/motion-detail-diff.component';
|
||||
import { MotionDetailOriginalChangeRecommendationsComponent } from '../motion-detail-original-change-recommendations/motion-detail-original-change-recommendations.component';
|
||||
import { MotionDetailComponent } from './motion-detail.component';
|
||||
import { MotionPollPreviewComponent } from '../motion-poll/motion-poll-preview/motion-poll-preview.component';
|
||||
import { MotionPollComponent } from '../motion-poll/motion-poll.component';
|
||||
import { PersonalNoteComponent } from '../personal-note/personal-note.component';
|
||||
|
||||
@ -24,7 +25,8 @@ describe('MotionDetailComponent', () => {
|
||||
ManageSubmittersComponent,
|
||||
MotionPollComponent,
|
||||
MotionDetailOriginalChangeRecommendationsComponent,
|
||||
MotionDetailDiffComponent
|
||||
MotionDetailDiffComponent,
|
||||
MotionPollPreviewComponent
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
@ -1382,9 +1382,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
* Handler for creating a poll
|
||||
*/
|
||||
public createPoll(): void {
|
||||
// TODO
|
||||
// this.repo.createPoll(<any>{}).catch(this.raiseError);
|
||||
throw new Error('TODO');
|
||||
this.router.navigate(['motions', 'polls', 'new'], { queryParams: { parent: this.motion.id || null } });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,56 @@
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header *ngIf="poll">{{ poll.title }}</mat-expansion-panel-header>
|
||||
<ng-template matExpansionPanelContent>
|
||||
<div class="poll-content">
|
||||
<div>{{ 'Current state' | translate }}: {{ poll.stateVerbose }}</div>
|
||||
<div *ngIf="poll.groups">{{ 'Groups' | translate }}: {{ poll.groups }}</div>
|
||||
<div>{{ 'Method' | translate }}: {{ poll.pollmethodVerbose }}</div>
|
||||
<div>{{ 'Type' | translate }}: {{ poll.typeVerbose }}</div>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
<div class="poll-footer">
|
||||
<button *ngIf="poll.type === pollTypes.Analog" mat-icon-button (click)="enterAnalogVotes()">
|
||||
<mat-icon class="small-icon" matTooltip="{{ 'Enter votes' | translate }}">check</mat-icon><!-- TODO: other icon-->
|
||||
</button>
|
||||
<button mat-icon-button (click)="openPoll()">
|
||||
<mat-icon class="small-icon" matTooltip="{{ 'View' | translate }}">pageview</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button (click)="editPoll()">
|
||||
<mat-icon class="small-icon" matTooltip="{{ 'Edit' | translate }}">edit</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button (click)="deletePoll()">
|
||||
<mat-icon class="small-icon" matTooltip="{{ 'Delete' | translate }}">delete</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</mat-expansion-panel>
|
||||
|
||||
<!-- Template for dialog for entering votes -->
|
||||
<ng-template #enterVotesDialog>
|
||||
<h1 mat-dialog-title translate>Enter votes</h1>
|
||||
<div class="os-form-card-mobile" mat-dialog-content>
|
||||
<form [formGroup]="statuteParagraphForm" (keydown)="onKeyDown($event)">
|
||||
<p>
|
||||
<mat-form-field>
|
||||
<input formControlName="title" matInput placeholder="{{ 'Title' | translate }}" required />
|
||||
<mat-hint *ngIf="!statuteParagraphForm.controls.title.valid">
|
||||
<span translate>Required</span>
|
||||
</mat-hint>
|
||||
</mat-form-field>
|
||||
</p>
|
||||
<span>
|
||||
<!-- The HTML Editor -->
|
||||
<h4 translate>Statute paragraph</h4>
|
||||
<editor formControlName="text" [init]="tinyMceSettings"></editor>
|
||||
</span>
|
||||
</form>
|
||||
</div>
|
||||
<div mat-dialog-actions>
|
||||
<button mat-button [mat-dialog-close]="true" [disabled]="!statuteParagraphForm.valid">
|
||||
<span translate>Save</span>
|
||||
</button>
|
||||
<button mat-button [mat-dialog-close]="false">
|
||||
<span translate>Cancel</span>
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
@ -0,0 +1,7 @@
|
||||
.poll-content {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.poll-footer {
|
||||
text-align: end;
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { E2EImportsModule } from 'e2e-imports.module';
|
||||
|
||||
import { MotionPollPreviewComponent } from './motion-poll-preview.component';
|
||||
|
||||
describe('MotionPollPreviewComponent', () => {
|
||||
let component: MotionPollPreviewComponent;
|
||||
let fixture: ComponentFixture<MotionPollPreviewComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule],
|
||||
declarations: [MotionPollPreviewComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(MotionPollPreviewComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,56 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { MatSnackBar } from '@angular/material';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.service';
|
||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||
import { PollType } from 'app/shared/models/poll/base-poll';
|
||||
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
||||
|
||||
@Component({
|
||||
selector: 'os-motion-poll-preview',
|
||||
templateUrl: './motion-poll-preview.component.html',
|
||||
styleUrls: ['./motion-poll-preview.component.scss']
|
||||
})
|
||||
export class MotionPollPreviewComponent extends BaseViewComponent {
|
||||
@Input()
|
||||
public poll: ViewMotionPoll;
|
||||
|
||||
public pollTypes = PollType;
|
||||
|
||||
public constructor(
|
||||
title: Title,
|
||||
protected translate: TranslateService,
|
||||
matSnackbar: MatSnackBar,
|
||||
private repo: MotionPollRepositoryService,
|
||||
private promptDialog: PromptService,
|
||||
private router: Router
|
||||
) {
|
||||
super(title, translate, matSnackbar);
|
||||
}
|
||||
|
||||
public openPoll(): void {
|
||||
this.router.navigate(['motions', 'polls', this.poll.id]);
|
||||
}
|
||||
|
||||
public editPoll(): void {
|
||||
this.router.navigate(['motions', 'polls', this.poll.id], { queryParams: { edit: true } });
|
||||
}
|
||||
|
||||
public async deletePoll(): Promise<void> {
|
||||
const title = 'Delete poll';
|
||||
const text = 'Do you really want to delete the selected poll?';
|
||||
|
||||
if (await this.promptDialog.open(title, text)) {
|
||||
await this.repo.delete(this.poll);
|
||||
}
|
||||
}
|
||||
|
||||
public enterAnalogVotes(): void {
|
||||
throw new Error('TODO');
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ import { MotionDetailOriginalChangeRecommendationsComponent } from './components
|
||||
import { MotionDetailRoutingModule } from './motion-detail-routing.module';
|
||||
import { MotionDetailComponent } from './components/motion-detail/motion-detail.component';
|
||||
import { MotionPollDialogComponent } from './components/motion-poll/motion-poll-dialog.component';
|
||||
import { MotionPollPreviewComponent } from './components/motion-poll/motion-poll-preview/motion-poll-preview.component';
|
||||
import { MotionPollComponent } from './components/motion-poll/motion-poll.component';
|
||||
import { MotionTitleChangeRecommendationDialogComponent } from './components/motion-title-change-recommendation-dialog/motion-title-change-recommendation-dialog.component';
|
||||
import { PersonalNoteComponent } from './components/personal-note/personal-note.component';
|
||||
@ -24,6 +25,7 @@ import { PersonalNoteComponent } from './components/personal-note/personal-note.
|
||||
PersonalNoteComponent,
|
||||
ManageSubmittersComponent,
|
||||
MotionPollComponent,
|
||||
MotionPollPreviewComponent,
|
||||
MotionPollDialogComponent,
|
||||
MotionDetailDiffComponent,
|
||||
MotionDetailOriginalChangeRecommendationsComponent,
|
||||
|
@ -0,0 +1,92 @@
|
||||
<os-head-bar
|
||||
[goBack]="true"
|
||||
[nav]="false"
|
||||
[hasMainButton]="true"
|
||||
[editMode]="isEditingPoll"
|
||||
[mainButtonIcon]="'edit'"
|
||||
[mainActionTooltip]="'Edit' | translate"
|
||||
[isSaveButtonEnabled]="contentForm.valid"
|
||||
(mainEvent)="editPoll()"
|
||||
(saveEvent)="savePoll()"
|
||||
(cancelEditEvent)="backToView()"
|
||||
>
|
||||
<div class="title-slot">
|
||||
<h2 *ngIf="isNewPoll" translate>New vote</h2>
|
||||
<h2 *ngIf="!isNewPoll && !!poll">{{ poll.title }}</h2>
|
||||
</div>
|
||||
</os-head-bar>
|
||||
|
||||
<mat-card [ngClass]="isEditingPoll ? 'os-form-card' : 'os-card'">
|
||||
<ng-container *ngIf="!isEditingPoll" [ngTemplateOutlet]="viewTemplate"></ng-container>
|
||||
<ng-container *ngIf="isEditingPoll" [ngTemplateOutlet]="formTemplate"></ng-container>
|
||||
</mat-card>
|
||||
|
||||
<ng-template #viewTemplate>
|
||||
<ng-container *ngIf="poll">
|
||||
<h1>{{ poll.title }}</h1>
|
||||
<mat-divider></mat-divider>
|
||||
<div class="poll-content">
|
||||
<div>{{ 'Current state' | translate }}: {{ poll.stateVerbose | translate }}</div>
|
||||
<div *ngIf="poll.groups">
|
||||
{{ 'Groups' | translate }}:
|
||||
<span *ngFor="let group of poll.groups">{{ group.getTitle() | translate }}</span>
|
||||
</div>
|
||||
<div>{{ 'Poll type' | translate }}: {{ poll.typeVerbose | translate }}</div>
|
||||
<div>{{ 'Poll method' | translate }}: {{ poll.pollmethodVerbose | translate }}</div>
|
||||
<div>{{ 'Majority method' | translate }}: {{ poll.majorityMethodVerbose | translate }}</div>
|
||||
<div>{{ '100% base' | translate }}: {{ poll.percentBaseVerbose | translate }}</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #formTemplate>
|
||||
<form [formGroup]="contentForm">
|
||||
<mat-form-field>
|
||||
<input matInput formControlName="title" [placeholder]="'Title' | translate" required />
|
||||
<mat-error translate>A title is required</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-select [placeholder]="'Poll type' | translate" formControlName="type" required>
|
||||
<mat-option *ngFor="let option of pollTypes | keyvalue" [value]="option.key">{{
|
||||
option.value | translate
|
||||
}}</mat-option>
|
||||
</mat-select>
|
||||
<mat-error translate>This field is required</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field *ngIf="contentForm.controls.type.value && contentForm.controls.type.value != 'analog'">
|
||||
<os-search-value-selector
|
||||
formControlName="groups_id"
|
||||
[multiple]="true"
|
||||
[includeNone]="false"
|
||||
[placeholder]="'Entitled to vote' | translate"
|
||||
[inputListValues]="groupObservable"
|
||||
></os-search-value-selector>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-select [placeholder]="'Poll method' | translate" formControlName="pollmethod" required>
|
||||
<mat-option *ngFor="let option of pollMethods | keyvalue" [value]="option.key">{{
|
||||
option.value
|
||||
}}</mat-option>
|
||||
</mat-select>
|
||||
<mat-error translate>This field is required</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-select
|
||||
placeholder="{{ '100% base' | translate }}"
|
||||
formControlName="onehundred_percent_base"
|
||||
required
|
||||
>
|
||||
<mat-option *ngFor="let option of percentBases | keyvalue" [value]="option.key">{{
|
||||
option.value | translate
|
||||
}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-select placeholder="{{ 'Majority' | translate }}" formControlName="majority_method" required>
|
||||
<mat-option *ngFor="let option of majorityMethods | keyvalue" [value]="option.key">{{
|
||||
option.value | translate
|
||||
}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</ng-template>
|
@ -0,0 +1,3 @@
|
||||
.poll-content {
|
||||
padding-top: 10px;
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { E2EImportsModule } from 'e2e-imports.module';
|
||||
|
||||
import { MotionPollDetailComponent } from './motion-poll-detail.component';
|
||||
|
||||
describe('MotionPollDetailComponent', () => {
|
||||
let component: MotionPollDetailComponent;
|
||||
let fixture: ComponentFixture<MotionPollDetailComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule],
|
||||
declarations: [MotionPollDetailComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(MotionPollDetailComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,152 @@
|
||||
import { Location } from '@angular/common';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MatSnackBar } from '@angular/material';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.service';
|
||||
import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service';
|
||||
import { MotionPoll, MotionPollMethodsVerbose } from 'app/shared/models/motions/motion-poll';
|
||||
import {
|
||||
MajorityMethodVerbose,
|
||||
PercentBaseVerbose,
|
||||
PollStateVerbose,
|
||||
PollTypeVerbose
|
||||
} from 'app/shared/models/poll/base-poll';
|
||||
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
||||
import { ViewGroup } from 'app/site/users/models/view-group';
|
||||
|
||||
@Component({
|
||||
selector: 'os-motion-poll-detail',
|
||||
templateUrl: './motion-poll-detail.component.html',
|
||||
styleUrls: ['./motion-poll-detail.component.scss']
|
||||
})
|
||||
export class MotionPollDetailComponent extends BaseViewComponent implements OnInit {
|
||||
private pollId: number;
|
||||
|
||||
public pollStates = PollStateVerbose;
|
||||
public pollMethods = MotionPollMethodsVerbose;
|
||||
public pollTypes = PollTypeVerbose;
|
||||
public percentBases = PercentBaseVerbose;
|
||||
public majorityMethods = MajorityMethodVerbose;
|
||||
|
||||
public userGroups: ViewGroup[] = [];
|
||||
|
||||
public groupObservable: Observable<ViewGroup[]> = null;
|
||||
|
||||
public isNewPoll = false;
|
||||
|
||||
public poll: ViewMotionPoll = null;
|
||||
|
||||
public motionId: number;
|
||||
|
||||
public isEditingPoll = false;
|
||||
|
||||
public contentForm: FormGroup;
|
||||
|
||||
public constructor(
|
||||
title: Title,
|
||||
protected translate: TranslateService,
|
||||
matSnackbar: MatSnackBar,
|
||||
private repo: MotionPollRepositoryService,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private fb: FormBuilder,
|
||||
private groupRepo: GroupRepositoryService,
|
||||
private location: Location
|
||||
) {
|
||||
super(title, translate, matSnackbar);
|
||||
}
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.findComponentById();
|
||||
this.createPoll();
|
||||
|
||||
this.groupObservable = this.groupRepo.getViewModelListObservable();
|
||||
this.subscriptions.push(
|
||||
this.groupRepo.getViewModelListObservable().subscribe(groups => (this.userGroups = groups))
|
||||
);
|
||||
}
|
||||
|
||||
public savePoll(): void {
|
||||
const pollValues = this.contentForm.value;
|
||||
const poll: MotionPoll = this.isNewPoll ? new MotionPoll() : this.poll.poll;
|
||||
Object.keys(pollValues).forEach(key => (poll[key] = pollValues[key]));
|
||||
if (this.isNewPoll) {
|
||||
poll.motion_id = this.motionId;
|
||||
this.repo.create(poll).then(success => {
|
||||
if (success && success.id) {
|
||||
this.pollId = success.id;
|
||||
this.router.navigate(['motions', 'polls', this.pollId]);
|
||||
}
|
||||
}, this.raiseError);
|
||||
} else {
|
||||
this.repo.update(pollValues, this.poll).then(() => (this.isEditingPoll = false), this.raiseError);
|
||||
}
|
||||
}
|
||||
|
||||
public editPoll(): void {
|
||||
this.isEditingPoll = true;
|
||||
}
|
||||
|
||||
public backToView(): void {
|
||||
if (this.pollId) {
|
||||
this.isEditingPoll = false;
|
||||
} else {
|
||||
// TODO
|
||||
this.location.back();
|
||||
}
|
||||
}
|
||||
|
||||
private findComponentById(): void {
|
||||
const params = this.route.snapshot.params;
|
||||
const queryParams = this.route.snapshot.queryParams;
|
||||
if (params && params.id) {
|
||||
this.pollId = +params.id;
|
||||
this.subscriptions.push(
|
||||
this.repo.getViewModelObservable(this.pollId).subscribe(poll => {
|
||||
if (poll) {
|
||||
this.poll = poll;
|
||||
this.updateForm();
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this.isNewPoll = true;
|
||||
this.isEditingPoll = true;
|
||||
if (queryParams && queryParams.parent) {
|
||||
this.motionId = +queryParams.parent;
|
||||
}
|
||||
}
|
||||
if (queryParams && queryParams.edit) {
|
||||
this.isEditingPoll = true;
|
||||
}
|
||||
}
|
||||
|
||||
private createPoll(): void {
|
||||
this.contentForm = this.fb.group({
|
||||
title: ['', Validators.required],
|
||||
type: ['', Validators.required],
|
||||
pollmethod: ['', Validators.required],
|
||||
onehundred_percent_base: ['', Validators.required],
|
||||
majority_method: ['', Validators.required],
|
||||
groups_id: [[]]
|
||||
});
|
||||
if (this.poll) {
|
||||
this.updateForm();
|
||||
}
|
||||
}
|
||||
|
||||
private updateForm(): void {
|
||||
if (this.contentForm) {
|
||||
Object.keys(this.contentForm.controls).forEach(key => {
|
||||
this.contentForm.get(key).setValue(this.poll[key]);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
<os-head-bar>
|
||||
<div class="title-slot" translate>Motions poll list</div>
|
||||
|
||||
<!-- Menu -->
|
||||
<div class="menu-slot">
|
||||
<button type="button" mat-icon-button [matMenuTriggerFor]="pollMenu"><mat-icon>more_vert</mat-icon></button>
|
||||
</div>
|
||||
</os-head-bar>
|
||||
|
||||
<os-list-view-table
|
||||
[repo]="repo"
|
||||
[vScrollFixed]="64"
|
||||
[columns]="tableColumnDefinition"
|
||||
[listStorageKey]="'motion-polls'"
|
||||
>
|
||||
<div *pblNgridCellDef="'title'; row as poll; rowContext as context" class="cell-slot fill">
|
||||
<a
|
||||
class="detail-link"
|
||||
(click)="saveScrollIndex('motion-polls', rowContext.identity)"
|
||||
[routerLink]="poll.id"
|
||||
*ngIf="!isMultiSelect"
|
||||
></a>
|
||||
<span>{{ poll.title }}</span>
|
||||
</div>
|
||||
<div *pblNgridCellDef="'state'; row as poll; rowContext as context" class="cell-slot fill">
|
||||
<span>{{ poll.stateVerbose }}</span>
|
||||
</div>
|
||||
</os-list-view-table>
|
||||
|
||||
<mat-menu #pollMenu="matMenu">
|
||||
<!-- Settings -->
|
||||
<button mat-menu-item *osPerms="'core.can_manage_config'" routerLink="/settings/polls">
|
||||
<mat-icon>settings</mat-icon>
|
||||
<span translate>Settings</span>
|
||||
</button>
|
||||
</mat-menu>
|
@ -0,0 +1,27 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { E2EImportsModule } from 'e2e-imports.module';
|
||||
|
||||
import { MotionPollListComponent } from './motion-poll-list.component';
|
||||
|
||||
describe('MotionPollListComponent', () => {
|
||||
let component: MotionPollListComponent;
|
||||
let fixture: ComponentFixture<MotionPollListComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule],
|
||||
declarations: [MotionPollListComponent]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(MotionPollListComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,45 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { MatSnackBar } from '@angular/material';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { PblColumnDefinition } from '@pebula/ngrid';
|
||||
|
||||
import { StorageService } from 'app/core/core-services/storage.service';
|
||||
import { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.service';
|
||||
import { BaseListViewComponent } from 'app/site/base/base-list-view';
|
||||
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
||||
|
||||
@Component({
|
||||
selector: 'os-motion-poll-list',
|
||||
templateUrl: './motion-poll-list.component.html',
|
||||
styleUrls: ['./motion-poll-list.component.scss']
|
||||
})
|
||||
export class MotionPollListComponent extends BaseListViewComponent<ViewMotionPoll> implements OnInit {
|
||||
public tableColumnDefinition: PblColumnDefinition[] = [
|
||||
{
|
||||
prop: 'title',
|
||||
width: 'auto'
|
||||
},
|
||||
{
|
||||
prop: 'state',
|
||||
width: 'auto'
|
||||
}
|
||||
];
|
||||
|
||||
public polls: ViewMotionPoll[] = [];
|
||||
|
||||
public constructor(
|
||||
title: Title,
|
||||
protected translate: TranslateService,
|
||||
matSnackbar: MatSnackBar,
|
||||
storage: StorageService,
|
||||
public repo: MotionPollRepositoryService
|
||||
) {
|
||||
super(title, translate, matSnackbar, storage);
|
||||
}
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.subscriptions.push(this.repo.getViewModelListObservable().subscribe(polls => (this.polls = polls)));
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { MotionPollDetailComponent } from './motion-poll-detail/motion-poll-detail.component';
|
||||
import { MotionPollListComponent } from './motion-poll-list/motion-poll-list.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', component: MotionPollListComponent, pathMatch: 'full' },
|
||||
{ path: 'new', component: MotionPollDetailComponent },
|
||||
{ path: ':id', component: MotionPollDetailComponent }
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class MotionPollRoutingModule {}
|
@ -0,0 +1,13 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { SharedModule } from 'app/shared/shared.module';
|
||||
import { MotionPollDetailComponent } from './motion-poll-detail/motion-poll-detail.component';
|
||||
import { MotionPollListComponent } from './motion-poll-list/motion-poll-list.component';
|
||||
import { MotionPollRoutingModule } from './motion-poll-routing.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [MotionPollDetailComponent, MotionPollListComponent],
|
||||
imports: [CommonModule, SharedModule, MotionPollRoutingModule]
|
||||
})
|
||||
export class MotionPollModule {}
|
@ -62,6 +62,11 @@ const routes: Routes = [
|
||||
loadChildren: () => import('./modules/amendment-list/amendment-list.module').then(m => m.AmendmentListModule),
|
||||
data: { basePerm: 'motions.can_see' }
|
||||
},
|
||||
{
|
||||
path: 'polls',
|
||||
loadChildren: () => import('./modules/motion-poll/motion-poll.module').then(m => m.MotionPollModule),
|
||||
data: { basePerm: 'motions.can_manage_polls' }
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
loadChildren: () => import('./modules/motion-detail/motion-detail.module').then(m => m.MotionDetailModule),
|
||||
|
@ -70,7 +70,7 @@
|
||||
|
||||
<!-- Attachments -->
|
||||
<os-attachment-control
|
||||
[controlName]="topicForm.get('attachments_id')"
|
||||
formControlName="attachments_id"
|
||||
(errorHandler)="raiseError($event)"
|
||||
></os-attachment-control>
|
||||
|
||||
@ -88,13 +88,14 @@
|
||||
|
||||
<!-- Parent item -->
|
||||
<div>
|
||||
<os-search-value-selector
|
||||
ngDefaultControl
|
||||
[formControl]="topicForm.get('agenda_parent_id')"
|
||||
[includeNone]="true"
|
||||
listname="{{ 'Parent agenda item' | translate }}"
|
||||
[inputListValues]="itemObserver"
|
||||
></os-search-value-selector>
|
||||
<mat-form-field>
|
||||
<os-search-value-selector
|
||||
formControlName="agenda_parent_id"
|
||||
[includeNone]="true"
|
||||
placeholder="{{ 'Parent agenda item' | translate }}"
|
||||
[inputListValues]="itemObserver"
|
||||
></os-search-value-selector>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -75,22 +75,12 @@
|
||||
</mat-form-field>
|
||||
<!-- First name -->
|
||||
<mat-form-field class="form37 distance force-min-with">
|
||||
<input
|
||||
type="text"
|
||||
matInput
|
||||
placeholder="{{ 'Given name' | translate }}"
|
||||
formControlName="first_name"
|
||||
/>
|
||||
<input type="text" matInput placeholder="{{ 'Given name' | translate }}" formControlName="first_name" />
|
||||
</mat-form-field>
|
||||
|
||||
<!-- Last name -->
|
||||
<mat-form-field class="form37 force-min-with">
|
||||
<input
|
||||
type="text"
|
||||
matInput
|
||||
placeholder="{{ 'Surname' | translate }}"
|
||||
formControlName="last_name"
|
||||
/>
|
||||
<input type="text" matInput placeholder="{{ 'Surname' | translate }}" formControlName="last_name" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
@ -142,13 +132,14 @@
|
||||
|
||||
<div>
|
||||
<!-- Groups -->
|
||||
<os-search-value-selector
|
||||
ngDefaultControl
|
||||
[formControl]="personalInfoForm.get('groups_id')"
|
||||
[multiple]="true"
|
||||
listname="{{ 'Groups' | translate }}"
|
||||
[inputListValues]="groups"
|
||||
></os-search-value-selector>
|
||||
<mat-form-field>
|
||||
<os-search-value-selector
|
||||
formControlName="groups_id"
|
||||
[multiple]="true"
|
||||
placeholder="{{ 'Groups' | translate }}"
|
||||
[inputListValues]="groups"
|
||||
></os-search-value-selector>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div *ngIf="isAllowed('manage')">
|
||||
@ -184,23 +175,14 @@
|
||||
<div *ngIf="isAllowed('seePersonal')">
|
||||
<!-- username -->
|
||||
<mat-form-field>
|
||||
<input
|
||||
type="text"
|
||||
matInput
|
||||
placeholder="{{ 'Username' | translate }}"
|
||||
formControlName="username"
|
||||
/>
|
||||
<input type="text" matInput placeholder="{{ 'Username' | translate }}" formControlName="username" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div *ngIf="isAllowed('seeExtra')">
|
||||
<!-- Comment -->
|
||||
<mat-form-field>
|
||||
<input
|
||||
matInput
|
||||
placeholder="{{ 'Comment' | translate }}"
|
||||
formControlName="comment"
|
||||
/>
|
||||
<input matInput placeholder="{{ 'Comment' | translate }}" formControlName="comment" />
|
||||
<mat-hint translate>Only for internal notes.</mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@ -245,7 +227,9 @@
|
||||
<span class="state-icons">
|
||||
<span>{{ user.short_name }}</span>
|
||||
<mat-icon *ngIf="user.is_present" matTooltip="{{ 'Is present' | translate }}">check_box</mat-icon>
|
||||
<mat-icon *ngIf="user.is_committee" matTooltip="{{ 'Is committee' | translate }}">account_balance</mat-icon>
|
||||
<mat-icon *ngIf="user.is_committee" matTooltip="{{ 'Is committee' | translate }}"
|
||||
>account_balance</mat-icon
|
||||
>
|
||||
<mat-icon *ngIf="!user.is_active && isAllowed('seeExtra')" matTooltip="{{ 'Inactive' | translate }}"
|
||||
>block</mat-icon
|
||||
>
|
||||
|
@ -136,6 +136,15 @@
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: mat-color($foreground, icon);
|
||||
}
|
||||
|
||||
.small-icon {
|
||||
@extend .icon;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/** Custom themes for NGrid. Could be an own file if it gets more */
|
||||
.pbl-ngrid-container {
|
||||
background: mat-color($background, card);
|
||||
|
Loading…
Reference in New Issue
Block a user