Merge pull request #3927 from MaximilianKrambach/tags

Tags
This commit is contained in:
Sean 2018-10-24 11:50:24 +02:00 committed by GitHub
commit 02fe1817cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 382 additions and 6 deletions

View File

@ -10,6 +10,7 @@ import { ConfigAppConfig } from '../../site/config/config.config';
import { AgendaAppConfig } from '../../site/agenda/agenda.config';
import { AssignmentsAppConfig } from '../../site/assignments/assignments.config';
import { UsersAppConfig } from '../../site/users/users.config';
import { TagAppConfig } from '../../site/tags/tag.config';
import { MainMenuService } from './main-menu.service';
/**
@ -22,6 +23,7 @@ const appConfigs: AppConfig[] = [
AssignmentsAppConfig,
MotionsAppConfig,
MediafileAppConfig,
TagAppConfig,
UsersAppConfig
];

View File

@ -42,6 +42,10 @@ const routes: Routes = [
{
path: 'users',
loadChildren: './users/users.module#UsersModule'
},
{
path: 'tags',
loadChildren: './tags/tag.module#TagModule'
}
],
canActivateChild: [AuthGuard]

View File

@ -0,0 +1,33 @@
<os-head-bar [nav]="false" [backButton]=true [allowEdit]="true" [editMode]="editTag" editIcon="add" (editEvent)="setEditMode($event)"
(saveEvent)="saveTag()">
<!-- Title -->
<div class="title-slot">
<h2 *ngIf="!editTag && !newTag" translate>Tags</h2>
<form *ngIf="editTag" [formGroup]="tagForm" (ngSubmit)="saveTag()" (keydown)="keyDownFunction($event)">
<mat-form-field>
<input type="text" matInput osAutofocus required formControlName="name" placeholder="{{ 'New tag name' | translate}}">
<mat-error *ngIf="tagForm.invalid" translate>A tag name is required</mat-error>
</mat-form-field>
</form>
</div>
<!-- remove button -->
<div class="extra-controls-slot on-transition-fade">
<button *ngIf="editTag && !newTag" type="button" mat-button (click)="deleteSelectedTag()">
<mat-icon>delete</mat-icon>
<span translate>Delete</span>
</button>
</div>
</os-head-bar>
<mat-table class='os-listview-table on-transition-fade' [dataSource]="dataSource" matSort>
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef mat-sort-header> Name </mat-header-cell>
<mat-cell *matCellDef="let tag"> {{tag.getTitle()}} </mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="['name']"></mat-header-row>
<mat-row (click)='selectTag(row)' *matRowDef="let row; columns: ['name']"></mat-row>
</mat-table>
<mat-paginator class="on-transition-fade" [pageSizeOptions]="[25, 50, 75, 100, 125]"></mat-paginator>

View File

@ -0,0 +1,26 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TagListComponent } from './tag-list.component';
import { E2EImportsModule } from '../../../../e2e-imports.module';
describe('TagListComponent', () => {
let component: TagListComponent;
let fixture: ComponentFixture<TagListComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule],
declarations: [TagListComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TagListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,140 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { Title } from '@angular/platform-browser';
import { Tag } from '../../../shared/models/core/tag';
import { ListViewBaseComponent } from '../../base/list-view-base';
import { TagRepositoryService } from '../services/tag-repository.service';
import { ViewTag } from '../models/view-tag';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { PromptService } from '../../../core/services/prompt.service';
/**
* Listview for the complete lsit of available Tags
* ### Usage:
* ```html
* <os-tag-list></os-tag-list>
* ```
*/
@Component({
selector: 'os-tag-list',
templateUrl: './tag-list.component.html',
styleUrls: ['./tag-list.component.css']
})
export class TagListComponent extends ListViewBaseComponent<ViewTag> implements OnInit {
public editTag = false;
public newTag = false;
public selectedTag: ViewTag;
@ViewChild('tagForm')
public tagForm: FormGroup;
/**
* Constructor.
* @param titleService
* @param translate
* @param repo the repository
*/
public constructor(
titleService: Title,
translate: TranslateService,
private repo: TagRepositoryService,
private promptService: PromptService
) {
super(titleService, translate);
}
/**
* Init function.
* Sets the title, inits the table and calls the repo.
*/
public ngOnInit(): void {
super.setTitle('Tags');
this.initTable();
this.tagForm = new FormGroup({ name: new FormControl('', Validators.required) });
this.repo.getViewModelListObservable().subscribe(newTags => {
this.dataSource.data = newTags;
});
}
/**
* Sends a new or updates tag to the dataStore
*/
public saveTag(): void {
if (this.editTag && this.newTag) {
this.submitNewTag();
} else if (this.editTag && !this.newTag) {
this.submitEditedTag();
}
}
/**
* Saves a newly created tag.
*/
public submitNewTag(): void {
if (this.tagForm.value && this.tagForm.valid) {
this.repo.create(this.tagForm.value).subscribe(response => {
if (response) {
this.tagForm.reset();
this.cancelEditing();
}
});
}
}
/**
* Saves an edited tag.
*/
public submitEditedTag(): void {
if (this.tagForm.value && this.tagForm.valid) {
const updateData = new Tag({ name: this.tagForm.value.name });
this.repo.update(updateData, this.selectedTag).subscribe(response => {
if (response) {
this.cancelEditing();
}
});
}
}
/**
* Deletes the selected Tag after a successful confirmation.
* @async
*/
public async deleteSelectedTag(): Promise<any> {
const content = this.translate.instant('Delete') + ` ${this.selectedTag.name}?`;
if (await this.promptService.open(this.translate.instant('Are you sure?'), content)) {
this.repo.delete(this.selectedTag).subscribe(response => {
this.cancelEditing();
});
}
}
public cancelEditing(): void {
this.newTag = false;
this.editTag = false;
this.tagForm.reset();
}
/**
* Select a row in the table
* @param viewTag
*/
public selectTag(viewTag: ViewTag): void {
this.selectedTag = viewTag;
this.setEditMode(true, false);
this.tagForm.setValue({ name: this.selectedTag.name });
}
public setEditMode(mode: boolean, newTag: boolean = true): void {
this.editTag = mode;
this.newTag = newTag;
if (!mode) {
this.cancelEditing();
}
}
public keyDownFunction(event: KeyboardEvent): void {
if (event.keyCode === 27) {
this.cancelEditing();
}
}
}

