Merge pull request #4052 from tsiegleauq/motion-attachments

Motion Attachments + Restructure
This commit is contained in:
Sean 2018-12-10 18:02:29 +01:00 committed by GitHub
commit 659f9f116a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 574 additions and 333 deletions

View File

@ -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;
}

View File

@ -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<ViewItem, Item> {
* @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<ViewItem, Item> {
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<number> {
return this.config.get('agenda_new_items_default_visibility').pipe(map(key => +key));
}
}

View File

@ -79,7 +79,7 @@ export abstract class ListViewBaseComponent<V extends BaseViewModel> 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<V extends BaseViewModel> 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<V extends BaseViewModel> 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
*/

View File

@ -168,6 +168,10 @@
<mat-icon>library_add</mat-icon>
<span translate>Exit multiselect</span>
</button>
<button mat-menu-item (click)="selectAll()">
<mat-icon>done_all</mat-icon>
<span translate>Select all</span>
</button>
<mat-divider></mat-divider>
<button mat-menu-item *osPerms="'mediafiles.can_manage'" (click)="deleteSelected()">
<mat-icon>delete</mat-icon>

View File

@ -43,7 +43,7 @@ export class ViewMediafile extends BaseViewModel {
}
public get downloadUrl(): string {
return this.mediafile && this.mediafile.mediafile ? `${this.prefix}${this.fileName}` : null;
return this.mediafile ? this.mediafile.getDownloadUrl() : null;
}
public constructor(mediafile?: Mediafile, uploader?: User) {
@ -100,7 +100,7 @@ export class ViewMediafile extends BaseViewModel {
'video/3gpp',
'video/x-msvideo',
'video/x-ms-wmv',
'video/x-matroska',
'video/x-matroska'
].includes(this.type);
}

View File

@ -3,14 +3,6 @@
<button class="small-button" type="button" mat-icon-button disableRipple *ngIf="!isEditMode" (click)="onEdit()">
<mat-icon>edit</mat-icon>
</button>
<span *ngIf="isEditMode">
<button class="small-button" type="button" mat-icon-button disableRipple (click)="onSave()">
<mat-icon>save</mat-icon>
</button>
<button class="small-button" type="button" mat-icon-button disableRipple (click)="onCancel()">
<mat-icon>close</mat-icon>
</button>
</span>
</h4>
<div *ngIf="!isEditMode">
@ -32,7 +24,13 @@
></os-search-value-selector>
</form>
<os-sorting-list class="testclass" [input]="editSubmitterObservable" [live]="true" [count]="true" (sortEvent)="onSortingChange($event)">
<os-sorting-list
class="testclass"
[input]="editSubmitterObservable"
[live]="true"
[count]="true"
(sortEvent)="onSortingChange($event)"
>
<!-- implicit user references into the component using ng-template slot -->
<ng-template let-user>
<button type="button" mat-icon-button matTooltip="{{ 'Remove' | translate }}" (click)="onRemove(user)">
@ -40,6 +38,9 @@
</button>
</ng-template>
</os-sorting-list>
<mat-card-actions>
<button type="button" mat-button (click)="onSave()"><span translate>Save</span></button>
<button type="button" mat-button (click)="onCancel()"><span translate>Cancel</span></button>
</mat-card-actions>
</mat-card>
</div>

View File

@ -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<ViewMotionBl
* @param router routing to children
* @param route determine the local route
* @param repo the motion block repository
* @param agendaRepo the agenda repository service
* @param DS the dataStore
* @param formBuilder creates forms
*/
@ -67,6 +69,7 @@ export class MotionBlockListComponent extends ListViewBaseComponent<ViewMotionBl
private router: Router,
private route: ActivatedRoute,
private repo: MotionBlockRepositoryService,
private agendaRepo: AgendaRepositoryService,
private DS: DataStoreService,
private formBuilder: FormBuilder
) {
@ -98,7 +101,7 @@ export class MotionBlockListComponent extends ListViewBaseComponent<ViewMotionBl
this.dataSource.data = newMotionblocks;
});
this.repo.getDefaultAgendaVisibility().subscribe(visibility => (this.defaultVisibility = visibility));
this.agendaRepo.getDefaultAgendaVisibility().subscribe(visibility => (this.defaultVisibility = visibility));
}
/**

View File

@ -12,7 +12,7 @@
<span translate>Motion</span>
<!-- Whitespace between "Motion" and identifier -->
<span>&nbsp;</span> <span *ngIf="!editMotion">{{ motion.identifier }}</span>
<span *ngIf="editMotion">{{ metaInfoForm.get('identifier').value }}</span>
<span *ngIf="editMotion">{{ contentForm.get('identifier').value }}</span>
</h2>
<h2 *ngIf="newMotion" translate>New motion</h2>
</div>
@ -21,14 +21,32 @@
<div *ngIf="!editMotion" class="extra-controls-slot on-transition-fade">
<div *ngIf="previousMotion">
<button mat-button (click)="navigateToMotion(previousMotion)">
<mat-icon>navigate_before</mat-icon>
<!-- possible icons:
arrow_left
chevron_left
first_page
arrow_back
arrow_back_ios
navigate_before
fast_rewind
-->
<mat-icon>chevron_left</mat-icon>
<span>{{ previousMotion.identifier }}</span>
</button>
</div>
<div *ngIf="nextMotion">
<button mat-button (click)="navigateToMotion(nextMotion)">
<span>{{ nextMotion.identifier }}</span>
<mat-icon>navigate_next</mat-icon>
<!-- possible icons:
arrow_right
chevron_right
last_page
arrow_forward
arrow_forward_ios
navigate_next
fast_forward
-->
<mat-icon>chevron_right</mat-icon>
</button>
</div>
</div>
@ -87,18 +105,16 @@
</os-head-bar>
<!-- Title -->
<div *ngIf="motion" class="motion-title on-transition-fade">
<h2 *ngIf="!editMotion">{{ motion.title }}</h2>
<h2 *ngIf="editMotion">{{ contentForm.get('title').value }}</h2>
<div class="motion-title on-transition-fade" *ngIf="motion && !editMotion">
<h2>{{ motion.title }}</h2>
</div>
<ng-container *ngIf="vp.isMobile; then: mobileView; else: desktopView"></ng-container>
<ng-container *ngIf="vp.isMobile; then mobileView; else desktopView"></ng-container>
<ng-template #mobileView>
<mat-accordion multi='true' class='on-transition-fade'>
<mat-accordion multi="true" class="on-transition-fade">
<!-- MetaInfo Panel-->
<mat-expansion-panel #metaInfoPanel [expanded]="true" class='meta-info-block meta-info-panel'>
<mat-expansion-panel #metaInfoPanel [expanded]="true" class="meta-info-block meta-info-panel">
<mat-expansion-panel-header>
<mat-panel-title>
<mat-icon>info</mat-icon>
@ -126,8 +142,8 @@
</div>
</mat-expansion-panel>
<os-motion-comments *ngIf="!newMotion" [motion]="motion"></os-motion-comments>
<os-personal-note *ngIf="!newMotion" [motion]="motion"></os-personal-note>
<os-motion-comments *ngIf="!editMotion" [motion]="motion"></os-motion-comments>
<os-personal-note *ngIf="!editMotion" [motion]="motion"></os-personal-note>
</mat-accordion>
</ng-template>
@ -139,8 +155,8 @@
<ng-container *ngTemplateOutlet="metaInfoTemplate"></ng-container>
</div>
<os-motion-comments *ngIf="!newMotion" [motion]="motion"></os-motion-comments>
<os-personal-note *ngIf="!newMotion" [motion]="motion"></os-personal-note>
<os-motion-comments *ngIf="!editMotion" [motion]="motion"></os-motion-comments>
<os-personal-note *ngIf="!editMotion" [motion]="motion"></os-personal-note>
</div>
<div class="desktop-right ">
<!-- Content -->
@ -150,99 +166,72 @@
</ng-template>
<ng-template #metaInfoTemplate>
<form [formGroup]="metaInfoForm" (keydown)="onKeyDown($event)" (ngSubmit)="saveMotion()">
<!-- Identifier -->
<div *ngIf="editMotion && !newMotion">
<!-- <div *ngIf="editMotion"> -->
<div *ngIf="!editMotion">
<h4 translate>Identifier</h4>
{{ motion.identifier }}
</div>
<mat-form-field *ngIf="editMotion">
<input
matInput
placeholder="{{ &quot;Identifier&quot; | translate }}"
formControlName="identifier"
[value]="motionCopy.identifier"
/>
</mat-form-field>
</div>
<div *ngIf="motion">
<!-- Submitters -->
<div *ngIf="motion && motion.submitters || newMotion">
<div *ngIf="newMotion">
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
<os-search-value-selector
ngDefaultControl
[form]="metaInfoForm"
[formControl]="metaInfoForm.get('submitters_id')"
[multiple]="true"
listname="{{ 'Submitters' | translate }}"
[InputListValues]="submitterObserver"
></os-search-value-selector>
</div>
</div>
<div *ngIf="!editMotion && !newMotion">
<os-manage-submitters [motion]="motion"></os-manage-submitters>
</div>
<div *ngIf="motion.submitters || newMotion">
<div *ngIf="!editMotion"><os-manage-submitters [motion]="motion"></os-manage-submitters></div>
</div>
<!-- Supporters -->
<div *ngIf='motion && minSupporters'>
<div *ngIf="editMotion">
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
<os-search-value-selector
ngDefaultControl
[form]="metaInfoForm"
[formControl]="metaInfoForm.get('supporters_id')"
[multiple]="true"
listname="{{ 'Supporters' | translate }}"
[InputListValues]="supporterObserver"
></os-search-value-selector>
</div>
</div>
<div *ngIf="!editMotion">
<h4 *ngIf="perms.isAllowed('support', motion) || motion.hasSupporters()" translate>Supporters</h4>
<!-- support button -->
<button type="button" *ngIf="perms.isAllowed('support', motion)" (click)=support() mat-raised-button color="primary">
<mat-icon>thumb_up</mat-icon>
{{ 'Support' | translate }}
</button>
<!-- unsupport button -->
<button type="button" *ngIf="perms.isAllowed('unsupport', motion)" (click)=unsupport() mat-raised-button color="primary">
<mat-icon>thumb_down</mat-icon>
{{ 'Unsupport' | translate }}
</button>
<!-- show supporters (TODO: open in dialog) -->
<button type="button" *ngIf="motion.hasSupporters()" (click)=openSupportersDialog() mat-button>
{{ motion.supporters.length }} {{ 'supporters' | translate }}
</button>
<p *ngIf="showSupporters">
<mat-chip-list *ngFor="let supporter of motion.supporters">
<mat-chip>{{ supporter.full_name }}</mat-chip>
</mat-chip-list>
</p>
</div>
<!-- do Support -->
<div *ngIf="minSupporters && !editMotion">
<h4 *ngIf="perms.isAllowed('support', motion) || motion.hasSupporters()" translate>Supporters</h4>
<!-- support button -->
<button
type="button"
mat-raised-button
color="primary"
(click)="support()"
*ngIf="perms.isAllowed('support', motion)"
>
<mat-icon>thumb_up</mat-icon>
{{ 'Support' | translate }}
</button>
<!-- unsupport button -->
<button
type="button"
*ngIf="perms.isAllowed('unsupport', motion)"
(click)="unsupport()"
mat-raised-button
color="primary"
>
<mat-icon>thumb_down</mat-icon>
{{ 'Unsupport' | translate }}
</button>
<!-- show supporters (TODO: open in dialog) -->
<button type="button" *ngIf="motion.hasSupporters()" (click)="openSupportersDialog()" mat-button>
{{ motion.supporters.length }} {{ 'supporters' | translate }}
</button>
<p *ngIf="showSupporters">
<mat-chip-list *ngFor="let supporter of motion.supporters">
<mat-chip>{{ supporter.full_name }}</mat-chip>
</mat-chip-list>
</p>
</div>
<!-- State -->
<div *ngIf='motion && !editMotion'>
<!-- Set State -->
<div *ngIf="!editMotion">
<h4 translate>State</h4>
<mat-menu #stateMenu='matMenu'>
<button *ngFor='let state of motion.nextStates' mat-menu-item
(click)=setState(state.id)>{{ state.name | translate }}
<mat-menu #stateMenu="matMenu">
<button *ngFor="let state of motion.nextStates" mat-menu-item (click)="setState(state.id)">
{{ state.name | translate }}
</button>
<mat-divider></mat-divider>
<button mat-menu-item (click)=setState(null)>
<button mat-menu-item (click)="setState(null)">
<mat-icon>replay</mat-icon> {{ 'Reset state' | translate }}
</button>
</mat-menu>
<mat-basic-chip [matMenuTriggerFor]='stateMenu' [ngClass]="{
'green': motion.state.css_class === 'success',
'red': motion.state.css_class === 'danger',
'grey': motion.state.css_class === 'default',
'lightblue': motion.state.css_class === 'primary' }">
<mat-basic-chip
*ngIf="motion.state"
[matMenuTriggerFor]="stateMenu"
[ngClass]="{
green: motion.state.css_class === 'success',
red: motion.state.css_class === 'danger',
grey: motion.state.css_class === 'default',
lightblue: motion.state.css_class === 'primary'
}"
>
{{ motion.state.name | translate }}
</mat-basic-chip>
@ -250,27 +239,35 @@
</div>
<!-- Recommendation -->
<div *ngIf='motion && recommender && !editMotion'>
<div *ngIf="recommender && !editMotion">
<h4>{{ recommender }}</h4>
<mat-menu #recommendationMenu='matMenu'>
<button *ngFor='let recommendation of motion.possibleRecommendations' mat-menu-item
(click)=setRecommendation(recommendation.id)>{{ recommendation.recommendation_label | translate }}
<mat-menu #recommendationMenu="matMenu">
<button
*ngFor="let recommendation of motion.possibleRecommendations"
mat-menu-item
(click)="setRecommendation(recommendation.id)"
>
{{ recommendation.recommendation_label | translate }}
</button>
<mat-divider></mat-divider>
<button mat-menu-item (click)=setRecommendation(null)>
<button mat-menu-item (click)="setRecommendation(null)">
<mat-icon>replay</mat-icon> {{ 'Reset recommendation' | translate }}
</button>
</mat-menu>
<mat-basic-chip [matMenuTriggerFor]='recommendationMenu' class="bluegrey">
{{ motion.recommendation ? (motion.recommendation.recommendation_label | translate) : ('not set' | translate) }}
<mat-basic-chip [matMenuTriggerFor]="recommendationMenu" class="bluegrey">
{{
motion.recommendation
? (motion.recommendation.recommendation_label | translate)
: ('not set' | translate)
}}
</mat-basic-chip>
</div>
<!-- Category -->
<!-- Disabled during "new motion" since changing has no effect -->
<div *ngIf="motion && !editMotion">
<div *ngIf="!editMotion">
<h4 translate>Category</h4>
<mat-menu #categoryMenu='matMenu'>
<mat-menu #categoryMenu="matMenu">
<button
mat-menu-item
*ngFor="let category of categoryObserver.value"
@ -278,75 +275,34 @@
>
{{ category }}
</button>
<button mat-menu-item (click)=setCategory(null)>
---
</button>
<button mat-menu-item (click)="setCategory(null)">---</button>
</mat-menu>
<mat-basic-chip [matMenuTriggerFor]='categoryMenu' class="grey">
<mat-basic-chip [matMenuTriggerFor]="categoryMenu" class="grey">
{{ motion.category ? motion.category : ('not set' | translate) }}
</mat-basic-chip>
</div>
<!-- Block -->
<div *ngIf="motion && !editMotion">
<div *ngIf="!editMotion">
<h4 translate>Motion block</h4>
<mat-menu #blockMenu='matMenu'>
<button
mat-menu-item
*ngFor="let block of blockObserver.value"
(click)="setBlock(block.id)"
>
<mat-menu #blockMenu="matMenu">
<button mat-menu-item *ngFor="let block of blockObserver.value" (click)="setBlock(block.id)">
{{ block }}
</button>
<button mat-menu-item (click)="setBlock(null)">
---
</button>
<button mat-menu-item (click)="setBlock(null)">---</button>
</mat-menu>
<mat-basic-chip [matMenuTriggerFor]='blockMenu' class="grey">
{{ motion.motion_block ? motion.motion_block : ('not set' | translate) }}
<mat-basic-chip [matMenuTriggerFor]="blockMenu" class="grey">
{{ motion.motion_block ? motion.motion_block : ('not set' | translate) }}
</mat-basic-chip>
</div>
<!-- Workflow -->
<div *ngIf="editMotion">
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
<os-search-value-selector
ngDefaultControl
[form]="metaInfoForm"
[formControl]="metaInfoForm.get('workflow_id')"
[multiple]="false"
listname="{{ 'Workflow' | translate }}"
[InputListValues]="workflowObserver"
></os-search-value-selector>
</div>
<!-- Origin - display only -->
<div *ngIf="!editMotion && motion.origin">
<h4 translate>Origin</h4>
{{ motion.origin }}
</div>
<!-- Origin -->
<div *ngIf="(motion && motion.origin) || editMotion">
<div *ngIf="!editMotion">
<h4 translate>Origin</h4>
{{ motion.origin }}
</div>
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
<mat-form-field *ngIf="editMotion">
<input
matInput
placeholder="{{ 'Origin' | translate}}"
formControlName="origin"
[value]="motionCopy.origin"
/>
</mat-form-field>
</div>
</div>
<!-- Voting -->
<!--
<div *ngIf='motion.polls && motion.polls.length > 0 || editMotion'>
<h4 translate>Voting</h4>
</div>
-->
</form>
</div>
</ng-template>
<ng-template #contentTemplate>
@ -356,9 +312,10 @@
(clickdown)="onKeyDown($event)"
(keydown)="onKeyDown($event)"
(ngSubmit)="saveMotion()"
*ngIf="motion"
>
<!-- Line Number and Diff buttons -->
<div *ngIf="motion && !editMotion && !motion.isStatuteAmendment()" class="motion-text-controls">
<div *ngIf="!editMotion && !motion.isStatuteAmendment()" class="motion-text-controls">
<button
type="button"
mat-icon-button
@ -397,31 +354,53 @@
</mat-form-field>
</div>
<!-- Title -->
<div *ngIf="(motion && motion.title) || editMotion">
<div *ngIf="!editMotion">
<h4>{{ motion.title }}</h4>
<!-- Submitter -->
<div *ngIf="newMotion" class="content-field form100">
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
<os-search-value-selector
ngDefaultControl
[form]="contentForm"
[formControl]="contentForm.get('submitters_id')"
[multiple]="true"
listname="{{ 'Submitters' | translate }}"
[InputListValues]="submitterObserver"
></os-search-value-selector>
</div>
</div>
<div class="form-id-title">
<!-- Identifier -->
<div *ngIf="editMotion && !newMotion" class="content-field form-identifier">
<mat-form-field *ngIf="editMotion">
<input
matInput
placeholder="{{ 'Identifier' | translate }}"
formControlName="identifier"
[value]="motionCopy.identifier"
/>
</mat-form-field>
</div>
<mat-form-field *ngIf="editMotion" class="wide-form">
<input
matInput
osAutofocus
placeholder="{{ 'Title' | translate }}"
formControlName="title"
[value]="motionCopy.title"
required
/>
</mat-form-field>
<!-- Title -->
<div *ngIf="editMotion" class="content-field form-title">
<mat-form-field *ngIf="editMotion">
<input
matInput
osAutofocus
placeholder="{{ 'Title' | translate }}"
formControlName="title"
[value]="motionCopy.title"
required
/>
</mat-form-field>
</div>
</div>
<!-- Text -->
<span class="text-prefix-label">{{ preamble | translate }}</span>
<!-- Regular motions or traditional amendments -->
<ng-container
*ngIf="motion && !editMotion && !motion.isStatuteAmendment() && !motion.isParagraphBasedAmendment()"
>
<ng-container *ngIf="!editMotion && !motion.isStatuteAmendment() && !motion.isParagraphBasedAmendment()">
<div
*ngIf="!isRecoModeDiff()"
class="motion-text"
@ -451,7 +430,7 @@
</ng-container>
<div
class="motion-text line-numbers-none"
*ngIf="motion && !editMotion && motion.isStatuteAmendment()"
*ngIf="!editMotion && motion.isStatuteAmendment()"
[innerHTML]="getFormattedStatuteAmendment()"
></div>
@ -459,21 +438,120 @@
<editor formControlName="text" [init]="tinyMceSettings" *ngIf="motion && editMotion"></editor>
<!-- Paragraph-based amendments -->
<ng-container *ngIf="motion && !editMotion && motion.isParagraphBasedAmendment()">
<ng-container *ngIf="!editMotion && motion.isParagraphBasedAmendment()">
<ng-container *ngTemplateOutlet="paragraphBasedAmendment"></ng-container>
</ng-container>
<!-- Reason -->
<div *ngIf="motion || editMotion">
<h5 *ngIf="motion.reason || editMotion" translate>Reason</h5>
<div *ngIf="motion.reason || editMotion">
<h3 translate>Reason</h3>
<div class="motion-text" *ngIf="!editMotion"><div [innerHtml]="motion.reason"></div></div>
<!-- The HTML Editor -->
<editor
formControlName='reason'
[init]="tinyMceSettings"
*ngIf="editMotion"
></editor>
<editor formControlName="reason" [init]="tinyMceSettings" *ngIf="editMotion"></editor>
</div>
<div class="extra-data">
<!-- Attachments -->
<div *ngIf="motion.hasAttachments() || editMotion" class="content-field form100">
<div *ngIf="!editMotion">
<h4 translate>Attachments</h4>
<mat-list dense>
<mat-list-item *ngFor="let file of motion.attachments">
<a [routerLink]="" (click)="onClickAttacment(file)">{{ file.title }}</a>
</mat-list-item>
</mat-list>
</div>
<div *ngIf="editMotion">
<os-search-value-selector
ngDefaultControl
[form]="contentForm"
[formControl]="contentForm.get('attachments_id')"
[multiple]="true"
listname="{{ 'Attachments' | translate }}"
[InputListValues]="mediafilesObserver"
></os-search-value-selector>
</div>
</div>
<!-- Category form -->
<div class="content-field form100" *ngIf="newMotion && categoryObserver.value.length > 0">
<os-search-value-selector
ngDefaultControl
[form]="contentForm"
[formControl]="contentForm.get('category_id')"
[multiple]="false"
[includeNone]="true"
listname="{{ 'Category' | translate }}"
[InputListValues]="categoryObserver"
></os-search-value-selector>
</div>
<!-- Parent item -->
<div class="content-field form100" *ngIf="newMotion && agendaItemObserver.value.length > 0">
<os-search-value-selector
ngDefaultControl
[form]="contentForm"
[formControl]="contentForm.get('parent_id')"
[multiple]="false"
[includeNone]="true"
listname="{{ 'Parent Item' | translate }}"
[InputListValues]="agendaItemObserver"
></os-search-value-selector>
</div>
<!-- Visibility -->
<div class="content-field form100" *ngIf="newMotion">
<mat-form-field>
<mat-select formControlName="agenda_type" placeholder="{{ 'Agenda visibility' | translate }}">
<mat-option *ngFor="let type of itemVisibility" [value]="type.key">
<span>{{ type.name | translate }}</span>
</mat-option>
</mat-select>
</mat-form-field>
</div>
<!-- Supporter form -->
<div class="content-field form100" *ngIf="editMotion && minSupporters">
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
<os-search-value-selector
ngDefaultControl
[form]="contentForm"
[formControl]="contentForm.get('supporters_id')"
[multiple]="true"
listname="{{ 'Supporters' | translate }}"
[InputListValues]="supporterObserver"
></os-search-value-selector>
</div>
</div>
<!-- Workflow -->
<div class="content-field form100" *ngIf="editMotion && workflowObserver.value.length > 1">
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
<os-search-value-selector
ngDefaultControl
[form]="contentForm"
[formControl]="contentForm.get('workflow_id')"
[multiple]="false"
listname="{{ 'Workflow' | translate }}"
[InputListValues]="workflowObserver"
></os-search-value-selector>
</div>
</div>
<!-- Origin form -->
<div class="content-field form100" *ngIf="editMotion">
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
<mat-form-field>
<input
matInput
placeholder="{{ 'Origin' | translate }}"
formControlName="origin"
[value]="motionCopy.origin"
/>
</mat-form-field>
</div>
</div>
</div>
</form>
</ng-template>
@ -522,9 +600,15 @@
<!-- Line number Menu -->
<mat-menu #lineNumberingMenu="matMenu">
<div *ngIf="motion">
<button mat-menu-item translate (click)=setLineNumberingMode(0) [ngClass]="{ 'selected': motion.lnMode === 0 }">none</button>
<button mat-menu-item translate (click)=setLineNumberingMode(1) [ngClass]="{ 'selected': motion.lnMode === 1 }">inline</button>
<button mat-menu-item translate (click)=setLineNumberingMode(2) [ngClass]="{ 'selected': motion.lnMode === 2 }">outside</button>
<button mat-menu-item translate (click)="setLineNumberingMode(0)" [ngClass]="{ selected: motion.lnMode === 0 }">
none
</button>
<button mat-menu-item translate (click)="setLineNumberingMode(1)" [ngClass]="{ selected: motion.lnMode === 1 }">
inline
</button>
<button mat-menu-item translate (click)="setLineNumberingMode(2)" [ngClass]="{ selected: motion.lnMode === 2 }">
outside
</button>
</div>
</mat-menu>

View File

@ -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 {

View File

@ -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<MotionBlock[]>;
/**
* Subject for mediafiles
*/
public mediafilesObserver: BehaviorSubject<Mediafile[]>;
/**
* Subject for agenda items
*/
public agendaItemObserver: BehaviorSubject<Item[]>;
/**
* 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<T extends Motion>(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<void> {
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<void> {
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<void> {
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;
});
}
}

View File

@ -41,13 +41,21 @@
<mat-header-cell *matHeaderCellDef mat-sort-header>Title</mat-header-cell>
<mat-cell *matCellDef="let motion">
<div class="innerTable">
<span class="motion-list-title">{{ motion.title }}</span> <br />
<span class="motion-list-title">{{ motion.title }}</span>
<!-- attachments -->
<span class="attached-files" *ngIf="motion.hasAttachments()">
<!-- <mat-basic-chip class="bluegrey"> <mat-icon>attach_file</mat-icon> </mat-basic-chip> -->
<mat-icon>attach_file</mat-icon>
</span>
<br />
<span class="motion-list-from" *ngIf="motion.submitters.length">
<span translate>by</span> {{ motion.submitters }}
</span>
<br *ngIf="motion.submitters.length" />
<!-- state -->
<mat-basic-chip
*ngIf="motion.state"
[ngClass]="{
green: motion.state.css_class === 'success',
red: motion.state.css_class === 'danger',

View File

@ -30,6 +30,17 @@
color: rgba(0, 0, 0, 0.5);
font-size: 90%;
}
.attached-files {
.mat-icon {
display: inline-flex;
vertical-align: middle;
$icon-size: 16px;
font-size: $icon-size;
height: $icon-size;
width: $icon-size;
}
}
}
/** State */

