commit
02fe1817cb
@ -10,6 +10,7 @@ import { ConfigAppConfig } from '../../site/config/config.config';
|
|||||||
import { AgendaAppConfig } from '../../site/agenda/agenda.config';
|
import { AgendaAppConfig } from '../../site/agenda/agenda.config';
|
||||||
import { AssignmentsAppConfig } from '../../site/assignments/assignments.config';
|
import { AssignmentsAppConfig } from '../../site/assignments/assignments.config';
|
||||||
import { UsersAppConfig } from '../../site/users/users.config';
|
import { UsersAppConfig } from '../../site/users/users.config';
|
||||||
|
import { TagAppConfig } from '../../site/tags/tag.config';
|
||||||
import { MainMenuService } from './main-menu.service';
|
import { MainMenuService } from './main-menu.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -22,6 +23,7 @@ const appConfigs: AppConfig[] = [
|
|||||||
AssignmentsAppConfig,
|
AssignmentsAppConfig,
|
||||||
MotionsAppConfig,
|
MotionsAppConfig,
|
||||||
MediafileAppConfig,
|
MediafileAppConfig,
|
||||||
|
TagAppConfig,
|
||||||
UsersAppConfig
|
UsersAppConfig
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -42,6 +42,10 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: 'users',
|
path: 'users',
|
||||||
loadChildren: './users/users.module#UsersModule'
|
loadChildren: './users/users.module#UsersModule'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'tags',
|
||||||
|
loadChildren: './tags/tag.module#TagModule'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
canActivateChild: [AuthGuard]
|
canActivateChild: [AuthGuard]
|
||||||
|
33
client/src/app/site/tags/components/tag-list.component.html
Normal file
33
client/src/app/site/tags/components/tag-list.component.html
Normal 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>
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
140
client/src/app/site/tags/components/tag-list.component.ts
Normal file
140
client/src/app/site/tags/components/tag-list.component.ts
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
42
client/src/app/site/tags/models/view-tag.ts
Normal file
42
client/src/app/site/tags/models/view-tag.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
54
client/src/app/site/tags/services/tag-repository.service.ts
Normal file
54
client/src/app/site/tags/services/tag-repository.service.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
11
client/src/app/site/tags/tag-routing.module.ts
Normal file
11
client/src/app/site/tags/tag-routing.module.ts
Normal 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 {}
|
16
client/src/app/site/tags/tag.config.ts
Normal file
16
client/src/app/site/tags/tag.config.ts
Normal 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'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
13
client/src/app/site/tags/tag.module.spec.ts
Normal file
13
client/src/app/site/tags/tag.module.spec.ts
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
12
client/src/app/site/tags/tag.module.ts
Normal file
12
client/src/app/site/tags/tag.module.ts
Normal 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 {}
|
@ -26,6 +26,12 @@ module.exports = function(config) {
|
|||||||
logLevel: config.LOG_INFO,
|
logLevel: config.LOG_INFO,
|
||||||
autoWatch: true,
|
autoWatch: true,
|
||||||
browsers: ['Chrome'],
|
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'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -29,12 +29,12 @@
|
|||||||
|
|
||||||
<table os-perms="core.can_manage_tags" class="table table-striped table-bordered table-hover">
|
<table os-perms="core.can_manage_tags" class="table table-striped table-bordered table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th ng-click="toggleSort('name')" class="sortable">
|
<th ng-click="toggleSort('name')" class="sortable">
|
||||||
<translate>Name</translate>
|
<translate>Name</translate>
|
||||||
<i class="pull-right fa" ng-show="sortColumn === 'name' && header.sortable != false"
|
<i class="pull-right fa" ng-show="sortColumn === 'name' && header.sortable != false"
|
||||||
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
|
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
|
||||||
</i>
|
</i>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr ng-repeat="tag in tags | filter: filter.search |
|
<tr ng-repeat="tag in tags | filter: filter.search |
|
||||||
orderBy: sortColumn:reverse">
|
orderBy: sortColumn:reverse">
|
||||||
|
Loading…
Reference in New Issue
Block a user