Add Tags into motion details

Adds tags to motion repository,
view-motion and selectable for the motion detail view
This commit is contained in:
Sean Engelhardt 2019-02-01 12:31:10 +01:00
parent f3c3d8ab8c
commit f992b77d99
5 changed files with 154 additions and 10 deletions

View File

@ -31,6 +31,7 @@ import { ViewUnifiedChange } from '../../../site/motions/models/view-unified-cha
import { ViewStatuteParagraph } from '../../../site/motions/models/view-statute-paragraph';
import { Workflow } from '../../../shared/models/motions/workflow';
import { WorkflowState } from '../../../shared/models/motions/workflow-state';
import { Tag } from 'app/shared/models/core/tag';
/**
* Repository Services for motions (and potentially categories)
@ -71,7 +72,7 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
private personalNoteService: PersonalNoteService,
private translate: TranslateService
) {
super(DS, mapperService, Motion, [Category, User, Workflow, Item, MotionBlock, Mediafile]);
super(DS, mapperService, Motion, [Category, User, Workflow, Item, MotionBlock, Mediafile, Tag]);
}
/**
@ -90,11 +91,23 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
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);
const tags = this.DS.getMany(Tag, motion.tags_id);
let state: WorkflowState = null;
if (workflow) {
state = workflow.getStateById(motion.state_id);
}
return new ViewMotion(motion, category, submitters, supporters, workflow, state, item, block, attachments);
return new ViewMotion(
motion,
category,
submitters,
supporters,
workflow,
state,
item,
block,
attachments,
tags
);
}
/**
@ -219,6 +232,26 @@ export class MotionRepositoryService extends BaseRepository<ViewMotion, Motion>
await this.update(motion, viewMotion);
}
/**
* Adds new or removes existing tags from motions
*
* @param viewMotion the motion to tag
* @param tagId the tags id to add or remove
*/
public async setTag(viewMotion: ViewMotion, tagId: number): Promise<void> {
const motion = viewMotion.motion;
const tagIndex = motion.tags_id.findIndex(tag => tag === tagId);
if (tagIndex === -1) {
// add tag to motion
motion.tags_id.push(tagId);
} else {
// remove tag from motion
motion.tags_id.splice(tagIndex, 1);
}
await this.update(motion, viewMotion);
}
/**
* Sets the submitters by sending a request to the server,
*

View File

@ -303,6 +303,47 @@
</mat-basic-chip>
</div>
<!-- Tags -->
<!-- Disabled during "new motion" since changing has no effect -->
<div *ngIf="!editMotion && tagObserver.value.length > 0">
<h4 *ngIf="perms.isAllowed('change_metadata', motion) || motion.hasTags()">Tags</h4>
<!-- For privileged users -->
<div *ngIf="perms.isAllowed('change_metadata', motion)">
<!-- Selection menu -->
<mat-menu #tagMenu="matMenu">
<button mat-menu-item *ngFor="let tag of tagObserver.value" (click)="setTag($event, tag.id)">
<mat-icon *ngIf="motion.tags.includes(tag)">check</mat-icon>
{{ tag }}
</button>
</mat-menu>
<!-- Make the whole container a trigger to prevent unexpected menu behavior -->
<div [matMenuTriggerFor]="tagMenu">
<!-- No selected tags -->
<mat-basic-chip *ngIf="!motion.hasTags()" class="grey" disabled>
{{ '' }}
</mat-basic-chip>
<!-- Display a chip list of tags -->
<mat-chip-list class="mat-chip-list-stacked">
<mat-basic-chip *ngFor="let tag of motion.tags" class="grey" disabled>
{{ tag }}
</mat-basic-chip>
</mat-chip-list>
</div>
</div>
<!-- For non privileged users -->
<div *ngIf="!perms.isAllowed('change_metadata', motion)">
<mat-chip-list class="mat-chip-list-stacked">
<mat-basic-chip *ngFor="let tag of motion.tags" class="grey">
{{ tag }}
</mat-basic-chip>
</mat-chip-list>
</div>
</div>
<!-- Block -->
<div *ngIf="!editMotion && blockObserver.value.length > 0">
<h4 translate>Motion block</h4>
@ -494,8 +535,13 @@
<!-- The HTML Editor -->
<editor formControlName="text" [init]="tinyMceSettings" *ngIf="motion && editMotion" required></editor>
<div *ngIf="contentForm.get('text').invalid && (contentForm.get('text').dirty || contentForm.get('text').touched)"
class="red-warning-text" translate>
<div
*ngIf="
contentForm.get('text').invalid && (contentForm.get('text').dirty || contentForm.get('text').touched)
"
class="red-warning-text"
translate
>
This field is required.
</div>
@ -506,15 +552,30 @@
<!-- Reason -->
<div *ngIf="motion.reason || editMotion">
<h3 [ngClass]="(reasonRequired && contentForm.get('reason').invalid && (contentForm.get('reason').dirty || contentForm.get('reason').touched)) ? 'red-warning-text' : ''">
<h3
[ngClass]="
reasonRequired &&
contentForm.get('reason').invalid &&
(contentForm.get('reason').dirty || contentForm.get('reason').touched)
? 'red-warning-text'
: ''
"
>
<span translate>Reason</span>&nbsp;<span *ngIf="reasonRequired && editMotion">*</span>
</h3>
<div class="motion-text" *ngIf="!editMotion"><div [innerHtml]="motion.reason"></div></div>
<!-- The HTML Editor -->
<editor formControlName="reason" [init]="tinyMceSettings" *ngIf="editMotion" required></editor>
<div *ngIf="reasonRequired && contentForm.get('reason').invalid && (contentForm.get('reason').dirty || contentForm.get('reason').touched)"
class="red-warning-text" translate>
<div
*ngIf="
reasonRequired &&
contentForm.get('reason').invalid &&
(contentForm.get('reason').dirty || contentForm.get('reason').touched)
"
class="red-warning-text"
translate
>
This field is required.
</div>
</div>

View File

@ -275,3 +275,9 @@ span {
padding: 0px;
}
}
.mat-chip-list-stacked {
.mat-chip {
margin: 4px 4px 4px 8px;
}
}

View File

@ -40,6 +40,7 @@ import { ViewUnifiedChange } from '../../models/view-unified-change';
import { ViewStatuteParagraph } from '../../models/view-statute-paragraph';
import { Workflow } from 'app/shared/models/motions/workflow';
import { LinenumberingService } from 'app/core/ui-services/linenumbering.service';
import { Tag } from 'app/shared/models/core/tag';
/**
* Component for the motion detail view
@ -221,6 +222,11 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
*/
public agendaItemObserver: BehaviorSubject<Item[]>;
/**
* Subject for tags
*/
public tagObserver: BehaviorSubject<Tag[]>;
/**
* Determine if the name of supporters are visible
*/
@ -356,6 +362,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
this.blockObserver = new BehaviorSubject(DS.getAll(MotionBlock));
this.mediafilesObserver = new BehaviorSubject(DS.getAll(Mediafile));
this.agendaItemObserver = new BehaviorSubject(DS.getAll(Item));
this.tagObserver = new BehaviorSubject(DS.getAll(Tag));
// Make sure the subjects are updated, when a new Model for the type arrives
this.DS.changeObservable.subscribe(newModel => {
@ -372,6 +379,8 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
this.mediafilesObserver.next(DS.getAll(Mediafile));
} else if (newModel instanceof Item) {
this.agendaItemObserver.next(DS.getAll(Item));
} else if (newModel instanceof Tag) {
this.tagObserver.next(DS.getAll(Tag));
}
});
@ -1013,6 +1022,17 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
}
}
/**
* Adds or removes a tag to the current motion
*
* @param id Motion tag id
*/
public setTag(event: MouseEvent, id: number): void {
console.log('event: ', event);
event.stopPropagation();
this.repo.setTag(this.motion, id);
}
/**
* Add the current motion to a motion block
*

View File

@ -12,6 +12,7 @@ import { ViewMotionCommentSection } from './view-motion-comment-section';
import { Workflow } from '../../../shared/models/motions/workflow';
import { WorkflowState } from '../../../shared/models/motions/workflow-state';
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
import { Tag } from 'app/shared/models/core/tag';
/**
* The line numbering mode for the motion detail view.
@ -51,6 +52,7 @@ export class ViewMotion extends BaseProjectableModel {
protected _item: Item;
protected _block: MotionBlock;
protected _attachments: Mediafile[];
protected _tags: Tag[];
public personalNote: PersonalNoteContent;
/**
@ -232,6 +234,10 @@ export class ViewMotion extends BaseProjectableModel {
return this._attachments ? this._attachments : null;
}
public get tags(): Tag[] {
return this._tags ? this._tags : null;
}
/**
* @returns the creation date as Date object
*/
@ -313,7 +319,8 @@ export class ViewMotion extends BaseProjectableModel {
state?: WorkflowState,
item?: Item,
block?: MotionBlock,
attachments?: Mediafile[]
attachments?: Mediafile[],
tags?: Tag[]
) {
super();
this._motion = motion;
@ -325,6 +332,7 @@ export class ViewMotion extends BaseProjectableModel {
this._item = item;
this._block = block;
this._attachments = attachments;
this._tags = tags;
}
public getTitle(): string {
@ -364,6 +372,8 @@ export class ViewMotion extends BaseProjectableModel {
this.updateUser(update as User);
} else if (update instanceof Mediafile) {
this.updateAttachments(update as Mediafile);
} else if (update instanceof Tag) {
this.updateTags(update as Tag);
}
}
@ -381,7 +391,7 @@ export class ViewMotion extends BaseProjectableModel {
/**
* Update routine for the workflow
*
* @param workflow potentially the changed workflow (state). Needs manual verification
* @param workflow potentially the (changed workflow (state). Needs manual verification
*/
public updateWorkflow(workflow: Workflow): void {
if (this.motion && workflow.id === this.motion.workflow_id) {
@ -443,6 +453,15 @@ export class ViewMotion extends BaseProjectableModel {
}
}
public updateTags(update: Tag): void {
if (this.motion) {
if (this.tags_id && this.tags_id.includes(update.id)) {
const tagIndex = this.tags.findIndex(tag => tag.id === update.id);
this.tags[tagIndex] = update as Tag;
}
}
}
public hasSupporters(): boolean {
return !!(this.supporters && this.supporters.length > 0);
}
@ -451,6 +470,10 @@ export class ViewMotion extends BaseProjectableModel {
return !!(this.attachments && this.attachments.length > 0);
}
public hasTags(): boolean {
return !!(this.tags && this.tags.length > 0);
}
public isStatuteAmendment(): boolean {
return !!this.statute_paragraph_id;
}
@ -508,7 +531,8 @@ export class ViewMotion extends BaseProjectableModel {
this._state,
this._item,
this._block,
this._attachments
this._attachments,
this._tags
);
}
}