View File

@ -9,6 +9,7 @@ import { ViewMotionCommentSection } from './view-motion-comment-section';
import { MotionComment } from '../../../shared/models/motions/motion-comment';
import { Item } from 'app/shared/models/agenda/item';
import { MotionBlock } from 'app/shared/models/motions/motion-block';
import { Mediafile } from 'app/shared/models/mediafiles/mediafile';
export enum LineNumberingMode {
None,
@ -39,6 +40,7 @@ export class ViewMotion extends BaseViewModel {
protected _state: WorkflowState;
protected _item: Item;
protected _block: MotionBlock;
protected _attachments: Mediafile[];
/**
* Indicates the LineNumberingMode Mode.
@ -79,7 +81,7 @@ export class ViewMotion extends BaseViewModel {
}
public get identifier(): string {
return this.motion ? this.motion.identifier : null;
return this.motion && this.motion.identifier ? this.motion.identifier : null;
}
public get title(): string {
@ -188,6 +190,10 @@ export class ViewMotion extends BaseViewModel {
return this._item;
}
public get agenda_type(): number {
return this.item ? this.item.type : null;
}
public get motion_block_id(): number {
return this.motion ? this.motion.motion_block_id : null;
}
@ -209,7 +215,15 @@ export class ViewMotion extends BaseViewModel {
}
public get tags_id(): number[] {
return this._motion ? this._motion.tags_id : null;
return this.motion ? this.motion.tags_id : null;
}
public get attachments_id(): number[] {
return this.motion ? this.motion.attachments_id : null
}
public get attachments(): Mediafile[] {
return this._attachments ? this._attachments : null;
}
public constructor(
@ -220,10 +234,10 @@ export class ViewMotion extends BaseViewModel {
workflow?: Workflow,
state?: WorkflowState,
item?: Item,
block?: MotionBlock
block?: MotionBlock,
attachments?: Mediafile[],
) {
super();
this._motion = motion;
this._category = category;
this._submitters = submitters;
@ -232,6 +246,7 @@ export class ViewMotion extends BaseViewModel {
this._state = state;
this._item = item;
this._block = block;
this._attachments = attachments;
// TODO: Should be set using a a config variable
/*this._configService.get('motions_default_line_numbering').subscribe(
@ -255,6 +270,7 @@ export class ViewMotion extends BaseViewModel {
/**
* Returns the motion comment for the given section. Null, if no comment exist.
*
* @param section The section to search the comment for.
*/
public getCommentForSection(section: ViewMotionCommentSection): MotionComment {
@ -266,6 +282,7 @@ export class ViewMotion extends BaseViewModel {
/**
* Updates the local objects if required
*
* @param update
*/
public updateValues(update: BaseModel): void {
@ -279,11 +296,14 @@ export class ViewMotion extends BaseViewModel {
this.updateMotionBlock(update);
} else if (update instanceof User) {
this.updateUser(update as User);
} else if (update instanceof Mediafile) {
this.updateAttachments(update as Mediafile);
}
}
/**
* Update routine for the category
*
* @param category potentially the changed category. Needs manual verification
*/
public updateCategory(category: Category): void {
@ -294,6 +314,7 @@ export class ViewMotion extends BaseViewModel {
/**
* Update routine for the workflow
*
* @param workflow potentially the changed workflow (state). Needs manual verification
*/
public updateWorkflow(workflow: Workflow): void {
@ -304,6 +325,7 @@ export class ViewMotion extends BaseViewModel {
/**
* Update routine for the agenda Item
*
* @param item potentially the changed agenda Item. Needs manual verification
*/
public updateItem(item: Item): void {
@ -314,6 +336,7 @@ export class ViewMotion extends BaseViewModel {
/**
* Update routine for the motion block
*
* @param block potentially the changed motion block. Needs manual verification
*/
public updateMotionBlock(block: MotionBlock): void {
@ -323,7 +346,8 @@ export class ViewMotion extends BaseViewModel {
}
/**
* Update routine for the agenda Item
* Update routine for supporters and submitters
*
* @param update potentially the changed agenda Item. Needs manual verification
*/
public updateUser(update: User): void {
@ -339,10 +363,28 @@ export class ViewMotion extends BaseViewModel {
}
}
/**
* Update routine for attachments
*
* @param update
*/
public updateAttachments(update: Mediafile): void {
if (this.motion) {
if (this.attachments_id && this.attachments_id.includes(update.id)) {
const attachmentIndex = this.attachments.findIndex(mediafile => mediafile.id === update.id);
this.attachments[attachmentIndex] = update as Mediafile;
}
}
}
public hasSupporters(): boolean {
return !!(this.supporters && this.supporters.length > 0);
}
public hasAttachments(): boolean {
return !!(this.attachments && this.attachments.length > 0);
}
public isStatuteAmendment(): boolean {
return !!this.statute_paragraph_id;
}
@ -372,7 +414,10 @@ export class ViewMotion extends BaseViewModel {
this._submitters,
this._supporters,
this._workflow,
this._state
this._state,
this._item,
this._block,
this._attachments
);
}
}

View File

@ -12,7 +12,6 @@ import { ViewMotion } from '../models/view-motion';
import { Observable } from 'rxjs';
import { MotionRepositoryService } from './motion-repository.service';
import { map } from 'rxjs/operators';
import { ConfigService } from 'app/core/services/config.service';
/**
* Repository service for motion blocks
@ -28,14 +27,12 @@ export class MotionBlockRepositoryService extends BaseRepository<ViewMotionBlock
* @param mapperService Mapping collection strings to classes
* @param dataSend Send models to the server
* @param motionRepo Accessing the motion repository
* @param config To access config variables
*/
public constructor(
DS: DataStoreService,
mapperService: CollectionStringModelMapperService,
private dataSend: DataSendService,
private motionRepo: MotionRepositoryService,
private config: ConfigService
) {
super(DS, mapperService, MotionBlock);
}
@ -103,15 +100,6 @@ export class MotionBlockRepositoryService extends BaseRepository<ViewMotionBlock
return this.DS.filter(Motion, motion => motion.motion_block_id === block.id).length;
}
/**
* Get agenda visibility from the config
*
* @return An observable to the default agenda visibility
*/
public getDefaultAgendaVisibility(): Observable<number> {
return this.config.get('agenda_new_items_default_visibility').pipe(map(key => +key));
}
/**
* Observe the motion repository and return the motions belonging to the given
* block as observable

View File

@ -27,6 +27,7 @@ import { TreeService } from 'app/core/services/tree.service';
import { ViewMotionAmendedParagraph } from '../models/view-motion-amended-paragraph';
import { CreateMotion } from '../models/create-motion';
import { MotionBlock } from 'app/shared/models/motions/motion-block';
import { Mediafile } from 'app/shared/models/mediafiles/mediafile';
/**
* Repository Services for motions (and potentially categories)
@ -64,7 +65,7 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
private readonly diff: DiffService,
private treeService: TreeService
) {
super(DS, mapperService, Motion, [Category, User, Workflow, Item, MotionBlock]);
super(DS, mapperService, Motion, [Category, User, Workflow, Item, MotionBlock, Mediafile]);
}
/**
@ -82,11 +83,12 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
const workflow = this.DS.get(Workflow, motion.workflow_id);
const item = this.DS.get(Item, motion.agenda_item_id);
const block = this.DS.get(MotionBlock, motion.motion_block_id);
const attachments = this.DS.getMany(Mediafile, motion.attachments_id);
let state: WorkflowState = null;
if (workflow) {
state = workflow.getStateById(motion.state_id);
}
return new ViewMotion(motion, category, submitters, supporters, workflow, state, item, block);
return new ViewMotion(motion, category, submitters, supporters, workflow, state, item, block, attachments);
}
/**