View File

@ -0,0 +1,42 @@
import { Tag } from '../../../shared/models/core/tag';
import { BaseViewModel } from '../../base/base-view-model';
/**
* Tag view class
*
* Stores a Tag including all (implicit) references
* Provides "safe" access to variables and functions in {@link Tag}
* @ignore
*/
export class ViewTag extends BaseViewModel {
private _tag: Tag;
public constructor(tag: Tag) {
super();
this._tag = tag;
}
public get tag(): Tag {
return this._tag;
}
public get id(): number {
return this.tag ? this.tag.id : null;
}
public get name(): string {
return this.tag ? this.tag.name : null;
}
public getTitle(): string {
return this.name;
}
/**
* Updates the local objects if required
* @param update
*/
public updateValues(update: Tag): void {
this._tag = update;
}
}

View File

@ -0,0 +1,17 @@
import { TestBed } from '@angular/core/testing';
import { TagRepositoryService } from './tag-repository.service';
import { E2EImportsModule } from '../../../../e2e-imports.module';
describe('TagRepositoryService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [E2EImportsModule],
providers: [TagRepositoryService]
});
});
it('should be created', () => {
const service = TestBed.get(TagRepositoryService);
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,54 @@
import { Injectable } from '@angular/core';
import { Tag } from '../../../shared/models/core/tag';
import { ViewTag } from '../models/view-tag';
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';
import { HTTPMethod } from 'app/core/services/http.service';
/**
* Repository Services for Tags
*
* 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 TagRepositoryService extends BaseRepository<ViewTag, Tag> {
/**
* Creates a TagRepository
* Converts existing and incoming Tags to ViewTags
* Handles CRUD using an observer to the DataStore
* @param DataSend
*/
public constructor(protected DS: DataStoreService, private dataSend: DataSendService) {
super(DS, Tag);
}
protected createViewModel(tag: Tag): ViewTag {
return new ViewTag(tag);
}
public create(update: Tag): Observable<any> {
const newTag = new Tag();
newTag.patchValues(update);
return this.dataSend.createModel(newTag);
}
public update(update: Partial<Tag>, viewTag: ViewTag): Observable<any> {
const updateTag = new Tag();
updateTag.patchValues(viewTag.tag);
updateTag.patchValues(update);
return this.dataSend.updateModel(updateTag, HTTPMethod.PUT);
}
public delete(viewTag: ViewTag): Observable<any> {
return this.dataSend.deleteModel(viewTag.tag);
}
}

View File

@ -0,0 +1,11 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { TagListComponent } from './components/tag-list.component';
const routes: Routes = [{ path: '', component: TagListComponent }];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class TagRoutingModule {}

View File

@ -0,0 +1,16 @@
import { AppConfig } from '../base/app-config';
import { Tag } from '../../shared/models/core/tag';
export const TagAppConfig: AppConfig = {
name: 'tag',
models: [{ collectionString: 'core/tag', model: Tag }],
mainMenuEntries: [
{
route: '/tags',
displayName: 'Tags',
icon: 'turned_in',
weight: 1100,
permission: 'core.can_manage_tags'
}
]
};

View File

@ -0,0 +1,13 @@
import { TagModule } from './tag.module';
describe('MotionsModule', () => {
let tagModule: TagModule;
beforeEach(() => {
tagModule = new TagModule();
});
it('should create an instance', () => {
expect(tagModule).toBeTruthy();
});
});

View File

@ -0,0 +1,12 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TagRoutingModule } from './tag-routing.module';
import { SharedModule } from '../../shared/shared.module';
import { TagListComponent } from './components/tag-list.component';
@NgModule({
imports: [CommonModule, TagRoutingModule, SharedModule],
declarations: [TagListComponent]
})
export class TagModule {}

View File

@ -26,6 +26,12 @@ module.exports = function(config) {
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
singleRun: false,
proxies: {
'/apps/': 'http://localhost:8000/apps/',
'/media/': 'http://localhost:8000/media/',
'/rest/': 'http://localhost:8000/rest/',
'/ws/site/': 'ws://localhost:8000/ws/site'
}
});
};

View File

@ -29,12 +29,12 @@
<table os-perms="core.can_manage_tags" class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th ng-click="toggleSort('name')" class="sortable">
<translate>Name</translate>
<i class="pull-right fa" ng-show="sortColumn === 'name' && header.sortable != false"
<tr>
<th ng-click="toggleSort('name')" class="sortable">
<translate>Name</translate>
<i class="pull-right fa" ng-show="sortColumn === 'name' && header.sortable != false"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
</i>
<tbody>
<tr ng-repeat="tag in tags | filter: filter.search |
orderBy: sortColumn:reverse">