From 00db199947262214a11a6a841505db48632664cf Mon Sep 17 00:00:00 2001 From: Sean Engelhardt Date: Mon, 10 Dec 2018 17:54:48 +0100 Subject: [PATCH] Motion Attachments + Restructure --- .../app/shared/models/mediafiles/mediafile.ts | 9 + .../services/agenda-repository.service.ts | 16 +- client/src/app/site/base/list-view-base.ts | 14 +- .../mediafile-list.component.html | 4 + .../site/mediafiles/models/view-mediafile.ts | 4 +- .../manage-submitters.component.html | 21 +- .../motion-block-list.component.ts | 5 +- .../motion-detail.component.html | 460 +++++++++++------- .../motion-detail.component.scss | 92 ++-- .../motion-detail/motion-detail.component.ts | 186 +++++-- .../motion-list/motion-list.component.html | 10 +- .../motion-list/motion-list.component.scss | 11 + .../app/site/motions/models/view-motion.ts | 57 ++- .../motion-block-repository.service.ts | 12 - .../services/motion-repository.service.ts | 6 +- 15 files changed, 574 insertions(+), 333 deletions(-) diff --git a/client/src/app/shared/models/mediafiles/mediafile.ts b/client/src/app/shared/models/mediafiles/mediafile.ts index 846cac425..f6161c1c6 100644 --- a/client/src/app/shared/models/mediafiles/mediafile.ts +++ b/client/src/app/shared/models/mediafiles/mediafile.ts @@ -24,6 +24,15 @@ export class Mediafile extends ProjectableBaseModel { this.mediafile = new File(input.mediafile); } + /** + * Determine the downloadURL + * + * @returns the download URL for the specific file as string + */ + public getDownloadUrl(): string { + return `${this.media_url_prefix}${this.mediafile.name}`; + } + public getTitle(): string { return this.title; } diff --git a/client/src/app/site/agenda/services/agenda-repository.service.ts b/client/src/app/site/agenda/services/agenda-repository.service.ts index 7c465c9a4..cb2a4d5c9 100644 --- a/client/src/app/site/agenda/services/agenda-repository.service.ts +++ b/client/src/app/site/agenda/services/agenda-repository.service.ts @@ -1,4 +1,6 @@ import { Injectable } from '@angular/core'; +import { map } from 'rxjs/operators'; +import { Observable } from 'rxjs'; import { BaseRepository } from '../../base/base-repository'; import { DataStoreService } from '../../../core/services/data-store.service'; @@ -12,6 +14,7 @@ import { ViewSpeaker } from '../models/view-speaker'; import { Speaker } from 'app/shared/models/agenda/speaker'; import { User } from 'app/shared/models/users/user'; import { HttpService } from 'app/core/services/http.service'; +import { ConfigService } from 'app/core/services/config.service'; /** * Repository service for users @@ -27,11 +30,13 @@ export class AgendaRepositoryService extends BaseRepository { * @param DS The DataStore * @param httpService OpenSlides own HttpService * @param mapperService OpenSlides mapping service for collection strings + * @param config Read config variables */ public constructor( protected DS: DataStoreService, private httpService: HttpService, - mapperService: CollectionStringModelMapperService + mapperService: CollectionStringModelMapperService, + private config: ConfigService ) { super(DS, mapperService, Item); } @@ -179,4 +184,13 @@ export class AgendaRepositoryService extends BaseRepository { const contentObject = this.getContentObject(item); return new ViewItem(item, contentObject); } + + /** + * Get agenda visibility from the config + * + * @return An observable to the default agenda visibility + */ + public getDefaultAgendaVisibility(): Observable { + return this.config.get('agenda_new_items_default_visibility').pipe(map(key => +key)); + } } diff --git a/client/src/app/site/base/list-view-base.ts b/client/src/app/site/base/list-view-base.ts index 7fc7e2d6f..2f2e6b290 100644 --- a/client/src/app/site/base/list-view-base.ts +++ b/client/src/app/site/base/list-view-base.ts @@ -79,7 +79,7 @@ export abstract class ListViewBaseComponent extends Bas this.singleSelectAction(row); } else { const idx = this.selectedRows.indexOf(row); - if ( idx < 0){ + if (idx < 0) { this.selectedRows.push(row); } else { this.selectedRows.splice(idx, 1); @@ -92,13 +92,12 @@ export abstract class ListViewBaseComponent extends Bas * Should be overridden by implementations. Currently there is no default action. * @param row a ViewModel */ - public singleSelectAction(row: V) : void { - } + public singleSelectAction(row: V): void {} /** * enables/disables the multiSelect Mode */ - public toggleMultiSelect() : void { + public toggleMultiSelect(): void { if (!this.canMultiSelect || this.isMultiSelect) { this._multiSelectModus = false; this.clearSelection(); @@ -107,6 +106,13 @@ export abstract class ListViewBaseComponent extends Bas } } + /** + * Select all files in the current data source + */ + public selectAll(): void { + this.selectedRows = this.dataSource.data; + } + /** * Returns the current state of the multiSelect modus */ diff --git a/client/src/app/site/mediafiles/components/mediafile-list/mediafile-list.component.html b/client/src/app/site/mediafiles/components/mediafile-list/mediafile-list.component.html index f874834c0..1c1532666 100644 --- a/client/src/app/site/mediafiles/components/mediafile-list/mediafile-list.component.html +++ b/client/src/app/site/mediafiles/components/mediafile-list/mediafile-list.component.html @@ -168,6 +168,10 @@ library_add Exit multiselect + - - - -
@@ -32,7 +24,13 @@ > - + + + + +
- diff --git a/client/src/app/site/motions/components/motion-block-list/motion-block-list.component.ts b/client/src/app/site/motions/components/motion-block-list/motion-block-list.component.ts index 7af140972..2c661cdd9 100644 --- a/client/src/app/site/motions/components/motion-block-list/motion-block-list.component.ts +++ b/client/src/app/site/motions/components/motion-block-list/motion-block-list.component.ts @@ -13,6 +13,7 @@ import { Item, itemVisibilityChoices } from 'app/shared/models/agenda/item'; import { DataStoreService } from 'app/core/services/data-store.service'; import { MotionBlockRepositoryService } from '../../services/motion-block-repository.service'; import { ViewMotionBlock } from '../../models/view-motion-block'; +import { AgendaRepositoryService } from 'app/site/agenda/services/agenda-repository.service'; /** * Table for the motion blocks @@ -57,6 +58,7 @@ export class MotionBlockListComponent extends ListViewBaseComponent (this.defaultVisibility = visibility)); + this.agendaRepo.getDefaultAgendaVisibility().subscribe(visibility => (this.defaultVisibility = visibility)); } /** diff --git a/client/src/app/site/motions/components/motion-detail/motion-detail.component.html b/client/src/app/site/motions/components/motion-detail/motion-detail.component.html index a2c28d008..e6b17a0e8 100644 --- a/client/src/app/site/motions/components/motion-detail/motion-detail.component.html +++ b/client/src/app/site/motions/components/motion-detail/motion-detail.component.html @@ -12,7 +12,7 @@ Motion   {{ motion.identifier }} - {{ metaInfoForm.get('identifier').value }} + {{ contentForm.get('identifier').value }}

New motion

@@ -21,14 +21,32 @@
@@ -87,18 +105,16 @@ -
-

{{ motion.title }}

-

{{ contentForm.get('title').value }}

+
+

{{ motion.title }}

- + - - + - + info @@ -126,8 +142,8 @@
- - + + @@ -139,8 +155,8 @@ - - + +
@@ -150,99 +166,72 @@ -
- -
- -
-

Identifier

- {{ motion.identifier }} -
- - - -
- +
-
-
-
- -
-
-
- -
+
+
- -
-
-
- -
-
-
-

Supporters

- - - - - - -

- - {{ supporter.full_name }} - -

-
+ +
+

Supporters

+ + + + + + + +

+ + {{ supporter.full_name }} + +

- -
+ +

State

- - - - + {{ motion.state.name | translate }} @@ -250,27 +239,35 @@
-
+

{{ recommender }}

- - - - - {{ motion.recommendation ? (motion.recommendation.recommendation_label | translate) : ('not set' | translate) }} + + {{ + motion.recommendation + ? (motion.recommendation.recommendation_label | translate) + : ('not set' | translate) + }}
-
+

Category

- + - + - + {{ motion.category ? motion.category : ('not set' | translate) }}
-
+

Motion block

- - - + - - {{ motion.motion_block ? motion.motion_block : ('not set' | translate) }} + + {{ motion.motion_block ? motion.motion_block : ('not set' | translate) }}
- -
-
- -
+ +
+

Origin

+ {{ motion.origin }}
- - -
-
-

Origin

- {{ motion.origin }} -
-
- - - -
-
- - - - +
@@ -356,9 +312,10 @@ (clickdown)="onKeyDown($event)" (keydown)="onKeyDown($event)" (ngSubmit)="saveMotion()" + *ngIf="motion" > -
+
- -
-
-

{{ motion.title }}

+ +
+
+ +
+
+ +
+ +
+ + +
- - - + +
+ + + +
{{ preamble | translate }} - +
@@ -459,21 +438,120 @@ - + -
-
Reason
+
+

Reason

- + +
+ +
+ +
+
+

Attachments

+ + + {{ file.title }} + + +
+
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ + + + {{ type.name | translate }} + + + +
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ + + +
+
@@ -522,9 +600,15 @@
- - - + + +
diff --git a/client/src/app/site/motions/components/motion-detail/motion-detail.component.scss b/client/src/app/site/motions/components/motion-detail/motion-detail.component.scss index 1f6a9f6cf..40b455a56 100644 --- a/client/src/app/site/motions/components/motion-detail/motion-detail.component.scss +++ b/client/src/app/site/motions/components/motion-detail/motion-detail.component.scss @@ -23,7 +23,6 @@ span { line-height: 180%; font-size: 120%; color: #317796; // TODO: put in theme as $primary - h2 { margin: 0; font-weight: normal; @@ -41,58 +40,6 @@ span { } } -.motion-submitter { - display: inline; - font-weight: bold; - font-size: 70%; -} - -.meta-info-block { - form { - div + div { - margin-top: 15px; - } - - ul { - margin: 5px; - } - } - - h3 { - display: block; - // padding-top: 0; - margin-top: 0px; //distance between heading and text - margin-bottom: 3px; //distance between heading and text - font-size: 80%; - color: gray; - - mat-icon { - margin-left: 5px; - } - } - - .mat-form-field-label { - font-size: 12pt; - color: gray; - } - - .mat-form-field-label-wrapper { - mat-icon { - margin-left: 5px; - } - } -} - -.wide-form { - textarea { - height: 25vh; - } - - ::ng-deep { - width: 100%; - } -} - .meta-info-panel { padding-top: 25px; @@ -143,6 +90,45 @@ span { display: block; margin: 0 10px 7px 0px; } + + .extra-data { + margin-top: 1em; + } + + .content-field { + display: inline-block; + ::ng-deep { + .mat-form-field { + width: 100%; + } + } + } + + .form100 { + width: 100%; + } + + .form70 { + width: 70%; + } + + .form30 { + width: 30%; + } + + .form-id-title { + display: flex; + + .form-identifier { + flex: 0 0 95px; + max-width: 95px; + margin-right: 1em; + } + + .form-title { + flex: 1 1 auto; + } + } } .desktop-view { diff --git a/client/src/app/site/motions/components/motion-detail/motion-detail.component.ts b/client/src/app/site/motions/components/motion-detail/motion-detail.component.ts index db016084c..df26d89af 100644 --- a/client/src/app/site/motions/components/motion-detail/motion-detail.component.ts +++ b/client/src/app/site/motions/components/motion-detail/motion-detail.component.ts @@ -1,7 +1,9 @@ import { Component, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; +import { DomSanitizer, SafeHtml, Title } from '@angular/platform-browser'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { MatDialog, MatExpansionPanel, MatSnackBar, MatCheckboxChange } from '@angular/material'; +import { take, takeWhile, multicast, skipWhile } from 'rxjs/operators'; import { Category } from '../../../../shared/models/motions/category'; import { ViewportService } from '../../../../core/services/viewport.service'; @@ -19,7 +21,7 @@ import { } from '../motion-change-recommendation/motion-change-recommendation.component'; import { ChangeRecommendationRepositoryService } from '../../services/change-recommendation-repository.service'; import { ViewChangeReco } from '../../models/view-change-reco'; -import { DomSanitizer, SafeHtml, Title } from '@angular/platform-browser'; + import { ViewUnifiedChange } from '../../models/view-unified-change'; import { OperatorService } from '../../../../core/services/operator.service'; import { BaseViewComponent } from '../../../base/base-view'; @@ -27,11 +29,14 @@ import { ViewStatuteParagraph } from '../../models/view-statute-paragraph'; import { StatuteParagraphRepositoryService } from '../../services/statute-paragraph-repository.service'; import { ConfigService } from '../../../../core/services/config.service'; import { Workflow } from 'app/shared/models/motions/workflow'; -import { take, takeWhile, multicast, skipWhile } from 'rxjs/operators'; import { LocalPermissionsService } from '../../services/local-permissions.service'; import { ViewCreateMotion } from '../../models/view-create-motion'; import { CreateMotion } from '../../models/create-motion'; import { MotionBlock } from 'app/shared/models/motions/motion-block'; +import { itemVisibilityChoices, Item } from 'app/shared/models/agenda/item'; +import { PromptService } from 'app/core/services/prompt.service'; +import { AgendaRepositoryService } from 'app/site/agenda/services/agenda-repository.service'; +import { Mediafile } from 'app/shared/models/mediafiles/mediafile'; /** * Component for the motion detail view @@ -56,11 +61,6 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { @ViewChild('contentPanel') public contentPanel: MatExpansionPanel; - /** - * Motions meta-info - */ - public metaInfoForm: FormGroup; - /** * Motion content. Can be a new version */ @@ -184,6 +184,16 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { */ public blockObserver: BehaviorSubject; + /** + * Subject for mediafiles + */ + public mediafilesObserver: BehaviorSubject; + + /** + * Subject for agenda items + */ + public agendaItemObserver: BehaviorSubject; + /** * Determine if the name of supporters are visible */ @@ -209,6 +219,16 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { */ public showAmendmentContext = false; + /** + * Determines the default agenda item visibility + */ + public defaultVisibility: number; + + /** + * Determine visibility states for the agenda that will be created implicitly + */ + public itemVisibility = itemVisibilityChoices; + /** * Constuct the detail view. * @@ -222,11 +242,13 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { * @param formBuilder For reactive forms. Form Group and Form Control * @param dialogService For opening dialogs * @param repo Motion Repository + * @param agendaRepo Read out agenda variables * @param changeRecoRepo Change Recommendation Repository * @param statuteRepo: Statute Paragraph Repository * @param DS The DataStoreService * @param configService The configuration provider * @param sanitizer For making HTML SafeHTML + * @param promptService ensure safe deletion */ public constructor( title: Title, @@ -240,15 +262,15 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { private formBuilder: FormBuilder, private dialogService: MatDialog, private repo: MotionRepositoryService, + private agendaRepo: AgendaRepositoryService, private changeRecoRepo: ChangeRecommendationRepositoryService, private statuteRepo: StatuteParagraphRepositoryService, private DS: DataStoreService, private configService: ConfigService, - private sanitizer: DomSanitizer + private sanitizer: DomSanitizer, + private promptService: PromptService ) { super(title, translate, matSnackBar); - this.createForm(); - this.getMotionByUrl(); // Initial Filling of the Subjects this.submitterObserver = new BehaviorSubject(DS.getAll(User)); @@ -256,6 +278,8 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { this.categoryObserver = new BehaviorSubject(DS.getAll(Category)); this.workflowObserver = new BehaviorSubject(DS.getAll(Workflow)); this.blockObserver = new BehaviorSubject(DS.getAll(MotionBlock)); + this.mediafilesObserver = new BehaviorSubject(DS.getAll(Mediafile)); + this.agendaItemObserver = new BehaviorSubject(DS.getAll(Item)); // Make sure the subjects are updated, when a new Model for the type arrives this.DS.changeObservable.subscribe(newModel => { @@ -268,8 +292,13 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { this.workflowObserver.next(DS.getAll(Workflow)); } else if (newModel instanceof MotionBlock) { this.blockObserver.next(DS.getAll(MotionBlock)); + } else if (newModel instanceof Mediafile) { + this.mediafilesObserver.next(DS.getAll(Mediafile)); + } else if (newModel instanceof Item) { + this.agendaItemObserver.next(DS.getAll(Item)); } }); + // load config variables this.configService.get('motions_statutes_enabled').subscribe(enabled => (this.statutesEnabled = enabled)); this.configService.get('motions_min_supporters').subscribe(supporters => (this.minSupporters = supporters)); @@ -277,6 +306,45 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { this.configService.get('motions_amendments_enabled').subscribe(enabled => (this.amendmentsEnabled = enabled)); } + /** + * Init. + * Sets the surrounding motions to navigate back and forth + */ + public ngOnInit(): void { + this.createForm(); + this.getMotionByUrl(); + + this.repo.getViewModelListObservable().subscribe(newMotionList => { + if (newMotionList) { + this.allMotions = newMotionList; + this.setSurroundingMotions(); + } + }); + + this.statuteRepo.getViewModelListObservable().subscribe(newViewStatuteParagraphs => { + this.statuteParagraphs = newViewStatuteParagraphs; + }); + + // Set the default visibility using observers + this.agendaRepo.getDefaultAgendaVisibility().subscribe(visibility => { + if (visibility && this.newMotion) { + this.contentForm.get('agenda_type').setValue(visibility); + } + }); + + // disable the selector for attachments if there are none + this.mediafilesObserver.subscribe(files => { + if (this.createForm) { + const attachmentsCtrl = this.contentForm.get('attachments_id'); + if (this.mediafilesObserver.value.length === 0) { + attachmentsCtrl.disable(); + } else { + attachmentsCtrl.enable(); + } + } + }); + } + /** * Merges amendments and change recommendations and sorts them by the line numbers. * Called each time one of these arrays changes. @@ -350,12 +418,6 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { * Async load the values of the motion in the Form. */ public patchForm(formMotion: ViewMotion): void { - const metaInfoPatch = {}; - Object.keys(this.metaInfoForm.controls).forEach(ctrl => { - metaInfoPatch[ctrl] = formMotion[ctrl]; - }); - this.metaInfoForm.patchValue(metaInfoPatch); - const contentPatch: { [key: string]: any } = {}; Object.keys(this.contentForm.controls).forEach(ctrl => { contentPatch[ctrl] = formMotion[ctrl]; @@ -380,20 +442,19 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { * TODO: Build a custom form validator */ public createForm(): void { - this.metaInfoForm = this.formBuilder.group({ - identifier: [''], - category_id: [''], - state_id: [''], - recommendation_id: [''], - submitters_id: [], - supporters_id: [[]], - workflow_id: [], - origin: [''] - }); this.contentForm = this.formBuilder.group({ + identifier: [''], title: ['', Validators.required], text: ['', Validators.required], reason: [''], + category_id: [''], + attachments_id: [[]], + parent_id: [], + agenda_type: [''], + submitters_id: [], + supporters_id: [[]], + workflow_id: [], + origin: [''], statute_amendment: [''], // Internal value for the checkbox, not saved to the model statute_paragraph_id: [''] }); @@ -417,6 +478,8 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { * * @param motionValues valus for the new motion * @param ctor The motion constructor, so different motion types can be created. + * + * @returns the motion to save */ private prepareMotionForSave(motionValues: any, ctor: new (...args: any[]) => T): T { const motion = new ctor(); @@ -440,7 +503,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { * Creates a motion. Calls the "patchValues" function in the MotionObject */ public async createMotion(): Promise { - const newMotionValues = { ...this.metaInfoForm.value, ...this.contentForm.value }; + const newMotionValues = { ...this.contentForm.value }; const motion = this.prepareMotionForSave(newMotionValues, CreateMotion); try { @@ -455,7 +518,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { * Save a motion. Calls the "patchValues" function in the MotionObject */ public async updateMotion(): Promise { - const newMotionValues = { ...this.metaInfoForm.value, ...this.contentForm.value }; + const newMotionValues = { ...this.contentForm.value }; const motion = this.prepareMotionForSave(newMotionValues, Motion); this.repo.update(motion, this.motionCopy).then(() => (this.editMotion = false), this.raiseError); } @@ -473,6 +536,8 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { /** * get the formated motion text from the repository. + * + * @returns formated motion texts */ public getFormattedTextPlain(): string { // Prevent this.allChangingObjects to be reordered from within formatMotion @@ -510,9 +575,9 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { * If `this.motion` is an amendment, this returns a specified line range from the parent motion * (e.g. to show the contect in which this amendment is happening) * - * @param {number} from - * @param {number} to - * @returns {SafeHtml} + * @param from the line number to start + * @param to the line number to stop + * @returns safe html strings */ public getParentMotionRange(from: number, to: number): SafeHtml { const str = this.repo.extractMotionLineRange(this.motion.parent_id, { from, to }, true); @@ -521,7 +586,8 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { /** * get the diff html from the statute amendment, as SafeHTML for [innerHTML] - * @returns {SafeHtml} + * + * @returns safe html strings */ public getFormattedStatuteAmendment(): SafeHtml { const diffHtml = this.repo.formatStatuteAmendment(this.statuteParagraphs, this.motion, this.motion.lineLength); @@ -530,17 +596,18 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { /** * Trigger to delete the motion. - * Sends a delete request over the repository and - * shows a "are you sure" dialog */ public async deleteMotionButton(): Promise { - this.repo.delete(this.motion).then(() => { + const content = this.translate.instant('Are you sure you want to delete this motion block?'); + if (await this.promptService.open(this.motion.title, content)) { + await this.repo.delete(this.motion); this.router.navigate(['./motions/']); - }, this.raiseError); + } } /** * Sets the motions line numbering mode + * * @param mode Needs to got the enum defined in ViewMotion */ public setLineNumberingMode(mode: LineNumberingMode): void { @@ -549,6 +616,8 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { /** * Returns true if no line numbers are to be shown. + * + * @returns whether there are line numbers at all */ public isLineNumberingNone(): boolean { return this.motion.lnMode === LineNumberingMode.None; @@ -556,6 +625,8 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { /** * Returns true if the line numbers are to be shown within the text with no line breaks. + * + * @returns whether the line numberings are inside */ public isLineNumberingInline(): boolean { return this.motion.lnMode === LineNumberingMode.Inside; @@ -563,6 +634,8 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { /** * Returns true if the line numbers are to be shown to the left of the text. + * + * @returns whether the line numberings are outside */ public isLineNumberingOutside(): boolean { return this.motion.lnMode === LineNumberingMode.Outside; @@ -627,6 +700,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { /** * Comes from the head bar + * * @param mode */ public setEditMode(mode: boolean): void { @@ -644,6 +718,9 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { } } + /** + * Sets the default workflow ID during form creation + */ public updateWorkflowIdForCreateForm(): void { const isStatuteAmendment = !!this.contentForm.get('statute_amendment').value; const configKey = isStatuteAmendment ? 'motions_statute_amendments_workflow' : 'motions_workflow'; @@ -663,7 +740,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { skipWhile(id => !id) ) .subscribe(id => { - this.metaInfoForm.patchValue({ + this.contentForm.patchValue({ workflow_id: parseInt(id, 10) }); }); @@ -671,6 +748,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { /** * If the checkbox is deactivated, the statute_paragraph_id-field needs to be reset, as only that field is saved + * * @param {MatCheckboxChange} $event */ public onStatuteAmendmentChange($event: MatCheckboxChange): void { @@ -682,6 +760,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { /** * The paragraph of the statute to amend was changed -> change the input fields below + * * @param {number} newValue */ public onStatuteParagraphChange(newValue: number): void { @@ -694,6 +773,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { /** * Navigates the user to the given ViewMotion + * * @param motion target */ public navigateToMotion(motion: ViewMotion): void { @@ -749,6 +829,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { /** * Sets the state + * * @param id Motion state id */ public setState(id: number): void { @@ -757,6 +838,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { /** * Sets the recommendation + * * @param id Motion recommendation id */ public setRecommendation(id: number): void { @@ -765,6 +847,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { /** * Sets the category for current motion + * * @param id Motion category id */ public setCategory(id: number): void { @@ -797,32 +880,29 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit { /** * Create the absolute path to the corresponding list of speakers + * * @returns the link to the corresponding list of speakers as string */ public getSpeakerLink(): string { return `/agenda/${this.motion.agenda_item_id}/speakers`; } + /** + * Click handler for attachments + * + * @param attachment the selected file + */ + public onClickAttacment(attachment: Mediafile): void { + window.open(attachment.getDownloadUrl()); + } + /** * Determine if the user has the correct requirements to alter the motion + * TODO: All views should probably have a "isAllowedTo" routine to simplify this process + * + * @returns whether or not the OP is allowed to edit the motion */ public opCanEdit(): boolean { return this.op.hasPerms('motions.can_manage', 'motions.can_manage_metadata'); } - - /** - * Init. - * Sets the surrounding motions to navigate back and forth - */ - public ngOnInit(): void { - this.repo.getViewModelListObservable().subscribe(newMotionList => { - if (newMotionList) { - this.allMotions = newMotionList; - this.setSurroundingMotions(); - } - }); - this.statuteRepo.getViewModelListObservable().subscribe(newViewStatuteParagraphs => { - this.statuteParagraphs = newViewStatuteParagraphs; - }); - } } diff --git a/client/src/app/site/motions/components/motion-list/motion-list.component.html b/client/src/app/site/motions/components/motion-list/motion-list.component.html index 6266c7af2..445b1324c 100644 --- a/client/src/app/site/motions/components/motion-list/motion-list.component.html +++ b/client/src/app/site/motions/components/motion-list/motion-list.component.html @@ -41,13 +41,21 @@ Title
- {{ motion.title }}
+ {{ motion.title }} + + + + attach_file + + +
by {{ motion.submitters }}