Merge pull request #3877 from tsiegleauq/views

Create more list views
This commit is contained in:
Finn Stutzenstein 2018-09-18 11:52:10 +02:00 committed by GitHub
commit 849da27745
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 1223 additions and 341 deletions

203
client/package-lock.json generated
View File

@ -106,13 +106,36 @@
}
},
"@angular-devkit/schematics": {
"version": "0.6.8",
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-0.6.8.tgz",
"integrity": "sha512-R4YqAUdo62wtrhX/5HSRGSKXNTWqfQb66ZE6m8jj6GEJNFKdNXMdxOchxr07LCiKTxfh1w6G3nGzxIsu/+D4KA==",
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-0.8.1.tgz",
"integrity": "sha512-ab3xeyTpPA9JO7oXgdfbQ/+5djddvoKjFaxFFcLD5GxgDDnvJcmhgDkluJY9JZHH2oSFaW8u1G5zg8uo1HrriA==",
"dev": true,
"requires": {
"@angular-devkit/core": "0.6.8",
"rxjs": "^6.0.0"
"@angular-devkit/core": "0.8.1",
"rxjs": "~6.2.0"
},
"dependencies": {
"@angular-devkit/core": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.8.1.tgz",
"integrity": "sha512-0TKjF/nHb7+wwWIQ5iuQRzDIF2CphmX9ZojBIGH6PWFgQNKG0yUWqSa+PTpr5eOcGYBOa1rHLf10iyPHDmBdBw==",
"dev": true,
"requires": {
"ajv": "~6.4.0",
"chokidar": "^2.0.3",
"rxjs": "~6.2.0",
"source-map": "^0.5.6"
}
},
"rxjs": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.2.2.tgz",
"integrity": "sha512-0MI8+mkKAXZUF9vMrEoPnaoHkfzBPP4IGwUYRJhIRJF6/w3uByO1e91bEHn8zd43RdkTMKiooYKmwz7RH6zfOQ==",
"dev": true,
"requires": {
"tslib": "^1.9.0"
}
}
}
},
"@angular/animations": {
@ -132,35 +155,71 @@
}
},
"@angular/cli": {
"version": "6.0.8",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-6.0.8.tgz",
"integrity": "sha512-DhH1Zq5Yonthw6zh6W07fhf+9XrAZbD1fcQ0MrmbxlieCfLlTAdBqyK2LavFCKwSZkUMLF6UHM3+jiNRVZSSIg==",
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-6.2.1.tgz",
"integrity": "sha512-4AO014PohYc/vbNaO6nPi/a6JqxdOHN2m0WLutgRGoBQswqSGn7aLEG1erZKRzbfq39E1a/efnNmEI3Okl3h1Q==",
"dev": true,
"requires": {
"@angular-devkit/architect": "0.6.8",
"@angular-devkit/core": "0.6.8",
"@angular-devkit/schematics": "0.6.8",
"@schematics/angular": "0.6.8",
"@schematics/update": "0.6.8",
"opn": "~5.3.0",
"resolve": "^1.1.7",
"rxjs": "^6.0.0",
"@angular-devkit/architect": "0.8.1",
"@angular-devkit/core": "0.8.1",
"@angular-devkit/schematics": "0.8.1",
"@schematics/angular": "0.8.1",
"@schematics/update": "0.8.1",
"json-schema-traverse": "^0.4.1",
"opn": "^5.3.0",
"rxjs": "~6.2.0",
"semver": "^5.1.0",
"silent-error": "^1.0.0",
"symbol-observable": "^1.2.0",
"yargs-parser": "^10.0.0"
},
"dependencies": {
"@angular-devkit/architect": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.8.1.tgz",
"integrity": "sha512-bf/8tg8X2y9f6wE2r48KAW2AVexfGd/rfTHRvl9+kSsFFtXVA233GNAL6Qs+wJ/G2t1NFddnE3ME2eyhJYxBwA==",
"dev": true,
"requires": {
"@angular-devkit/core": "0.8.1",
"rxjs": "~6.2.0"
}
},
"@angular-devkit/core": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.8.1.tgz",
"integrity": "sha512-0TKjF/nHb7+wwWIQ5iuQRzDIF2CphmX9ZojBIGH6PWFgQNKG0yUWqSa+PTpr5eOcGYBOa1rHLf10iyPHDmBdBw==",
"dev": true,
"requires": {
"ajv": "~6.4.0",
"chokidar": "^2.0.3",
"rxjs": "~6.2.0",
"source-map": "^0.5.6"
}
},
"camelcase": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
"integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
"dev": true
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"rxjs": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.2.2.tgz",
"integrity": "sha512-0MI8+mkKAXZUF9vMrEoPnaoHkfzBPP4IGwUYRJhIRJF6/w3uByO1e91bEHn8zd43RdkTMKiooYKmwz7RH6zfOQ==",
"dev": true,
"requires": {
"tslib": "^1.9.0"
}
},
"yargs-parser": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.0.0.tgz",
"integrity": "sha512-+DHejWujTVYeMHLff8U96rLc4uE4Emncoftvn5AjhB1Jw1pWxLzgBUT/WYbPrHmy6YPEBTZQx5myHhVcuuu64g==",
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz",
"integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==",
"dev": true,
"requires": {
"camelcase": "^4.1.0"
@ -1065,28 +1124,74 @@
"dev": true
},
"@schematics/angular": {
"version": "0.6.8",
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-0.6.8.tgz",
"integrity": "sha512-9kRphqTYG5Df/I8fvnT1zMsw0YNDPO9tl18tQZXj4am4raT7l9UCr+WkwJdlBoA5pwG6baWE9sL0iGWV/bzF/g==",
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-0.8.1.tgz",
"integrity": "sha512-AW7063IaYFIcskt+eI5k4drb/hDuY2wK3zLsW01E49WaBcRhXVIOVox7cbfwHCA6zch117WZ5xVZlbGHJ4pkMw==",
"dev": true,
"requires": {
"@angular-devkit/core": "0.6.8",
"@angular-devkit/schematics": "0.6.8",
"typescript": ">=2.6.2 <2.8"
"@angular-devkit/core": "0.8.1",
"@angular-devkit/schematics": "0.8.1",
"typescript": ">=2.6.2 <2.10"
},
"dependencies": {
"@angular-devkit/core": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.8.1.tgz",
"integrity": "sha512-0TKjF/nHb7+wwWIQ5iuQRzDIF2CphmX9ZojBIGH6PWFgQNKG0yUWqSa+PTpr5eOcGYBOa1rHLf10iyPHDmBdBw==",
"dev": true,
"requires": {
"ajv": "~6.4.0",
"chokidar": "^2.0.3",
"rxjs": "~6.2.0",
"source-map": "^0.5.6"
}
},
"rxjs": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.2.2.tgz",
"integrity": "sha512-0MI8+mkKAXZUF9vMrEoPnaoHkfzBPP4IGwUYRJhIRJF6/w3uByO1e91bEHn8zd43RdkTMKiooYKmwz7RH6zfOQ==",
"dev": true,
"requires": {
"tslib": "^1.9.0"
}
}
}
},
"@schematics/update": {
"version": "0.6.8",
"resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.6.8.tgz",
"integrity": "sha512-1Uq7LYnwL2wBwGVCgNz76QAR13ghAk+2vDDHOi+VX5+usHManxydrpoMGeX66OBPd+y5D3D2MFb+8mYHE7mygg==",
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.8.1.tgz",
"integrity": "sha512-G5EU8nvqAC9fX09sV+UEM9EgR+PjGwguUatOk10uvwvZvCfO+W0FVeLmSap9A6mJ+e8YOH6XFpkwLG0AMuhYrw==",
"dev": true,
"requires": {
"@angular-devkit/core": "0.6.8",
"@angular-devkit/schematics": "0.6.8",
"@angular-devkit/core": "0.8.1",
"@angular-devkit/schematics": "0.8.1",
"npm-registry-client": "^8.5.1",
"rxjs": "^6.0.0",
"rxjs": "~6.2.0",
"semver": "^5.3.0",
"semver-intersect": "^1.1.2"
},
"dependencies": {
"@angular-devkit/core": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.8.1.tgz",
"integrity": "sha512-0TKjF/nHb7+wwWIQ5iuQRzDIF2CphmX9ZojBIGH6PWFgQNKG0yUWqSa+PTpr5eOcGYBOa1rHLf10iyPHDmBdBw==",
"dev": true,
"requires": {
"ajv": "~6.4.0",
"chokidar": "^2.0.3",
"rxjs": "~6.2.0",
"source-map": "^0.5.6"
}
},
"rxjs": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.2.2.tgz",
"integrity": "sha512-0MI8+mkKAXZUF9vMrEoPnaoHkfzBPP4IGwUYRJhIRJF6/w3uByO1e91bEHn8zd43RdkTMKiooYKmwz7RH6zfOQ==",
"dev": true,
"requires": {
"tslib": "^1.9.0"
}
}
}
},
"@types/jasmine": {
@ -2068,7 +2173,7 @@
"dependencies": {
"minimist": {
"version": "1.2.0",
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true
}
@ -6898,7 +7003,7 @@
},
"es6-promise": {
"version": "3.0.2",
"resolved": "http://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz",
"integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y=",
"dev": true
},
@ -6910,7 +7015,7 @@
},
"readable-stream": {
"version": "2.0.6",
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz",
"integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=",
"dev": true,
"requires": {
@ -8335,9 +8440,9 @@
}
},
"npm-registry-client": {
"version": "8.5.1",
"resolved": "https://registry.npmjs.org/npm-registry-client/-/npm-registry-client-8.5.1.tgz",
"integrity": "sha512-7rjGF2eA7hKDidGyEWmHTiKfXkbrcQAsGL/Rh4Rt3x3YNRNHhwaTzVJfW3aNvvlhg4G62VCluif0sLCb/i51Hg==",
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/npm-registry-client/-/npm-registry-client-8.6.0.tgz",
"integrity": "sha512-Qs6P6nnopig+Y8gbzpeN/dkt+n7IyVd8f45NTMotGk6Qo7GfBmzwYx6jRLoOOgKiMnaQfYxsuyQlD8Mc3guBhg==",
"dev": true,
"requires": {
"concat-stream": "^1.5.2",
@ -9259,7 +9364,7 @@
},
"chalk": {
"version": "1.1.3",
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
@ -9272,7 +9377,7 @@
},
"minimist": {
"version": "1.2.0",
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true
},
@ -9905,9 +10010,9 @@
}
},
"rxjs": {
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.2.tgz",
"integrity": "sha512-hV7criqbR0pe7EeL3O66UYVg92IR0XsA97+9y+BWTePK9SKmEI5Qd3Zj6uPnGkNzXsBywBQWTvujPl+1Kn9Zjw==",
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.2.2.tgz",
"integrity": "sha512-0MI8+mkKAXZUF9vMrEoPnaoHkfzBPP4IGwUYRJhIRJF6/w3uByO1e91bEHn8zd43RdkTMKiooYKmwz7RH6zfOQ==",
"requires": {
"tslib": "^1.9.0"
}
@ -10153,9 +10258,9 @@
}
},
"semver-intersect": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/semver-intersect/-/semver-intersect-1.3.1.tgz",
"integrity": "sha1-j6hKnhAovSOeRTDRo+GB5pjYhLo=",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/semver-intersect/-/semver-intersect-1.4.0.tgz",
"integrity": "sha512-d8fvGg5ycKAq0+I6nfWeCx6ffaWJCsBYU0H2Rq56+/zFePYfT8mXkB3tWBSjR5BerkHNZ5eTPIk1/LBYas35xQ==",
"dev": true,
"requires": {
"semver": "^5.0.0"
@ -11470,9 +11575,9 @@
"dev": true
},
"typescript": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.7.2.tgz",
"integrity": "sha512-p5TCYZDAO0m4G344hD+wx/LATebLWZNkkh2asWUFqSsD2OrDNhbAHuSjobrmsUmdzjJjEeZVU9g1h3O6vpstnw==",
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz",
"integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==",
"dev": true
},
"uglify-js": {

View File

@ -34,13 +34,13 @@
"@ngx-translate/http-loader": "^3.0.1",
"core-js": "^2.5.4",
"ngx-mat-select-search": "^1.3.1",
"rxjs": "^6.3.2",
"rxjs": "^6.2.2",
"uuid": "^3.3.2",
"zone.js": "^0.8.26"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.6.8",
"@angular/cli": "~6.0.8",
"@angular/cli": "^6.2.1",
"@angular/compiler-cli": "^6.1.7",
"@angular/language-service": "^6.1.7",
"@biesbjerg/ngx-translate-extract": "^2.3.4",
@ -63,6 +63,6 @@
"protractor": "^5.4.1",
"ts-node": "~5.0.1",
"tslint": "~5.9.1",
"typescript": "~2.7.2"
"typescript": "~2.9.2"
}
}

