From 48526d6c19bf2fd280cf720fa9468103278b725a Mon Sep 17 00:00:00 2001 From: Jochen Saalfeld Date: Wed, 19 Sep 2018 15:18:57 +0200 Subject: [PATCH] implementing categories --- client/package-lock.json | 8 +- .../head-bar/head-bar.component.html | 3 +- .../head-bar/head-bar.component.scss | 4 + .../components/head-bar/head-bar.component.ts | 19 +- .../search-value-selector.component.html | 6 +- .../search-value-selector.component.ts | 16 +- .../src/app/shared/models/motions/category.ts | 7 + .../services/agenda-repository.service.ts | 2 +- .../services/assignment-repository.service.ts | 2 +- client/src/app/site/base/base-repository.ts | 2 +- .../services/mediafile-repository.service.ts | 2 +- .../category-list.component.html | 75 +++++-- .../category-list.component.scss | 44 ++++ .../category-list/category-list.component.ts | 208 +++++++++++++++--- .../motion-detail.component.html | 10 +- .../motion-detail/motion-detail.component.ts | 54 +++-- .../app/site/motions/models/view-category.ts | 98 +++++++++ .../category-repository.service.spec.ts | 15 ++ .../services/category-repository.service.ts | 83 +++++++ .../services/motion-repository.service.ts | 11 +- .../services/config-repository.service.ts | 2 +- .../users/services/user-repository.service.ts | 2 +- client/src/styles.scss | 6 - 23 files changed, 571 insertions(+), 108 deletions(-) create mode 100644 client/src/app/site/motions/models/view-category.ts create mode 100644 client/src/app/site/motions/services/category-repository.service.spec.ts create mode 100644 client/src/app/site/motions/services/category-repository.service.ts diff --git a/client/package-lock.json b/client/package-lock.json index f88cb9eb2..61cc07ec9 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -4779,13 +4779,15 @@ "version": "1.0.0", "resolved": false, "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4808,7 +4810,8 @@ "version": "0.0.1", "resolved": false, "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", @@ -4975,6 +4978,7 @@ "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } diff --git a/client/src/app/shared/components/head-bar/head-bar.component.html b/client/src/app/shared/components/head-bar/head-bar.component.html index c92a32747..aab8a6cb8 100644 --- a/client/src/app/shared/components/head-bar/head-bar.component.html +++ b/client/src/app/shared/components/head-bar/head-bar.component.html @@ -1,5 +1,6 @@ - diff --git a/client/src/app/shared/components/head-bar/head-bar.component.scss b/client/src/app/shared/components/head-bar/head-bar.component.scss index e69de29bb..f1b01bb55 100644 --- a/client/src/app/shared/components/head-bar/head-bar.component.scss +++ b/client/src/app/shared/components/head-bar/head-bar.component.scss @@ -0,0 +1,4 @@ +.head-button { + bottom: -30px; + z-index: 100; +} diff --git a/client/src/app/shared/components/head-bar/head-bar.component.ts b/client/src/app/shared/components/head-bar/head-bar.component.ts index 8ee96c415..06935dbec 100644 --- a/client/src/app/shared/components/head-bar/head-bar.component.ts +++ b/client/src/app/shared/components/head-bar/head-bar.component.ts @@ -16,10 +16,10 @@ import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; * ```html * + * (ellipsisMenuItem)=onEllipsisItem($event) * * ``` * @@ -54,29 +54,34 @@ export class HeadBarComponent implements OnInit { /** * Input declaration for the app name */ - @Input() public appName: string; + @Input() + public appName: string; /** * Determine if there should be a plus button. */ - @Input() public plusButton: false; + @Input() + public plusButton: false; /** * If not empty shows a ellipsis menu on the right side * * The parent needs to provide a menu, i.e `[menuList]=myMenu`. */ - @Input() public menuList: any[]; + @Input() + public menuList: any[]; /** * Emit a signal to the parent component if the plus button was clicked */ - @Output() public plusButtonClicked = new EventEmitter(); + @Output() + public plusButtonClicked = new EventEmitter(); /** * Emit a signal to the parent of an item in the menuList was selected. */ - @Output() public ellipsisMenuItem = new EventEmitter(); + @Output() + public ellipsisMenuItem = new EventEmitter(); /** * Empty constructor diff --git a/client/src/app/shared/components/search-value-selector/search-value-selector.component.html b/client/src/app/shared/components/search-value-selector/search-value-selector.component.html index cfedb9bfb..d44e9a1d7 100644 --- a/client/src/app/shared/components/search-value-selector/search-value-selector.component.html +++ b/client/src/app/shared/components/search-value-selector/search-value-selector.component.html @@ -1,6 +1,10 @@ - + +
+ None + +
{{selectedItem.getTitle(translate)}} diff --git a/client/src/app/shared/components/search-value-selector/search-value-selector.component.ts b/client/src/app/shared/components/search-value-selector/search-value-selector.component.ts index c7f24ab38..246f6b2a3 100644 --- a/client/src/app/shared/components/search-value-selector/search-value-selector.component.ts +++ b/client/src/app/shared/components/search-value-selector/search-value-selector.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit, Input, ViewChild } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; -import { ReplaySubject, Subject } from 'rxjs'; +import { Subject, BehaviorSubject } from 'rxjs'; import { MatSelect } from '@angular/material'; import { takeUntil } from 'rxjs/operators'; import { Displayable } from '../../models/base/displayable'; @@ -20,7 +20,7 @@ import { TranslateService } from '@ngx-translate/core'; * ```html * = new ReplaySubject(1); + public filteredItems: BehaviorSubject; /** * Decide if this should be a single or multi-select-field @@ -62,7 +62,7 @@ export class SearchValueSelectorComponent implements OnInit { * The Input List Values */ @Input() - public InputListValues: Displayable[]; + public InputListValues: BehaviorSubject; /** * Placeholder of the List @@ -111,13 +111,11 @@ export class SearchValueSelectorComponent implements OnInit { * onInit with filter ans subscription on filter */ public ngOnInit(): void { - // load the initial item list - this.filteredItems.next(this.InputListValues.slice()); + this.filteredItems = this.InputListValues; // listen to value changes this.filterControl.valueChanges.pipe(takeUntil(this._onDestroy)).subscribe(() => { this.filterItems(); }); - // this.multiSelect.stateChanges.subscribe(fn => console.log('ive changed')); } /** @@ -130,14 +128,14 @@ export class SearchValueSelectorComponent implements OnInit { // get the search keyword let search = this.filterControl.value; if (!search) { - this.filteredItems.next(this.InputListValues.slice()); + this.filteredItems.next(this.InputListValues.getValue()); return; } else { search = search.toLowerCase(); } // filter the values this.filteredItems.next( - this.InputListValues.filter( + this.InputListValues.getValue().filter( selectedItem => selectedItem .toString() diff --git a/client/src/app/shared/models/motions/category.ts b/client/src/app/shared/models/motions/category.ts index d772a8925..978ddf4ad 100644 --- a/client/src/app/shared/models/motions/category.ts +++ b/client/src/app/shared/models/motions/category.ts @@ -16,6 +16,13 @@ export class Category extends BaseModel { public getTitle(): string { return this.prefix + ' - ' + this.name; } + + /** + * update the values of the motion with new values + */ + public patchValues(update: object): void { + Object.assign(this, update); + } } BaseModel.registerCollectionElement('motions/category', Category); 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 d49400ff0..db79fff72 100644 --- a/client/src/app/site/agenda/services/agenda-repository.service.ts +++ b/client/src/app/site/agenda/services/agenda-repository.service.ts @@ -49,7 +49,7 @@ export class AgendaRepositoryService extends BaseRepository { * * TODO: used over not-yet-existing detail view */ - public save(item: Item, viewUser: ViewItem): Observable { + public update(item: Item, viewUser: ViewItem): Observable { return null; } diff --git a/client/src/app/site/assignments/services/assignment-repository.service.ts b/client/src/app/site/assignments/services/assignment-repository.service.ts index 135b725a7..3e8cca80e 100644 --- a/client/src/app/site/assignments/services/assignment-repository.service.ts +++ b/client/src/app/site/assignments/services/assignment-repository.service.ts @@ -25,7 +25,7 @@ export class AssignmentRepositoryService extends BaseRepository { + public update(assignment: Assignment, viewAssignment: ViewAssignment): Observable { return null; } diff --git a/client/src/app/site/base/base-repository.ts b/client/src/app/site/base/base-repository.ts index 10d1c2846..b5668404c 100644 --- a/client/src/app/site/base/base-repository.ts +++ b/client/src/app/site/base/base-repository.ts @@ -75,7 +75,7 @@ export abstract class BaseRepository; + public abstract update(update: M, viewModel: V): Observable; /** * Deletes a given Model diff --git a/client/src/app/site/mediafiles/services/mediafile-repository.service.ts b/client/src/app/site/mediafiles/services/mediafile-repository.service.ts index 09e5ab70d..5e3218f64 100644 --- a/client/src/app/site/mediafiles/services/mediafile-repository.service.ts +++ b/client/src/app/site/mediafiles/services/mediafile-repository.service.ts @@ -27,7 +27,7 @@ export class MediafileRepositoryService extends BaseRepository { + public update(file: Mediafile, viewFile: ViewMediafile): Observable { return null; } diff --git a/client/src/app/site/motions/components/category-list/category-list.component.html b/client/src/app/site/motions/components/category-list/category-list.component.html index 1194f89bf..b36668ae2 100644 --- a/client/src/app/site/motions/components/category-list/category-list.component.html +++ b/client/src/app/site/motions/components/category-list/category-list.component.html @@ -1,19 +1,58 @@ - + - - - - - Name - {{category.name}} - - - - - Prefix - {{category.prefix}} - - - - - +
+ +
+ + + + + {{category.name}} + + + {{this.formGroup.get('name').value}} + + + {{category.prefix}} + + + {{this.formGroup.get('prefix').value}} + + +
+ Edit category details:
+ + + + Required + + + + + + Required + + +
+ + + + + + +
+
diff --git a/client/src/app/site/motions/components/category-list/category-list.component.scss b/client/src/app/site/motions/components/category-list/category-list.component.scss index e69de29bb..1826997c7 100644 --- a/client/src/app/site/motions/components/category-list/category-list.component.scss +++ b/client/src/app/site/motions/components/category-list/category-list.component.scss @@ -0,0 +1,44 @@ +.button-side { + right: 0; + top: 0px; + float: right; +} + +.text-side { + size: 50%; +} + +.content-row { + size: 100%; +} + +.new { + // put in theme later + background-color: lightblue; +} + +.mini-button { + top: 0px; + width: 20px; + height: 20px; + min-height: 20px; + font-size: 10px; + box-shadow: none; + vertical-align: top; + padding: 0 0; + margin: 0; +} + +.onethird { + width: 33%; +} + +.custom-table-header { + // display: none; + width: 100%; + height: 60px; + line-height: 60px; + text-align: right; + background: white; + border-bottom: 1px solid rgba(0, 0, 0, 0.12); +} diff --git a/client/src/app/site/motions/components/category-list/category-list.component.ts b/client/src/app/site/motions/components/category-list/category-list.component.ts index 2923a1761..79d84db62 100644 --- a/client/src/app/site/motions/components/category-list/category-list.component.ts +++ b/client/src/app/site/motions/components/category-list/category-list.component.ts @@ -1,59 +1,98 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; +import { Component, OnInit, OnDestroy } from '@angular/core'; import { Title } from '@angular/platform-browser'; -import { MatSort, MatTable, MatTableDataSource } from '@angular/material'; import { TranslateService } from '@ngx-translate/core'; import { BaseComponent } from '../../../../base.component'; import { Category } from '../../../../shared/models/motions/category'; -import { DataStoreService } from '../../../../core/services/data-store.service'; +import { CategoryRepositoryService } from '../../services/category-repository.service'; +import { ViewCategory } from '../../models/view-category'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; /** * List view for the categories. - * - * TODO: Creation of new Categories */ @Component({ selector: 'os-category-list', templateUrl: './category-list.component.html', styleUrls: ['./category-list.component.scss'] }) -export class CategoryListComponent extends BaseComponent implements OnInit { +export class CategoryListComponent extends BaseComponent implements OnInit, OnDestroy { /** - * Store the categories + * States the edit mode */ - public categoryArray: Array; + public editMode = false; /** - * Will be processed by the mat-table + * Source of the Data */ - public dataSource: MatTableDataSource; + public dataSource: Array; /** - * The table itself. + * The current focussed formgroup */ - @ViewChild(MatTable) - public table: MatTable; - - /** - * Sort the Table - */ - @ViewChild(MatSort) - public sort: MatSort; + public formGroup: FormGroup; /** * The usual component constructor * @param titleService * @param translate + * @param repo + * @param formBuilder */ public constructor( protected titleService: Title, protected translate: TranslateService, - protected DS: DataStoreService + private repo: CategoryRepositoryService, + private formBuilder: FormBuilder ) { super(titleService, translate); + this.formGroup = this.formBuilder.group({ + name: ['', Validators.required], + prefix: ['', Validators.required] + }); } + /** + * On Destroy Function + * + * Saves the edits + */ + public ngOnDestroy(): void { + this.dataSource.forEach(viewCategory => { + if (viewCategory.edit && viewCategory.opened) { + const nameControl = this.formGroup.get('name'); + const prefixControl = this.formGroup.get('prefix'); + const nameValue = nameControl.value; + const prefixValue = prefixControl.value; + viewCategory.name = nameValue; + viewCategory.prefix = prefixValue; + this.saveCategory(viewCategory); + } + }); + } + + /** + * Event on Key Down in form + */ + public keyDownFunction(event: KeyboardEvent, viewCategory: ViewCategory): void { + if (event.keyCode === 13) { + this.onSaveButton(viewCategory); + } + } + + /** + * Stores the Datamodel in the repo + * @param viewCategory + */ + private saveCategory(viewCategory: ViewCategory): void { + if (this.repo.osInDataStore(viewCategory)) { + this.repo.create(viewCategory).subscribe(); + } else { + this.repo.update(viewCategory).subscribe(); + } + viewCategory.edit = false; + } /** * Init function. * @@ -61,26 +100,127 @@ export class CategoryListComponent extends BaseComponent implements OnInit { */ public ngOnInit(): void { super.setTitle('Category'); - this.categoryArray = this.DS.getAll(Category); - this.dataSource = new MatTableDataSource(this.categoryArray); - this.dataSource.sort = this.sort; + this.repo.getViewModelListObservable().subscribe(newViewCategories => { + this.dataSource = newViewCategories; + }); + this.sortDataSource(); + } - // Observe DataStore for motions. Initially, executes once for every motion. - // The alternative approach is to put the observable as DataSource to the table - this.DS.changeObservable.subscribe(newModel => { - if (newModel instanceof Category) { - this.categoryArray = this.DS.getAll(Category); - this.dataSource.data = this.categoryArray; + /** + * Add a new Category. + */ + public onPlusButton(): void { + let noNewOnes = true; + this.dataSource.forEach(viewCategory => { + if (viewCategory.id === undefined) { + noNewOnes = false; + } + }); + if (noNewOnes) { + const newCategory = new Category(); + newCategory.id = undefined; + newCategory.name = this.translate.instant('Name'); + newCategory.prefix = this.translate.instant('Prefix'); + const newViewCategory = new ViewCategory(newCategory); + newViewCategory.opened = true; + this.dataSource.reverse(); + this.dataSource.push(newViewCategory); + this.dataSource.reverse(); + this.editMode = true; + } + } + + /** + * Executed on edit button + * @param viewCategory + */ + public onEditButton(viewCategory: ViewCategory): void { + viewCategory.edit = true; + viewCategory.synced = false; + this.editMode = true; + const nameControl = this.formGroup.get('name'); + const prefixControl = this.formGroup.get('prefix'); + nameControl.setValue(viewCategory.name); + prefixControl.setValue(viewCategory.prefix); + } + + /** + * Saves the categories + */ + public onSaveButton(viewCategory: ViewCategory): void { + if (this.formGroup.controls.name.valid && this.formGroup.controls.prefix.valid) { + this.editMode = false; + const nameControl = this.formGroup.get('name'); + const prefixControl = this.formGroup.get('prefix'); + const nameValue = nameControl.value; + const prefixValue = prefixControl.value; + if ( + viewCategory.id === undefined || + nameValue !== viewCategory.name || + prefixValue !== viewCategory.prefix + ) { + viewCategory.prefix = prefixValue; + viewCategory.name = nameValue; + this.saveCategory(viewCategory); + } + } + this.sortDataSource(); + } + + /** + * sorts the datasource by prefix alphabetically + */ + protected sortDataSource(): void { + this.dataSource.sort((viewCategory1, viewCategory2) => { + if (viewCategory1.prefix > viewCategory2.prefix) { + return 1; + } + if (viewCategory1.prefix < viewCategory2.prefix) { + return -1; } }); } /** - * Add a new Category. - * - * TODO: Not yet implemented + * executed on cancel button + * @param viewCategory */ - public onPlusButton(): void { - console.log('Add New Category'); + public onCancelButton(viewCategory: ViewCategory): void { + viewCategory.edit = false; + this.editMode = false; + } + + /** + * is executed, when the delete button is pressed + */ + public onDeleteButton(viewCategory: ViewCategory): void { + if (this.repo.osInDataStore(viewCategory) && viewCategory.id !== undefined) { + this.repo.delete(viewCategory).subscribe(); + } + const index = this.dataSource.indexOf(viewCategory, 0); + if (index > -1) { + this.dataSource.splice(index, 1); + } + // if no category is there, we setill have to be able to create one + if (this.dataSource.length < 1) { + this.editMode = false; + } + } + + /** + * Is executed when a mat-extension-panel is opened or closed + * @param open true if opened, false if being closed + * @param category the category in the panel + */ + public panelOpening(open: boolean, category: ViewCategory): void { + category.opened = open as boolean; + if (category.edit === true) { + this.onSaveButton(category); + this.onCancelButton(category); + } + if (!open) { + category.edit = false; + this.editMode = false; + } } } 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 d445f640b..9b87d4cd4 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 @@ -15,7 +15,7 @@ {{motion.title}} {{contentForm.get('title').value}}
-
+
by {{motion.submitters}}
@@ -148,7 +148,7 @@
- +
@@ -164,7 +164,7 @@
- +
@@ -218,11 +218,11 @@
-

Category

+

Category

{{motion.category}}
- +
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 c4d387969..579d443d4 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 @@ -10,6 +10,8 @@ import { MotionRepositoryService } from '../../services/motion-repository.servic import { ViewMotion } from '../../models/view-motion'; import { User } from '../../../../shared/models/users/user'; import { DataStoreService } from '../../../../core/services/data-store.service'; +import { BehaviorSubject } from 'rxjs'; +import { TranslateService } from '@ngx-translate/core'; /** * Component for the motion detail view @@ -64,6 +66,21 @@ export class MotionDetailComponent extends BaseComponent implements OnInit { */ public motionCopy: ViewMotion; + /** + * Subject for the Categories + */ + public categoryObserver: BehaviorSubject>; + + /** + * Subject for the Submitters + */ + public submitterObserver: BehaviorSubject>; + + /** + * Subject for the Supporters + */ + public supporterObserver: BehaviorSubject>; + /** * Constuct the detail view. * @@ -72,6 +89,7 @@ export class MotionDetailComponent extends BaseComponent implements OnInit { * @param route determine if this is a new or an existing motion * @param formBuilder For reactive forms. Form Group and Form Control * @param repo: Motion Repository + * @param translate: Translation Service */ public constructor( public vp: ViewportService, @@ -79,7 +97,8 @@ export class MotionDetailComponent extends BaseComponent implements OnInit { private route: ActivatedRoute, private formBuilder: FormBuilder, private repo: MotionRepositoryService, - private DS: DataStoreService + private DS: DataStoreService, + protected translate: TranslateService ) { super(); this.createForm(); @@ -100,6 +119,21 @@ export class MotionDetailComponent extends BaseComponent implements OnInit { }); }); } + // Initial Filling of the Subjects + this.submitterObserver = new BehaviorSubject(DS.getAll(User)); + this.supporterObserver = new BehaviorSubject(DS.getAll(User)); + this.categoryObserver = new BehaviorSubject(this.DS.getAll(Category)); + + // Make sure the subjects are updated, when a new Model for the type arrives + this.DS.changeObservable.subscribe(newModel => { + if (newModel instanceof User) { + this.submitterObserver.next(DS.getAll(User)); + this.supporterObserver.next(DS.getAll(User)); + } + if (newModel instanceof Category) { + this.categoryObserver.next(DS.getAll(Category)); + } + }); } /** @@ -107,7 +141,7 @@ export class MotionDetailComponent extends BaseComponent implements OnInit { */ public patchForm(formMotion: ViewMotion): void { this.metaInfoForm.patchValue({ - category_id: formMotion.categoryId, + category_id: formMotion.category, supporters_id: formMotion.supporters, submitters: formMotion.submitters, state_id: formMotion.stateId, @@ -162,17 +196,10 @@ export class MotionDetailComponent extends BaseComponent implements OnInit { this.router.navigate(['./motions/' + response.id]); }); } else { - this.repo.save(newMotionValues, this.motionCopy).subscribe(); + this.repo.update(newMotionValues, this.motionCopy).subscribe(); } } - /** - * return all Categories - */ - public getMotionCategories(): Category[] { - return this.DS.getAll(Category); - } - /** * Click on the edit button (pen-symbol) */ @@ -215,13 +242,6 @@ export class MotionDetailComponent extends BaseComponent implements OnInit { }); } - /** - * returns all Possible supporters - */ - public getAllUsers(): User[] { - return this.DS.getAll(User); - } - /** * Init. Does nothing here. */ diff --git a/client/src/app/site/motions/models/view-category.ts b/client/src/app/site/motions/models/view-category.ts new file mode 100644 index 000000000..b9a1a7f73 --- /dev/null +++ b/client/src/app/site/motions/models/view-category.ts @@ -0,0 +1,98 @@ +import { Category } from '../../../shared/models/motions/category'; +import { TranslateService } from '@ngx-translate/core'; +import { BaseViewModel } from '../../base/base-view-model'; + +/** + * Category class for the View + * + * Stores a Category including all (implicit) references + * Provides "safe" access to variables and functions in {@link Category} + * @ignore + */ +export class ViewCategory extends BaseViewModel { + private _category: Category; + private _edit: boolean; + private _synced: boolean; + private _opened: boolean; + + public get category(): Category { + return this._category; + } + + public get id(): number { + return this.category ? this.category.id : null; + } + + public get name(): string { + return this.category ? this.category.name : null; + } + + public get prefix(): string { + return this.category ? this.category.prefix : null; + } + + public set synced(bol: boolean) { + this._synced = bol; + } + + public set edit(bol: boolean) { + this._edit = bol; + } + + public set opened(bol: boolean) { + this._opened = bol; + } + + public set prefix(pref: string) { + this._category.prefix = pref; + } + + public set name(nam: string) { + this._category.name = nam; + } + + public get opened(): boolean { + return this._opened; + } + + public get synced(): boolean { + return this._synced; + } + + public get edit(): boolean { + return this._edit; + } + + public constructor(category?: Category, id?: number, prefix?: string, name?: string) { + super(); + if (!category) { + category = new Category(); + category.id = id; + category.name = name; + category.prefix = prefix; + } + this._category = category; + this._edit = false; + this._synced = true; + this._opened = false; + } + + public getTitle(translate?: TranslateService): string { + return this.name; + } + + /** + * Updates the local objects if required + * @param update + */ + public updateValues(update: Category): void { + this._category = update; + } + + /** + * Duplicate this motion into a copy of itself + */ + public copy(): ViewCategory { + return new ViewCategory(this._category); + } +} diff --git a/client/src/app/site/motions/services/category-repository.service.spec.ts b/client/src/app/site/motions/services/category-repository.service.spec.ts new file mode 100644 index 000000000..0d8fa398b --- /dev/null +++ b/client/src/app/site/motions/services/category-repository.service.spec.ts @@ -0,0 +1,15 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { CategoryRepositoryService } from './category-repository.service'; + +describe('CategoryRepositoryService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [CategoryRepositoryService] + }); + }); + + it('should be created', inject([CategoryRepositoryService], (service: CategoryRepositoryService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/client/src/app/site/motions/services/category-repository.service.ts b/client/src/app/site/motions/services/category-repository.service.ts new file mode 100644 index 000000000..078e5ccf1 --- /dev/null +++ b/client/src/app/site/motions/services/category-repository.service.ts @@ -0,0 +1,83 @@ +import { Injectable } from '@angular/core'; +import { Category } from '../../../shared/models/motions/category'; +import { ViewCategory } from '../models/view-category'; +import { DataSendService } from '../../../core/services/data-send.service'; +import { Observable } from 'rxjs'; +import { DataStoreService } from '../../../core/services/data-store.service'; +import { BaseRepository } from '../../base/base-repository'; + +/** + * Repository Services for Categories + * + * The repository is meant to process domain objects (those found under + * shared/models), so components can display them and interact with them. + * + * Rather than manipulating models directly, the repository is meant to + * inform the {@link DataSendService} about changes which will send + * them to the Server. + */ +@Injectable({ + providedIn: 'root' +}) +export class CategoryRepositoryService extends BaseRepository { + /** + * Creates a CategoryRepository + * Converts existing and incoming category to ViewCategories + * Handles CRUD using an observer to the DataStore + * @param DataSend + */ + public constructor(protected DS: DataStoreService, private dataSend: DataSendService) { + super(DS, Category); + } + + protected createViewModel(category: Category): ViewCategory { + return new ViewCategory(category); + } + + public create(update: object, viewCategory?: ViewCategory): Observable { + const categories = this.DS.getAll(Category); + const categoryIds: number[] = []; + if (update instanceof Category) { + viewCategory = new ViewCategory(update); + } + if (update instanceof ViewCategory) { + viewCategory = update; + } + categories.forEach(category => { + categoryIds.push(category.id); + }); + if (viewCategory.id in categoryIds) { + return this.update(update, viewCategory); + } else { + return this.dataSend.saveModel(viewCategory.category); + } + } + + public update(update: object, viewCategory?: ViewCategory): Observable { + let updateCategory: Category; + if (viewCategory) { + updateCategory = viewCategory.category; + } else { + updateCategory = new Category(); + } + updateCategory.patchValues(update); + return this.dataSend.saveModel(updateCategory); + } + + public delete(viewCategory: ViewCategory): Observable { + const category = viewCategory.category; + return this.dataSend.delete(category); + } + + /** + * Checks if a Catagory is on the server already + * @param viewCategory the category to check if it is already on the server + */ + public osInDataStore(viewCategory: ViewCategory): boolean { + const serverCategoryArray = this.DS.getAll(Category); + if (serverCategoryArray.find(cat => cat.id === viewCategory.id)) { + return true; + } + return false; + } +} diff --git a/client/src/app/site/motions/services/motion-repository.service.ts b/client/src/app/site/motions/services/motion-repository.service.ts index 0c3882e6f..17ba67dff 100644 --- a/client/src/app/site/motions/services/motion-repository.service.ts +++ b/client/src/app/site/motions/services/motion-repository.service.ts @@ -66,7 +66,7 @@ export class MotionRepositoryService extends BaseRepository * TODO: Remove the viewMotion and make it actually distignuishable from save() */ public create(update: any, viewMotion?: ViewMotion): Observable { - return this.save(update, viewMotion); + return this.update(update, viewMotion); } /** @@ -78,7 +78,7 @@ export class MotionRepositoryService extends BaseRepository * @param update the form data containing the update values * @param viewMotion The View Motion. If not present, a new motion will be created */ - public save(update: any, viewMotion?: ViewMotion): Observable { + public update(update: any, viewMotion?: ViewMotion): Observable { let updateMotion: Motion; if (viewMotion) { // implies that an existing motion was updated @@ -109,6 +109,13 @@ export class MotionRepositoryService extends BaseRepository }); } update.supporters_id = supporterIds; + // category_id: Category -> category_id: number; + const category = update.category_id as Category; + update.category_id = undefined; + if (category) { + update.category_id = category.id; + } + // Update the Motion updateMotion.patchValues(update); return this.dataSend.saveModel(updateMotion); } diff --git a/client/src/app/site/settings/services/config-repository.service.ts b/client/src/app/site/settings/services/config-repository.service.ts index 1e5958049..de99c5a6f 100644 --- a/client/src/app/site/settings/services/config-repository.service.ts +++ b/client/src/app/site/settings/services/config-repository.service.ts @@ -27,7 +27,7 @@ export class ConfigRepositoryService extends BaseRepository * * TODO: used over not-yet-existing detail view */ - public save(config: Config, viewConfig: ViewConfig): Observable { + public update(config: Config, viewConfig: ViewConfig): Observable { return null; } diff --git a/client/src/app/site/users/services/user-repository.service.ts b/client/src/app/site/users/services/user-repository.service.ts index e79e78da0..f9dfc1b23 100644 --- a/client/src/app/site/users/services/user-repository.service.ts +++ b/client/src/app/site/users/services/user-repository.service.ts @@ -28,7 +28,7 @@ export class UserRepositoryService extends BaseRepository { * * TODO: used over not-yet-existing detail view */ - public save(user: User, viewUser: ViewUser): Observable { + public update(user: User, viewUser: ViewUser): Observable { return null; } diff --git a/client/src/styles.scss b/client/src/styles.scss index 15d6640f6..ecb69e0ad 100644 --- a/client/src/styles.scss +++ b/client/src/styles.scss @@ -21,12 +21,6 @@ body { padding: 0; } -/**the plus button in Motion, Agenda, etc*/ -.generic-plus-button { - bottom: -30px; - z-index: 100; -} - .generic-mini-button { bottom: -28px; z-index: 100;