View File

@ -1,25 +1,17 @@
{
"/apps/core/version": {
"/apps/": {
"target": "http://localhost:8000",
"secure": false
},
"/apps/users/login": {
"/media/": {
"target": "http://localhost:8000",
"secure": false
},
"/apps/users/logout": {
"/rest/": {
"target": "http://localhost:8000",
"secure": false
},
"/apps/users/whoami": {
"target": "http://localhost:8000",
"secure": false
},
"/rest": {
"target": "http://localhost:8000",
"secure": false
},
"/ws/site": {
"/ws/site/": {
"target": "ws://localhost:8000",
"secure": false,
"ws": true

View File

@ -34,23 +34,6 @@ export class Item extends ProjectableBaseModel {
super('agenda/item', input);
}
// Note: This has to be used in the agenda repository
/*public get contentObject(): AgendaBaseModel {
const contentObject = this.DS.get<BaseModel>(this.content_object.collection, this.content_object.id);
if (!contentObject) {
return null;
}
if (contentObject instanceof AgendaBaseModel) {
return contentObject as AgendaBaseModel;
} else {
throw new Error(
`The content object (${this.content_object.collection}, ${this.content_object.id}) of item ${
this.id
} is not a BaseProjectableModel.`
);
}
}*/
public deserialize(input: any): void {
Object.assign(this, input);
@ -61,26 +44,11 @@ export class Item extends ProjectableBaseModel {
}
}
// The repository has to check for the content object and choose which title to use.
// The code below is belongs to the repository
public getTitle(): string {
/*const contentObject: AgendaBaseModel = this.contentObject;
if (contentObject) {
return contentObject.getAgendaTitle();
} else {
return this.title;
}*/
return this.title;
}
// Same here. See comment for getTitle()
public getListTitle(): string {
/*const contentObject: AgendaBaseModel = this.contentObject;
if (contentObject) {
return contentObject.getAgendaTitleWithType();
} else {
return this.title_with_type;
}*/
return this.title_with_type;
}

View File

@ -29,4 +29,4 @@ export class Mediafile extends ProjectableBaseModel {
}
}
ProjectableBaseModel.registerCollectionElement('amediafiles/mediafile', Mediafile);
ProjectableBaseModel.registerCollectionElement('mediafiles/mediafile', Mediafile);

View File

@ -85,7 +85,7 @@ export class Motion extends AgendaBaseModel {
}
public getDetailStateURL(): string {
return 'TODO';
return `/motions/${this.id}`;
}
public deserialize(input: any): void {

View File

@ -48,6 +48,10 @@ export class User extends ProjectableBaseModel {
return name.trim();
}
public containsGroupId(id: number): boolean {
return this.groups_id.some(groupId => groupId === id);
}
// TODO read config values for "users_sort_by"
public get short_name(): string {
const title = this.title.trim();

View File

@ -15,8 +15,7 @@ import {
MatSnackBarModule,
MatTableModule,
MatPaginatorModule,
MatSortModule,
MatTabsModule
MatSortModule
} from '@angular/material';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatChipsModule } from '@angular/material';
@ -79,10 +78,10 @@ library.add(fas);
MatMenuModule,
MatDialogModule,
MatSnackBarModule,
MatChipsModule,
FontAwesomeModule,
TranslateModule.forChild(),
RouterModule,
MatChipsModule,
NgxMatSelectSearchModule
],
exports: [
@ -106,7 +105,7 @@ library.add(fas);
MatMenuModule,
MatDialogModule,
MatSnackBarModule,
MatTabsModule,
MatChipsModule,
NgxMatSelectSearchModule,
FontAwesomeModule,
TranslateModule,

View File

@ -1,16 +1,18 @@
<os-head-bar appName="Agenda" plusButton=true (plusButtonClicked)=onPlusButton()>
</os-head-bar>
<os-head-bar appName="Agenda" plusButton=true (plusButtonClicked)=onPlusButton()></os-head-bar>
<mat-card class="os-card card-plus-distance">
<div class="app-content">
Agenda Works
<br/>
<div>
everyone should see this
</div>
<br/>
<div *osPerms="'agenda.can_see'">
Only permitted users should see this
</div>
</div>
</mat-card>
<mat-table class='os-listview-table on-transition-fade' [dataSource]="dataSource" matSort>
<!-- title column -->
<ng-container matColumnDef="title">
<mat-header-cell *matHeaderCellDef mat-sort-header> Topic </mat-header-cell>
<mat-cell *matCellDef="let item"> {{item.getListTitle()}} </mat-cell>
</ng-container>
<ng-container matColumnDef="duration">
<mat-header-cell *matHeaderCellDef mat-sort-header> Duration </mat-header-cell>
<mat-cell *matCellDef="let item"> {{item.duration}} </mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="['title', 'duration']"></mat-header-row>
<mat-row (click)='selectAgendaItem(row)' *matRowDef="let row; columns: ['title', 'duration']"></mat-row>
</mat-table>
<mat-paginator class="on-transition-fade" [pageSizeOptions]="[25, 50, 75, 100, 125]"></mat-paginator>

View File

@ -1,9 +1,10 @@
import { Component, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { BaseComponent } from 'app/base.component';
import { TranslateService } from '@ngx-translate/core';
import { Item } from '../../../shared/models/agenda/item';
import { Topic } from '../../../shared/models/topics/topic';
import { ViewItem } from '../models/view-item';
import { ListViewBaseComponent } from '../../base/list-view-base';
import { AgendaRepositoryService } from '../services/agenda-repository.service';
import { Router } from '@angular/router';
/**
* List view for the agenda.
@ -15,26 +16,43 @@ import { Topic } from '../../../shared/models/topics/topic';
templateUrl: './agenda-list.component.html',
styleUrls: ['./agenda-list.component.css']
})
export class AgendaListComponent extends BaseComponent implements OnInit {
export class AgendaListComponent extends ListViewBaseComponent<ViewItem> implements OnInit {
/**
* The usual constructor for components
* @param titleService
* @param translate
*/
public constructor(titleService: Title, protected translate: TranslateService) {
public constructor(
titleService: Title,
translate: TranslateService,
private router: Router,
private repo: AgendaRepositoryService
) {
super(titleService, translate);
}
/**
* Init function.
* Sets the title
* Sets the title, initializes the table and calls the repository.
*/
public ngOnInit(): void {
super.setTitle('Agenda');
// tslint:disable-next-line
const i: Item = new Item(); // Needed, that the Item.ts is loaded. Can be removed, if something else creates/uses items.
// tslint:disable-next-line
const t: Topic = new Topic(); // Needed, that the Topic.ts is loaded. Can be removed, if something else creates/uses topics.
this.initTable();
this.repo.getViewModelListObservable().subscribe(newAgendaItem => {
this.dataSource.data = newAgendaItem;
});
}
/**
* Handler for click events on agenda item rows
* Links to the content object if any
*/
public selectAgendaItem(item: ViewItem): void {
if (item.contentObject) {
this.router.navigate([item.contentObject.getDetailStateURL()]);
} else {
console.error(`The selected item ${item} has no content object`);
}
}
/**

View File

@ -0,0 +1,54 @@
import { BaseViewModel } from '../../base/base-view-model';
import { Item } from '../../../shared/models/agenda/item';
import { BaseModel } from '../../../shared/models/base/base-model';
import { AgendaBaseModel } from '../../../shared/models/base/agenda-base-model';
export class ViewItem extends BaseViewModel {
private _item: Item;
private _contentObject: AgendaBaseModel;
public get item(): Item {
return this._item;
}
public get contentObject(): AgendaBaseModel {
return this._contentObject;
}
public get id(): number {
return this.item ? this.item.id : null;
}
public get duration(): number {
return this.item ? this.item.duration : null;
}
public constructor(item: Item, contentObject: AgendaBaseModel) {
super();
this._item = item;
this._contentObject = contentObject;
}
public getTitle(): string {
if (this.contentObject) {
return this.contentObject.getAgendaTitle();
} else {
return this.item ? this.item.title : null;
}
}
public getListTitle(): string {
const contentObject: AgendaBaseModel = this.contentObject;
if (contentObject) {
return contentObject.getAgendaTitleWithType();
} else {
return this.item ? this.item.title_with_type : null;
}
}
public updateValues(update: BaseModel): void {
if (update instanceof Item && this.id === update.id) {
this._item = update;
}
}
}

View File

@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { AgendaRepositoryService } from './agenda-repository.service';
describe('AgendaRepositoryService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [AgendaRepositoryService]
});
});
it('should be created', inject([AgendaRepositoryService], (service: AgendaRepositoryService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,79 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { BaseRepository } from '../../base/base-repository';
import { DataStoreService } from '../../../core/services/data-store.service';
import { Item } from '../../../shared/models/agenda/item';
import { ViewItem } from '../models/view-item';
import { AgendaBaseModel } from '../../../shared/models/base/agenda-base-model';
import { BaseModel } from '../../../shared/models/base/base-model';
/**
* Repository service for users
*
* Documentation partially provided in {@link BaseRepository}
*/
@Injectable({
providedIn: 'root'
})
export class AgendaRepositoryService extends BaseRepository<ViewItem, Item> {
public constructor(DS: DataStoreService) {
super(DS, Item);
}
/**
* Returns the corresponding content object to a given {@link Item} as an {@link AgendaBaseModel}
* @param agendaItem
*/
private getContentObject(agendaItem: Item): AgendaBaseModel {
const contentObject = this.DS.get<BaseModel>(
agendaItem.content_object.collection,
agendaItem.content_object.id
);
if (!contentObject) {
return null;
}
if (contentObject instanceof AgendaBaseModel) {
return contentObject as AgendaBaseModel;
} else {
throw new Error(
`The content object (${agendaItem.content_object.collection}, ${
agendaItem.content_object.id
}) of item ${agendaItem.id} is not a BaseProjectableModel.`
);
}
}
/**
* @ignore
*
* TODO: used over not-yet-existing detail view
*/
public save(item: Item, viewUser: ViewItem): Observable<Item> {
return null;
}
/**
* @ignore
*
* TODO: used over not-yet-existing detail view
*/
public delete(item: ViewItem): Observable<Item> {
return null;
}
/**
* @ignore
*
* TODO: used over not-yet-existing detail view
*/
public create(item: Item, viewItem: ViewItem): Observable<Item> {
return null;
}
public createViewModel(item: Item): ViewItem {
const contentObject = this.getContentObject(item);
return new ViewItem(item, contentObject);
}
}

View File

@ -1,21 +1,32 @@
<!-- <mat-toolbar color='primary'>
<button class='generic-plus-button on-transition-fade' mat-fab>
<fa-icon icon='plus'></fa-icon>
</button>
<span class='app-name on-transition-fade' translate>Assignments</span>
<span class='spacer'></span>
<button class='on-transition-fade' mat-icon-button (click)='downloadAssignmentButton()'>
<fa-icon icon='download'></fa-icon>
</button>
</mat-toolbar> -->
<os-head-bar appName="Assignments" plusButton=true [menuList]=assignmentMenu (plusButtonClicked)=onPlusButton() (ellipsisMenuItem)=onEllipsisItem($event)>
<os-head-bar appName="Assignments" plusButton=true [menuList]=assignmentMenu (plusButtonClicked)=onPlusButton()
(ellipsisMenuItem)=onEllipsisItem($event)>
</os-head-bar>
<mat-card class="os-card card-plus-distance">
assignment-list works!
</mat-card>
<mat-table class='os-listview-table on-transition-fade' [dataSource]="dataSource" matSort>
<!-- name column -->
<ng-container matColumnDef="title">
<mat-header-cell *matHeaderCellDef mat-sort-header> Title </mat-header-cell>
<mat-cell *matCellDef="let assignment"> {{assignment.getTitle()}} </mat-cell>
</ng-container>
<ng-container matColumnDef="phase">
<mat-header-cell *matHeaderCellDef mat-sort-header> Phase </mat-header-cell>
<mat-cell *matCellDef="let assignment">
<mat-chip-list>
<mat-chip color="primary" selected>{{assignment.phase}} </mat-chip>
</mat-chip-list>
</mat-cell>
</ng-container>
<ng-container matColumnDef="candidates">
<mat-header-cell *matHeaderCellDef mat-sort-header> Candidates </mat-header-cell>
<mat-cell *matCellDef="let assignment">
<mat-chip-list>
<mat-chip color="accent" selected>{{assignment.candidateAmount}}</mat-chip>
</mat-chip-list>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="['title', 'phase', 'candidates']"></mat-header-row>
<mat-row (click)='selectAssignment(row)' *matRowDef="let row; columns: ['title', 'phase', 'candidates']"></mat-row>
</mat-table>
<mat-paginator class="on-transition-fade" [pageSizeOptions]="[25, 50, 75, 100, 125]"></mat-paginator>

View File

@ -1,20 +1,20 @@
import { Component, OnInit } from '@angular/core';
import { BaseComponent } from '../../../base.component';
import { TranslateService } from '@ngx-translate/core';
import { Title } from '@angular/platform-browser';
import { Assignment } from '../../../shared/models/assignments/assignment';
import { ViewAssignment } from '../models/view-assignment';
import { ListViewBaseComponent } from '../../base/list-view-base';
import { AssignmentRepositoryService } from '../services/assignment-repository.service';
/**
* Listview for the assignments
*
* TODO: not yet implemented
*/
@Component({
selector: 'os-assignment-list',
templateUrl: './assignment-list.component.html',
styleUrls: ['./assignment-list.component.css']
})
export class AssignmentListComponent extends BaseComponent implements OnInit {
export class AssignmentListComponent extends ListViewBaseComponent<ViewAssignment> implements OnInit {
/**
* Define the content of the ellipsis menu.
* Give it to the HeadBar to display them.
@ -29,13 +29,27 @@ export class AssignmentListComponent extends BaseComponent implements OnInit {
/**
* Constructor.
*
* @param repo the repository
* @param titleService
* @param translate
*/
public constructor(titleService: Title, protected translate: TranslateService) {
public constructor(private repo: AssignmentRepositoryService, titleService: Title, translate: TranslateService) {
super(titleService, translate);
}
/**
* Init function.
* Sets the title, inits the table and calls the repo.
*/
public ngOnInit(): void {
super.setTitle('Assignments');
this.initTable();
this.repo.getViewModelListObservable().subscribe(newAssignments => {
this.dataSource.data = newAssignments;
});
}
/**
* Click on the plus button delegated from head-bar
*/
@ -44,13 +58,11 @@ export class AssignmentListComponent extends BaseComponent implements OnInit {
}
/**
* Init function. Sets the title.
* Select an row in the table
* @param assignment
*/
public ngOnInit(): void {
super.setTitle('Assignments');
// tslint:disable-next-line
const a: Assignment = new Assignment(); // Needed, that the Assignment.ts is loaded. Can be removed, if something else creates/uses assignments.
public selectAssignment(assignment: ViewAssignment): void {
console.log('select assignment list: ', assignment);
}
/**
@ -60,15 +72,4 @@ export class AssignmentListComponent extends BaseComponent implements OnInit {
public downloadAssignmentButton(): void {
console.log('Hello World');
}
/**
* handler function for clicking on items in the ellipsis menu.
*
* @param event clicked entry from ellipsis menu
*/
public onEllipsisItem(event: any): void {
if (event.action) {
this[event.action]();
}
}
}

View File

@ -0,0 +1,53 @@
import { BaseViewModel } from '../../base/base-view-model';
import { Assignment } from '../../../shared/models/assignments/assignment';
import { Tag } from '../../../shared/models/core/tag';
import { User } from '../../../shared/models/users/user';
import { Item } from '../../../shared/models/agenda/item';
export class ViewAssignment extends BaseViewModel {
private _assignment: Assignment;
private _relatedUser: User[];
private _agendaItem: Item;
private _tags: Tag[];
public get assignment(): Assignment {
return this._assignment;
}
public get candidates(): User[] {
return this._relatedUser;
}
public get agendaItem(): Item {
return this._agendaItem;
}
public get tags(): Tag[] {
return this._tags;
}
/**
* unknown where the identifier to the phase is get
*/
public get phase(): number {
return this.assignment ? this.assignment.phase : null;
}
public get candidateAmount(): number {
return this.candidates ? this.candidates.length : 0;
}
public constructor(assignment: Assignment, relatedUser: User[], agendaItem?: Item, tags?: Tag[]) {
super();
this._assignment = assignment;
this._relatedUser = relatedUser;
this._agendaItem = agendaItem;
this._tags = tags;
}
public updateValues(): void {}
public getTitle(): string {
return this.assignment ? this.assignment.title : null;
}
}

View File

@ -0,0 +1,12 @@
import { TestBed } from '@angular/core/testing';
import { AssignmentRepositoryService } from './assignment-repository.service';
describe('AssignmentRepositoryService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: AssignmentRepositoryService = TestBed.get(AssignmentRepositoryService);
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,47 @@
import { Injectable } from '@angular/core';
import { ViewAssignment } from '../models/view-assignment';
import { Assignment } from '../../../shared/models/assignments/assignment';
import { User } from '../../../shared/models/users/user';
import { Tag } from '../../../shared/models/core/tag';
import { Item } from '../../../shared/models/agenda/item';
import { Observable } from 'rxjs';
import { BaseRepository } from '../../base/base-repository';
import { DataStoreService } from '../../../core/services/data-store.service';
/**
* Repository Service for Assignments.
*
* Documentation partially provided in {@link BaseRepository}
*/
@Injectable({
providedIn: 'root'
})
export class AssignmentRepositoryService extends BaseRepository<ViewAssignment, Assignment> {
/**
* Constructor for the Assignment Repository.
*
*/
public constructor(DS: DataStoreService) {
super(DS, Assignment, [User, Item, Tag]);
}
public save(assignment: Assignment, viewAssignment: ViewAssignment): Observable<Assignment> {
return null;
}
public delete(viewAssignment: ViewAssignment): Observable<Assignment> {
return null;
}
public create(assignment: Assignment, viewAssignment: ViewAssignment): Observable<Assignment> {
return null;
}
public createViewModel(assignment: Assignment): ViewAssignment {
const relatedUser = this.DS.getMany(User, assignment.candidateIds);
const agendaItem = this.DS.get(Item, assignment.agenda_item_id);
const tags = this.DS.getMany(Tag, assignment.tags_id);
return new ViewAssignment(assignment, relatedUser, agendaItem, tags);
}
}

View File

@ -1,9 +1,9 @@
import { OpenSlidesComponent } from '../openslides.component';
import { OpenSlidesComponent } from '../../openslides.component';
import { BehaviorSubject, Observable } from 'rxjs';
import { BaseViewModel } from './base-view-model';
import { BaseModel, ModelConstructor } from '../shared/models/base/base-model';
import { CollectionStringModelMapperService } from '../core/services/collectionStringModelMapper.service';
import { DataStoreService } from '../core/services/data-store.service';
import { BaseModel, ModelConstructor } from '../../shared/models/base/base-model';
import { CollectionStringModelMapperService } from '../../core/services/collectionStringModelMapper.service';
import { DataStoreService } from '../../core/services/data-store.service';
export abstract class BaseRepository<V extends BaseViewModel, M extends BaseModel> extends OpenSlidesComponent {
/**
@ -30,7 +30,7 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
public constructor(
protected DS: DataStoreService,
protected baseModelCtor: ModelConstructor<M>,
protected depsModelCtors: ModelConstructor<BaseModel>[]
protected depsModelCtors?: ModelConstructor<BaseModel>[]
) {
super();
@ -47,7 +47,7 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
// Add new and updated motions to the viewModelStore
this.viewModelStore[model.id] = this.createViewModel(model as M);
this.updateAllObservables(model.id);
} else {
} else if (this.depsModelCtors) {
const dependencyChanged: boolean = this.depsModelCtors.some(ctor => {
return model instanceof ctor;
});
@ -92,6 +92,13 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
*/
public abstract create(update: M, viewModel: V): Observable<M>;
/**
* Creates a view model out of a base model.
*
* Should read all necessary objects from the datastore
* that the viewmodel needs
* @param model
*/
protected abstract createViewModel(model: M): V;
/**

View File

@ -1,5 +1,5 @@
import { BaseModel } from '../shared/models/base/base-model';
import { Displayable } from '../shared/models/base/displayable';
import { BaseModel } from '../../shared/models/base/base-model';
import { Displayable } from '../../shared/models/base/displayable';
/**
* Base class for view models. alls view models should have titles.

View File

@ -0,0 +1,63 @@
import { ViewChild } from '@angular/core';
import { BaseComponent } from '../../base.component';
import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { MatTableDataSource, MatTable, MatSort, MatPaginator } from '@angular/material';
import { BaseViewModel } from './base-view-model';
export abstract class ListViewBaseComponent<V extends BaseViewModel> extends BaseComponent {
/**
* The data source for a table. Requires to be initialised with a BaseViewModel
*/
public dataSource: MatTableDataSource<V>;
/**
* The table itself
*/
@ViewChild(MatTable)
protected table: MatTable<V>;
/**
* Table paginator
*/
@ViewChild(MatPaginator)
protected paginator: MatPaginator;
/**
* Sorter for a table
*/
@ViewChild(MatSort)
protected sort: MatSort;
/**
* Constructor for list view bases
* @param titleService the title serivce
* @param translate the translate service
*/
public constructor(titleService: Title, translate: TranslateService) {
super(titleService, translate);
}
/**
* Children need to call this in their init-function.
* Calling these three functions in the constructor of this class
* would be too early, resulting in non-paginated tables
*/
public initTable(): void {
this.dataSource = new MatTableDataSource();
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
/**
* handler function for clicking on items in the ellipsis menu.
* Ellipsis menu comes from the HeadBarComponent is is implemented by most ListViews
*
* @param event clicked entry from ellipsis menu
*/
public onEllipsisItem(event: any): void {
if (event.action) {
this[event.action]();
}
}
}

View File

@ -2,6 +2,32 @@
</os-head-bar>
<mat-card class="os-card card-plus-distance">
mediafile-list works!
</mat-card>
<mat-table class='os-listview-table on-transition-fade' [dataSource]="dataSource" matSort>
<!-- name column -->
<ng-container matColumnDef="title">
<mat-header-cell *matHeaderCellDef mat-sort-header> Name </mat-header-cell>
<mat-cell (click)='selectFile(file)' *matCellDef="let file"> {{file.title}} </mat-cell>
</ng-container>
<!-- prefix column -->
<ng-container matColumnDef="info">
<mat-header-cell *matHeaderCellDef mat-sort-header> Group </mat-header-cell>
<mat-cell (click)='selectFile(file)' *matCellDef="let file">
{{file.type}}
<br>
{{file.size}}
</mat-cell>
</ng-container>
<!-- prefix column -->
<ng-container matColumnDef="download">
<mat-header-cell *matHeaderCellDef mat-sort-header> Download </mat-header-cell>
<mat-cell (click)="download(file)" *matCellDef="let file">
<fa-icon icon='download'></fa-icon>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="['title', 'info', 'download']"></mat-header-row>
<mat-row *matRowDef="let row; columns: ['title', 'info', 'download']"></mat-row>
</mat-table>
<mat-paginator class="on-transition-fade" [pageSizeOptions]="[25, 50, 75, 100, 125]"></mat-paginator>

View File

@ -3,19 +3,20 @@ import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { BaseComponent } from '../../../base.component';
import { ViewMediafile } from '../models/view-mediafile';
import { MediafileRepositoryService } from '../services/mediafile-repository.service';
import { ListViewBaseComponent } from '../../base/list-view-base';
/**
* Lists all the uploaded mediafiles.
* Lists all the uploaded files.
*
* Not yet implemented
*/
@Component({
selector: 'os-mediafile-list',
templateUrl: './mediafile-list.component.html',
styleUrls: ['./mediafile-list.component.css']
})
export class MediafileListComponent extends BaseComponent implements OnInit {
export class MediafileListComponent extends ListViewBaseComponent<ViewMediafile> implements OnInit {
/**
* Define the content of the ellipsis menu.
* Give it to the HeadBar to display them.
@ -31,10 +32,15 @@ export class MediafileListComponent extends BaseComponent implements OnInit {
/**
* Constructor
*
* @param repo the repository for files
* @param titleService
* @param translate
*/
public constructor(titleService: Title, protected translate: TranslateService) {
public constructor(
private repo: MediafileRepositoryService,
protected titleService: Title,
protected translate: TranslateService
) {
super(titleService, translate);
}
@ -44,6 +50,10 @@ export class MediafileListComponent extends BaseComponent implements OnInit {
*/
public ngOnInit(): void {
super.setTitle('Files');
this.initTable();
this.repo.getViewModelListObservable().subscribe(newUsers => {
this.dataSource.data = newUsers;
});
}
/**
@ -64,13 +74,18 @@ export class MediafileListComponent extends BaseComponent implements OnInit {
}
/**
* handler function for clicking on items in the ellipsis menu.
*
* @param event clicked entry from ellipsis menu
* Clicking on a list row
* @param file the selected file
*/
public onEllipsisItem(event: any): void {
if (event.action) {
this[event.action]();
}
public selectFile(file: ViewMediafile): void {
console.log('The file: ', file);
}
/**
* Directly download a mediafile using the download button on the table
* @param file
*/
public download(file: ViewMediafile): void {
window.open(file.downloadUrl);
}
}

View File

@ -0,0 +1,59 @@
import { BaseViewModel } from '../../base/base-view-model';
import { Mediafile } from '../../../shared/models/mediafiles/mediafile';
import { User } from '../../../shared/models/users/user';
import { BaseModel } from '../../../shared/models/base/base-model';
export class ViewMediafile extends BaseViewModel {
private _mediafile: Mediafile;
private _uploader: User;
public get mediafile(): Mediafile {
return this._mediafile;
}
public get uploader(): User {
return this._uploader;
}
public get title(): string {
return this.mediafile ? this.mediafile.title : null;
}
public get size(): string {
return this.mediafile ? this.mediafile.filesize : null;
}
public get type(): string {
return this.mediafile && this.mediafile.mediafile ? this.mediafile.mediafile.type : null;
}
public get prefix(): string {
return this.mediafile ? this.mediafile.media_url_prefix : null;
}
public get fileName(): string {
return this.mediafile && this.mediafile.mediafile ? this.mediafile.mediafile.name : null;
}
public get downloadUrl(): string {
return this.mediafile && this.mediafile.mediafile ? `${this.prefix}${this.fileName}` : null;
}
public constructor(mediafile?: Mediafile, uploader?: User) {
super();
this._mediafile = mediafile;
this._uploader = uploader;
}
public getTitle(): string {
return this.title;
}
public updateValues(update: BaseModel): void {
if (update instanceof Mediafile) {
if (this.mediafile.id === update.id) {
this._mediafile = update;
}
}
}
}

View File

@ -0,0 +1,12 @@
import { TestBed } from '@angular/core/testing';
import { MediafileRepositoryService } from './mediafile-repository.service';
describe('FileRepositoryService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: MediafileRepositoryService = TestBed.get(MediafileRepositoryService);
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,56 @@
import { Injectable } from '@angular/core';
import { BaseRepository } from '../../base/base-repository';
import { ViewMediafile } from '../models/view-mediafile';
import { Mediafile } from '../../../shared/models/mediafiles/mediafile';
import { User } from '../../../shared/models/users/user';
import { Observable } from 'rxjs';
import { DataStoreService } from '../../../core/services/data-store.service';
/**
* Repository for files
*/
@Injectable({
providedIn: 'root'
})
export class MediafileRepositoryService extends BaseRepository<ViewMediafile, Mediafile> {
/**
* Consturctor for the file repo
* @param DS the DataStore
*/
public constructor(DS: DataStoreService) {
super(DS, Mediafile, [User]);
}
/**
* Saves a config value.
*
* TODO: used over not-yet-existing detail view
*/
public save(file: Mediafile, viewFile: ViewMediafile): Observable<Mediafile> {
return null;
}
/**
* Saves a config value.
*
* TODO: used over not-yet-existing detail view
*/
public delete(file: ViewMediafile): Observable<Mediafile> {
return null;
}
/**
* Saves a config value.
*
* TODO: used over not-yet-existing detail view
*/
public create(file: Mediafile, viewFile: ViewMediafile): Observable<Mediafile> {
return null;
}
public createViewModel(file: Mediafile): ViewMediafile {
const uploader = this.DS.get(User, file.uploader_id);
return new ViewMediafile(file, uploader);
}
}

View File

@ -1,4 +1,5 @@
<os-head-bar appName="Motions" plusButton=true (plusButtonClicked)=onPlusButton() [menuList]=motionMenuList (ellipsisMenuItem)=onEllipsisItem($event)>
<os-head-bar appName="Motions" plusButton=true (plusButtonClicked)=onPlusButton() [menuList]=motionMenuList
(ellipsisMenuItem)=onEllipsisItem($event)>
</os-head-bar>
<div class='custom-table-header on-transition-fade'>
@ -10,7 +11,7 @@
</button>
</div>
<mat-table class='on-transition-fade' [dataSource]="dataSource" matSort>
<mat-table class='os-listview-table on-transition-fade' [dataSource]="dataSource" matSort>
<!-- identifier column -->
<ng-container matColumnDef="identifier">
<mat-header-cell *matHeaderCellDef mat-sort-header> Identifier </mat-header-cell>

View File

@ -15,24 +15,7 @@
line-height: normal;
}
mat-table {
width: 100%;
/** hide mat header row */
.mat-header-row {
display: none;
}
/** size of the mat row */
mat-row {
height: 60px;
}
mat-row:hover {
cursor: pointer;
background-color: rgba(0, 0, 0, 0.025);
}
.os-listview-table {
/** identifier */
.mat-column-identifier {
padding-left: 10px;

View File

@ -1,14 +1,13 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { Title } from '@angular/platform-browser';
import { MatTable, MatPaginator, MatSort, MatTableDataSource } from '@angular/material';
import { TranslateService } from '@ngx-translate/core';
import { BaseComponent } from '../../../../base.component';
import { MotionRepositoryService } from '../../services/motion-repository.service';
import { ViewMotion } from '../../models/view-motion';
import { WorkflowState } from '../../../../shared/models/motions/workflow-state';
import { ListViewBaseComponent } from '../../../base/list-view-base';
/**
* Component that displays all the motions in a Table using DataSource.
@ -18,29 +17,7 @@ import { WorkflowState } from '../../../../shared/models/motions/workflow-state'
templateUrl: './motion-list.component.html',
styleUrls: ['./motion-list.component.scss']
})
export class MotionListComponent extends BaseComponent implements OnInit {
/**
* Will be processed by the mat-table
*
* Will represent the object that comes from the repository
*/
public dataSource: MatTableDataSource<ViewMotion>;
/**
* The table itself.
*/
@ViewChild(MatTable) public table: MatTable<ViewMotion>;
/**
* Pagination. Might be turned off to all motions at once.
*/
@ViewChild(MatPaginator) public paginator: MatPaginator;
/**
* Sort the Table
*/
@ViewChild(MatSort) public sort: MatSort;
export class MotionListComponent extends ListViewBaseComponent<ViewMotion> implements OnInit {
/**
* Use for minimal width
*/
@ -78,8 +55,8 @@ export class MotionListComponent extends BaseComponent implements OnInit {
* @param repo Motion Repository
*/
public constructor(
protected titleService: Title,
protected translate: TranslateService,
titleService: Title,
translate: TranslateService,
private router: Router,
private route: ActivatedRoute,
private repo: MotionRepositoryService
@ -88,15 +65,13 @@ export class MotionListComponent extends BaseComponent implements OnInit {
}
/**
* Init function
* Init function.
*
* Sets the title, inits the table and calls the repository
*/
public ngOnInit(): void {
super.setTitle('Motions');
this.dataSource = new MatTableDataSource();
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
this.initTable();
this.repo.getViewModelListObservable().subscribe(newMotions => {
this.dataSource.data = newMotions;
});
@ -163,15 +138,4 @@ export class MotionListComponent extends BaseComponent implements OnInit {
public downloadMotions(): void {
console.log('Download Motions Button');
}
/**
* handler function for clicking on items in the ellipsis menu.
*
* @param event clicked entry from ellipsis menu
*/
public onEllipsisItem(event: any): void {
if (event.action) {
this[event.action]();
}
}
}

View File

@ -4,7 +4,7 @@ import { User } from '../../../shared/models/users/user';
import { Workflow } from '../../../shared/models/motions/workflow';
import { WorkflowState } from '../../../shared/models/motions/workflow-state';
import { BaseModel } from '../../../shared/models/base/base-model';
import { BaseViewModel } from '../../base-view-model';
import { BaseViewModel } from '../../base/base-view-model';
import { TranslateService } from '@ngx-translate/core';
/**
@ -27,43 +27,23 @@ export class ViewMotion extends BaseViewModel {
}
public get id(): number {
if (this.motion) {
return this.motion.id;
} else {
return null;
}
return this.motion ? this.motion.id : null;
}
public get identifier(): string {
if (this.motion) {
return this.motion.identifier;
} else {
return null;
}
return this.motion ? this.motion.identifier : null;
}
public get title(): string {
if (this.motion) {
return this.motion.title;
} else {
return null;
}
return this.motion ? this.motion.title : null;
}
public get text(): string {
if (this.motion) {
return this.motion.text;
} else {
return null;
}
return this.motion ? this.motion.text : null;
}
public get reason(): string {
if (this.motion) {
return this.motion.reason;
} else {
return null;
}
return this.motion ? this.motion.reason : null;
}
public get category(): Category {
@ -71,11 +51,7 @@ export class ViewMotion extends BaseViewModel {
}
public get categoryId(): number {
if (this._motion && this._motion.category_id) {
return this._motion.category_id;
} else {
return null;
}
return this.motion && this.category ? this.motion.category_id : null;
}
public get submitters(): User[] {
@ -95,15 +71,11 @@ export class ViewMotion extends BaseViewModel {
}
public get stateId(): number {
if (this._motion && this._motion.state_id) {
return this._motion.state_id;
} else {
return null;
}
return this.motion && this.motion.state_id ? this.motion.state_id : null;
}
public get recommendationId(): number {
return this._motion.recommendation_id;
return this.motion && this.motion.recommendation_id ? this.motion.recommendation_id : null;
}
/**
@ -118,27 +90,15 @@ export class ViewMotion extends BaseViewModel {
}
public get recommendation(): WorkflowState {
if (this.recommendationId && this.workflow) {
return this.workflow.getStateById(this.recommendationId);
} else {
return null;
}
return this.recommendationId && this.workflow ? this.workflow.getStateById(this.recommendationId) : null;
}
public get origin(): string {
if (this.motion) {
return this.motion.origin;
} else {
return null;
}
return this.motion ? this.motion.origin : null;
}
public get nextStates(): WorkflowState[] {
if (this.state && this.workflow) {
return this.state.getNextStates(this.workflow);
} else {
return null;
}
return this.state && this.workflow ? this.state.getNextStates(this.workflow) : null;
}
public constructor(

View File

@ -8,7 +8,7 @@ import { Workflow } from '../../../shared/models/motions/workflow';
import { WorkflowState } from '../../../shared/models/motions/workflow-state';
import { ViewMotion } from '../models/view-motion';
import { Observable } from 'rxjs';
import { BaseRepository } from '../../base-repository';
import { BaseRepository } from '../../base/base-repository';
import { DataStoreService } from '../../../core/services/data-store.service';
/**

View File

@ -0,0 +1,38 @@
import { BaseViewModel } from '../../base/base-view-model';
import { BaseModel } from '../../../shared/models/base/base-model';
import { Config } from '../../../shared/models/core/config';
export class ViewConfig extends BaseViewModel {
private _config: Config;
public get config(): Config {
return this._config;
}
public get id(): number {
return this.config ? this.config.id : null;
}
public get key(): string {
return this.config ? this.config.key : null;
}
public get value(): Object {
return this.config ? this.config.value : null;
}
public constructor(config: Config) {
super();
this._config = config;
}
public getTitle(): string {
return this.key;
}
public updateValues(update: BaseModel): void {
if (update instanceof Config && this.id === update.id) {
this._config = update;
}
}
}

View File

@ -0,0 +1,12 @@
import { TestBed } from '@angular/core/testing';
import { ConfigRepositoryService } from './config-repository.service';
describe('SettingsRepositoryService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: ConfigRepositoryService = TestBed.get(ConfigRepositoryService);
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,61 @@
import { Injectable } from '@angular/core';
import { BaseRepository } from '../../base/base-repository';
import { ViewConfig } from '../models/view-config';
import { Config } from '../../../shared/models/core/config';
import { Observable } from 'rxjs';
import { DataStoreService } from '../../../core/services/data-store.service';
/**
* Repository for Configs.
*
* Documentation provided over {@link BaseRepository}
*/
@Injectable({
providedIn: 'root'
})
export class ConfigRepositoryService extends BaseRepository<ViewConfig, Config> {
/**
* Constructor for ConfigRepositoryService
*/
public constructor(DS: DataStoreService) {
super(DS, Config);
}
/**
* Saves a config value.
*
* TODO: used over not-yet-existing detail view
*/
public save(config: Config, viewConfig: ViewConfig): Observable<Config> {
return null;
}
/**
* This particular function should never be necessary since the creation of config
* values is not planed.
*
* Function exists solely to correctly implement {@link BaseRepository}
*/
public delete(config: ViewConfig): Observable<Config> {
return null;
}
/**
* This particular function should never be necessary since the creation of config
* values is not planed.
*
* Function exists solely to correctly implement {@link BaseRepository}
*/
public create(config: Config, viewConfig: ViewConfig): Observable<Config> {
return null;
}
/**
* Creates a new ViewConfig of a given Config object
* @param config
*/
public createViewModel(config: Config): ViewConfig {
return new ViewConfig(config);
}
}

View File

@ -1,20 +1,19 @@
<os-head-bar appName="Settings">
</os-head-bar>
<os-head-bar appName="Settings"></os-head-bar>
<mat-card class="os-card">
<div *osPerms="'core.can_manage_config'" class="app-content">
<mat-accordion>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
Title
</mat-panel-title>
</mat-expansion-panel-header>
<mat-table class='os-listview-table on-transition-fade' [dataSource]="dataSource" matSort>
<!-- name column -->
<ng-container matColumnDef="key">
<mat-header-cell *matHeaderCellDef mat-sort-header> Key </mat-header-cell>
<mat-cell *matCellDef="let config"> {{config.key}} </mat-cell>
</ng-container>
<p> CONTENT </p>
</mat-expansion-panel>
</mat-accordion>
<!-- prefix column -->
<ng-container matColumnDef="value">
<mat-header-cell *matHeaderCellDef mat-sort-header> Value </mat-header-cell>
<mat-cell *matCellDef="let config"> {{config.value}}</mat-cell>
</ng-container>
</div>
</mat-card>
<mat-header-row *matHeaderRowDef="['key', 'value']"></mat-header-row>
<mat-row (click)='selectConfig(row)' *matRowDef="let row; columns: ['key', 'value']"></mat-row>
</mat-table>

View File

@ -1,41 +1,61 @@
import { Component, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { BaseComponent } from '../../../base.component';
import { ConstantsService } from '../../../core/services/constants.service';
import { ListViewBaseComponent } from '../../base/list-view-base';
import { ConfigRepositoryService } from '../services/config-repository.service';
import { ViewConfig } from '../models/view-config';
/**
* List view for the global settings
*
* TODO: Not yet implemented
*/
@Component({
selector: 'os-settings-list',
templateUrl: './settings-list.component.html',
styleUrls: ['./settings-list.component.css']
})
export class SettingsListComponent extends BaseComponent implements OnInit {
export class SettingsListComponent extends ListViewBaseComponent<ViewConfig> implements OnInit {
/**
* The usual component constructor
* @param titleService
* @param translate
*/
public constructor(
titleService: Title,
protected titleService: Title,
protected translate: TranslateService,
private constantsService: ConstantsService
private repo: ConfigRepositoryService,
private constantsService: ConstantsService,
) {
super(titleService, translate);
}
/**
* Init function. Sets the title
* Init function.
*
* Sets the title, inits the table and calls the repo
*
* TODO: Needs the constants to be working
*/
public ngOnInit(): void {
super.setTitle('Settings');
this.initTable();
this.constantsService.get('OpenSlidesConfigVariables').subscribe(data => {
console.log(data);
});
this.repo.getViewModelListObservable().subscribe(newConfig => {
this.dataSource.data = newConfig;
});
}
/**
* Triggers when user selects a row
* @param row
*
* TODO: This prints the clicked item in the log.
* Needs the constants to be working
*/
public selectConfig(row: ViewConfig): void {
console.log('change a config: ', row.value);
}
}

View File

@ -0,0 +1,68 @@
import { BaseViewModel } from '../../base/base-view-model';
import { User } from '../../../shared/models/users/user';
import { Group } from '../../../shared/models/users/group';
import { BaseModel } from '../../../shared/models/base/base-model';
export class ViewUser extends BaseViewModel {
private _user: User;
private _groups: Group[];
public get user(): User {
return this._user;
}
public get groups(): Group[] {
return this._groups;
}
public get fullName(): string {
return this.user ? this.user.full_name : null;
}
/**
* TODO: Make boolean, use function over view component.
*/
public get isActive(): string {
return this.user && this.user.is_active ? 'active' : 'inactive';
}
public get structureLevel(): string {
return this.user ? this.user.structure_level : null;
}
public constructor(user?: User, groups?: Group[]) {
super();
this._user = user;
this._groups = groups;
}
public getTitle(): string {
return this.user ? this.user.toString() : null;
}
/**
* TODO: Implement
*/
public replaceGroup(newGroup: Group): void {
console.log('replace group - not yet implemented, ', newGroup);
}
/**
* Updates values. Triggered through observables.
*
* @param update a new User or Group
*/
public updateValues(update: BaseModel): void {
if (update instanceof User) {
if (this.user.id === update.id) {
this._user = update;
}
} else if (update instanceof Group) {
if (this.user && this.user.groups_id) {
if (this.user.containsGroupId(update.id)) {
this.replaceGroup(update);
}
}
}
}
}

View File

@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { UserRepositoryService } from './user-repository.service';
describe('UserRepositoryService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [UserRepositoryService]
});
});
it('should be created', inject([UserRepositoryService], (service: UserRepositoryService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,57 @@
import { Injectable } from '@angular/core';
import { BaseRepository } from '../../base/base-repository';
import { ViewUser } from '../models/view-user';
import { User } from '../../../shared/models/users/user';
import { Group } from '../../../shared/models/users/group';
import { Observable } from 'rxjs';
import { DataStoreService } from '../../../core/services/data-store.service';
/**
* Repository service for users
*
* Documentation partially provided in {@link BaseRepository}
*/
@Injectable({
providedIn: 'root'
})
export class UserRepositoryService extends BaseRepository<ViewUser, User> {
/**
* Constructor calls the parent constructor
*/
public constructor(DS: DataStoreService) {
super(DS, User, [Group]);
}
/**
* @ignore
*
* TODO: used over not-yet-existing detail view
*/
public save(user: User, viewUser: ViewUser): Observable<User> {
return null;
}
/**
* @ignore
*
* TODO: used over not-yet-existing detail view
*/
public delete(user: ViewUser): Observable<User> {
return null;
}
/**
* @ignore
*
* TODO: used over not-yet-existing detail view
*/
public create(user: User, viewFile: ViewUser): Observable<User> {
return null;
}
public createViewModel(user: User): ViewUser {
const groups = this.DS.getMany(Group, user.groups_id);
return new ViewUser(user, groups);
}
}

View File

@ -1,6 +1,27 @@
<os-head-bar appName="Users">
<os-head-bar appName="Users" plusButton=true (plusButtonClicked)=onPlusButton() [menuList]=userMenuList
(ellipsisMenuItem)=onEllipsisItem($event)>
</os-head-bar>
<mat-card class="os-card">
UserList Works!
</mat-card>
<mat-table class='os-listview-table on-transition-fade' [dataSource]="dataSource" matSort>
<!-- name column -->
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef mat-sort-header> Name </mat-header-cell>
<mat-cell *matCellDef="let user"> {{user.fullName}} </mat-cell>
</ng-container>
<!-- prefix column -->
<ng-container matColumnDef="group">
<mat-header-cell *matHeaderCellDef mat-sort-header> Group </mat-header-cell>
<mat-cell *matCellDef="let user"> {{user.groups}} {{user.structureLevel}} </mat-cell>
</ng-container>
<ng-container matColumnDef="presence">
<mat-header-cell *matHeaderCellDef mat-sort-header> Presence </mat-header-cell>
<mat-cell *matCellDef="let user"> {{user.isActive}} </mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="['name', 'group', 'presence']"></mat-header-row>
<mat-row (click)='selectUser(row)' *matRowDef="let row; columns: ['name', 'group', 'presence']"></mat-row>
</mat-table>
<mat-paginator class="on-transition-fade" [pageSizeOptions]="[25, 50, 75, 100, 125]"></mat-paginator>

View File

@ -1,32 +1,98 @@
import { Component, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { BaseComponent } from '../../../base.component';
import { ViewUser } from '../models/view-user';
import { UserRepositoryService } from '../services/user-repository.service';
import { ListViewBaseComponent } from '../../base/list-view-base';
/**
* Component for the user list view.
*
* TODO: Not yet implemented
*/
@Component({
selector: 'os-user-list',
templateUrl: './user-list.component.html',
styleUrls: ['./user-list.component.css']
})
export class UserListComponent extends BaseComponent implements OnInit {
export class UserListComponent extends ListViewBaseComponent<ViewUser> implements OnInit {
/**
* content of the ellipsis menu
*/
public userMenuList = [
{
text: 'Groups',
icon: 'users',
action: 'toGroups'
},
{
text: 'Import',
icon: 'download',
action: 'toGroups'
},
{
text: 'Export',
icon: 'file-export',
action: 'toGroups'
}
];
/**
* The usual constructor for components
* @param repo the user repository
* @param titleService
* @param translate
*/
public constructor(titleService: Title, protected translate: TranslateService) {
public constructor(
private repo: UserRepositoryService,
protected titleService: Title,
protected translate: TranslateService
) {
super(titleService, translate);
}
/**
* Init function, sets the title
* Init function
*
* sets the title, inits the table and calls the repo
*/
public ngOnInit(): void {
super.setTitle('Users');
this.initTable();
this.repo.getViewModelListObservable().subscribe(newUsers => {
this.dataSource.data = newUsers;
});
}
/**
* Navigate to import page or do it inline
*
* TODO: implement importing of users
*/
public import(): void {
console.log('click on Import');
}
/**
* Navigate to groups page
* TODO: implement
*/
public toGroups(): void {
console.log('to Groups');
}
/**
* Handles the click on a user row
* @param row selected row
*/
public selectUser(row: ViewUser): void {
console.log('clicked the row for user: ', row);
}
/**
* Handles the click on the plus button
*/
public onPlusButton(): void {
console.log('new User');
}
}

View File

@ -39,6 +39,25 @@ body {
margin-right: auto;
}
.os-listview-table {
width: 100%;
/** hide mat header row */
.mat-header-row {
display: none;
}
/** size of the mat row */
mat-row {
height: 60px;
}
mat-row:hover {
cursor: pointer;
background-color: rgba(0, 0, 0, 0.025);
}
}
.card-plus-distance {
margin-top: 40px;
}