Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf7ec832b2 | ||
|
|
1a357f4121 | ||
|
|
8aa0828701 | ||
|
|
41ad9d9a28 | ||
|
|
6af8ccbf7a | ||
|
|
270a55f6b9 | ||
|
|
ad8f5491b5 | ||
|
|
79227258e8 | ||
|
|
80dc5b54e8 | ||
|
|
57f85768eb | ||
|
|
aa995e845a | ||
|
|
a15cb298a0 | ||
|
|
ea05a47086 | ||
|
|
0f535881a4 | ||
|
|
96918b50ca |
@ -1,63 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
|
||||||
"project": {
|
|
||||||
"name": "mtas-tv-frontend"
|
|
||||||
},
|
|
||||||
"apps": [
|
|
||||||
{
|
|
||||||
"root": "src",
|
|
||||||
"outDir": "dist",
|
|
||||||
"assets": [
|
|
||||||
"assets",
|
|
||||||
"favicon.ico"
|
|
||||||
],
|
|
||||||
"index": "index.html",
|
|
||||||
"main": "main.ts",
|
|
||||||
"polyfills": "polyfills.ts",
|
|
||||||
"test": "test.ts",
|
|
||||||
"tsconfig": "tsconfig.app.json",
|
|
||||||
"testTsconfig": "tsconfig.spec.json",
|
|
||||||
"prefix": "app",
|
|
||||||
"styles": [
|
|
||||||
"../node_modules/semantic-ui-css/semantic.css",
|
|
||||||
"styles.css"
|
|
||||||
],
|
|
||||||
"scripts": [
|
|
||||||
"../node_modules/marked/lib/marked.js"
|
|
||||||
],
|
|
||||||
"environmentSource": "environments/environment.ts",
|
|
||||||
"environments": {
|
|
||||||
"dev": "environments/environment.ts",
|
|
||||||
"prod": "environments/environment.prod.ts"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"e2e": {
|
|
||||||
"protractor": {
|
|
||||||
"config": "./protractor.conf.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lint": [
|
|
||||||
{
|
|
||||||
"project": "src/tsconfig.app.json",
|
|
||||||
"exclude": "**/node_modules/**"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"project": "src/tsconfig.spec.json",
|
|
||||||
"exclude": "**/node_modules/**"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"project": "e2e/tsconfig.e2e.json",
|
|
||||||
"exclude": "**/node_modules/**"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"test": {
|
|
||||||
"karma": {
|
|
||||||
"config": "./karma.conf.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"defaults": {
|
|
||||||
"styleExt": "css",
|
|
||||||
"component": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
156
angular.json
Executable file
156
angular.json
Executable file
@ -0,0 +1,156 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"version": 1,
|
||||||
|
"newProjectRoot": "projects",
|
||||||
|
"projects": {
|
||||||
|
"mtas-tv-frontend": {
|
||||||
|
"root": "",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"projectType": "application",
|
||||||
|
"architect": {
|
||||||
|
"build": {
|
||||||
|
"builder": "@angular-devkit/build-angular:browser",
|
||||||
|
"options": {
|
||||||
|
"outputPath": "dist",
|
||||||
|
"index": "src/index.html",
|
||||||
|
"main": "src/main.ts",
|
||||||
|
"tsConfig": "src/tsconfig.app.json",
|
||||||
|
"polyfills": "src/polyfills.ts",
|
||||||
|
"assets": [
|
||||||
|
"src/assets",
|
||||||
|
"src/favicon.ico"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"node_modules/semantic-ui-css/semantic.css",
|
||||||
|
"src/styles.css"
|
||||||
|
],
|
||||||
|
"scripts": [
|
||||||
|
"node_modules/marked/lib/marked.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"optimization": true,
|
||||||
|
"outputHashing": "all",
|
||||||
|
"sourceMap": false,
|
||||||
|
"extractCss": true,
|
||||||
|
"namedChunks": false,
|
||||||
|
"aot": true,
|
||||||
|
"extractLicenses": true,
|
||||||
|
"vendorChunk": false,
|
||||||
|
"buildOptimizer": true,
|
||||||
|
"fileReplacements": [
|
||||||
|
{
|
||||||
|
"replace": "src/environments/environment.ts",
|
||||||
|
"with": "src/environments/environment.prod.ts"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"staging": {
|
||||||
|
"optimization": true,
|
||||||
|
"outputHashing": "all",
|
||||||
|
"sourceMap": false,
|
||||||
|
"extractCss": true,
|
||||||
|
"namedChunks": false,
|
||||||
|
"aot": true,
|
||||||
|
"extractLicenses": true,
|
||||||
|
"vendorChunk": false,
|
||||||
|
"buildOptimizer": true,
|
||||||
|
"fileReplacements": [
|
||||||
|
{
|
||||||
|
"replace": "src/environments/environment.ts",
|
||||||
|
"with": "src/environments/environment.stg.ts"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"serve": {
|
||||||
|
"builder": "@angular-devkit/build-angular:dev-server",
|
||||||
|
"options": {
|
||||||
|
"browserTarget": "mtas-tv-frontend:build"
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"browserTarget": "mtas-tv-frontend:build:production"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extract-i18n": {
|
||||||
|
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||||
|
"options": {
|
||||||
|
"browserTarget": "mtas-tv-frontend:build"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"builder": "@angular-devkit/build-angular:karma",
|
||||||
|
"options": {
|
||||||
|
"main": "src/test.ts",
|
||||||
|
"karmaConfig": "./karma.conf.js",
|
||||||
|
"polyfills": "src/polyfills.ts",
|
||||||
|
"tsConfig": "src/tsconfig.spec.json",
|
||||||
|
"scripts": [
|
||||||
|
"node_modules/marked/lib/marked.js"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"node_modules/semantic-ui-css/semantic.css",
|
||||||
|
"src/styles.css"
|
||||||
|
],
|
||||||
|
"assets": [
|
||||||
|
"src/assets",
|
||||||
|
"src/favicon.ico"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"builder": "@angular-devkit/build-angular:tslint",
|
||||||
|
"options": {
|
||||||
|
"tsConfig": [
|
||||||
|
"src/tsconfig.app.json",
|
||||||
|
"src/tsconfig.spec.json"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"**/node_modules/**"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mtas-tv-frontend-e2e": {
|
||||||
|
"root": "e2e",
|
||||||
|
"sourceRoot": "e2e",
|
||||||
|
"projectType": "application",
|
||||||
|
"architect": {
|
||||||
|
"e2e": {
|
||||||
|
"builder": "@angular-devkit/build-angular:protractor",
|
||||||
|
"options": {
|
||||||
|
"protractorConfig": "./protractor.conf.js",
|
||||||
|
"devServerTarget": "mtas-tv-frontend:serve"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"builder": "@angular-devkit/build-angular:tslint",
|
||||||
|
"options": {
|
||||||
|
"tsConfig": [
|
||||||
|
"e2e/tsconfig.e2e.json"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"**/node_modules/**"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultProject": "mtas-tv-frontend",
|
||||||
|
"schematics": {
|
||||||
|
"@schematics/angular:component": {
|
||||||
|
"prefix": "app",
|
||||||
|
"styleext": "css"
|
||||||
|
},
|
||||||
|
"@schematics/angular:directive": {
|
||||||
|
"prefix": "app"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
deploy.php
Normal file → Executable file
24
deploy.php
Normal file → Executable file
@ -15,21 +15,29 @@ set('writable_dirs', []);
|
|||||||
set('keep_releases', 3);
|
set('keep_releases', 3);
|
||||||
set('default_stage', 'production');
|
set('default_stage', 'production');
|
||||||
|
|
||||||
// Servers
|
// Servers - mtas : esekivws5222a.rnd.ki.sw.ericsson.se
|
||||||
host('vasgyuro.tsp')
|
host('mtas')
|
||||||
->stage('production')
|
->stage('production')
|
||||||
->user('edvidan')
|
->user('edvidan')
|
||||||
->forwardAgent()
|
->forwardAgent()
|
||||||
|
->set('ng_basehref', '/mtastv/')
|
||||||
|
->set('ng_configuration', 'production')
|
||||||
|
->set('env_vars', 'NODE_ENV=production')
|
||||||
|
->set('deploy_path', '/proj/webdocs/mtoolbox/root/mtastv-inst/frontend');
|
||||||
|
|
||||||
|
host('vasgyuro.tsp')
|
||||||
|
->stage('staging')
|
||||||
|
->user('edvidan')
|
||||||
|
->forwardAgent()
|
||||||
->set('ng_basehref', '/mtas-tv/')
|
->set('ng_basehref', '/mtas-tv/')
|
||||||
->set('ng_target', 'production')
|
->set('ng_configuration', 'staging')
|
||||||
->set('ng_environment', 'prod')
|
|
||||||
->set('env_vars', 'NODE_ENV=production')
|
->set('env_vars', 'NODE_ENV=production')
|
||||||
->set('deploy_path', '/home/edvidan/applications/mtas-tv');
|
->set('deploy_path', '/home/edvidan/applications/mtas-tv');
|
||||||
|
|
||||||
// Tasks
|
// Tasks
|
||||||
desc('Prepare release');
|
desc('Build release');
|
||||||
task('deploy:ng-prepare', function() {
|
task('deploy:ng-build', function() {
|
||||||
runLocally("ng build --base-href={{ng_basehref}} --target={{ng_target}} --environment={{ng_environment}}");
|
runLocally("ng build --base-href={{ng_basehref}} --configuration={{ng_configuration}}");
|
||||||
runLocally("tar -cJf dist.tar.xz dist");
|
runLocally("tar -cJf dist.tar.xz dist");
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -52,7 +60,7 @@ task('deploy', [
|
|||||||
'deploy:prepare',
|
'deploy:prepare',
|
||||||
'deploy:lock',
|
'deploy:lock',
|
||||||
'deploy:release',
|
'deploy:release',
|
||||||
'deploy:ng-prepare',
|
'deploy:ng-build',
|
||||||
'deploy:ng-upload',
|
'deploy:ng-upload',
|
||||||
'deploy:shared',
|
'deploy:shared',
|
||||||
'deploy:clear_paths',
|
'deploy:clear_paths',
|
||||||
|
|||||||
12
htaccess
12
htaccess
@ -16,12 +16,12 @@ RewriteCond %{REQUEST_URI}::$1 ^(/.+)(.+)::\2$
|
|||||||
RewriteRule ^(.*) - [E=BASE:%1]
|
RewriteRule ^(.*) - [E=BASE:%1]
|
||||||
RewriteRule ^(.*)$ %{ENV:BASE}index.html [NC,L]
|
RewriteRule ^(.*)$ %{ENV:BASE}index.html [NC,L]
|
||||||
|
|
||||||
<Limit GET POST PUT DELETE HEAD OPTIONS>
|
#<Limit GET POST PUT DELETE HEAD OPTIONS>
|
||||||
Require all granted
|
# Require all granted
|
||||||
</Limit>
|
#</Limit>
|
||||||
<LimitExcept GET POST PUT DELETE HEAD OPTIONS>
|
#<LimitExcept GET POST PUT DELETE HEAD OPTIONS>
|
||||||
Require all denied
|
# Require all denied
|
||||||
</LimitExcept>
|
#</LimitExcept>
|
||||||
|
|
||||||
<Files revision.json>
|
<Files revision.json>
|
||||||
FileETag None
|
FileETag None
|
||||||
|
|||||||
@ -4,24 +4,22 @@
|
|||||||
module.exports = function (config) {
|
module.exports = function (config) {
|
||||||
config.set({
|
config.set({
|
||||||
basePath: '',
|
basePath: '',
|
||||||
frameworks: ['jasmine', '@angular/cli'],
|
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||||
plugins: [
|
plugins: [
|
||||||
require('karma-jasmine'),
|
require('karma-jasmine'),
|
||||||
require('karma-chrome-launcher'),
|
require('karma-chrome-launcher'),
|
||||||
require('karma-jasmine-html-reporter'),
|
require('karma-jasmine-html-reporter'),
|
||||||
require('karma-coverage-istanbul-reporter'),
|
require('karma-coverage-istanbul-reporter'),
|
||||||
require('@angular/cli/plugins/karma')
|
require('@angular-devkit/build-angular/plugins/karma')
|
||||||
],
|
],
|
||||||
client:{
|
client:{
|
||||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||||
},
|
},
|
||||||
coverageIstanbulReporter: {
|
coverageIstanbulReporter: {
|
||||||
reports: [ 'html', 'lcovonly' ],
|
dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ],
|
||||||
fixWebpackSourcePaths: true
|
fixWebpackSourcePaths: true
|
||||||
},
|
},
|
||||||
angularCli: {
|
|
||||||
environment: 'dev'
|
|
||||||
},
|
|
||||||
reporters: ['progress', 'kjhtml'],
|
reporters: ['progress', 'kjhtml'],
|
||||||
port: 9876,
|
port: 9876,
|
||||||
colors: true,
|
colors: true,
|
||||||
|
|||||||
11187
package-lock.json
generated
11187
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
64
package.json
64
package.json
@ -1,52 +1,54 @@
|
|||||||
{
|
{
|
||||||
"name": "mtas-tv-frontend",
|
"name": "mtas-tv-frontend",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"license": "MIT",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
"start": "ng serve",
|
"start": "ng serve",
|
||||||
"build": "ng build --prod",
|
"build": "ng build",
|
||||||
"test": "ng test",
|
"test": "ng test",
|
||||||
"lint": "ng lint",
|
"lint": "ng lint",
|
||||||
"e2e": "ng e2e"
|
"e2e": "ng e2e"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^5.2.0",
|
"@angular/animations": "^6.1.7",
|
||||||
"@angular/common": "^5.2.0",
|
"@angular/common": "^6.1.7",
|
||||||
"@angular/compiler": "^5.2.0",
|
"@angular/compiler": "^6.1.7",
|
||||||
"@angular/core": "^5.2.0",
|
"@angular/core": "^6.1.7",
|
||||||
"@angular/forms": "^5.2.0",
|
"@angular/forms": "^6.1.7",
|
||||||
"@angular/http": "^5.2.0",
|
"@angular/http": "^6.1.7",
|
||||||
"@angular/platform-browser": "^5.2.0",
|
"@angular/platform-browser": "^6.1.7",
|
||||||
"@angular/platform-browser-dynamic": "^5.2.0",
|
"@angular/platform-browser-dynamic": "^6.1.7",
|
||||||
"@angular/router": "^5.2.0",
|
"@angular/router": "^6.1.7",
|
||||||
"@types/marked": "^0.3.0",
|
"@types/date-fns": "^2.6.0",
|
||||||
"core-js": "^2.4.1",
|
"@types/marked": "^0.4.1",
|
||||||
"marked": "^0.3.19",
|
"core-js": "^2.5.4",
|
||||||
|
"date-fns": "^1.29.0",
|
||||||
|
"marked": "^0.5.0",
|
||||||
"ng2-semantic-ui": "^0.9.7",
|
"ng2-semantic-ui": "^0.9.7",
|
||||||
"rxjs": "^5.5.6",
|
"rxjs": "^6.3.2",
|
||||||
"semantic-ui-css": "^2.3.1",
|
"semantic-ui-css": "^2.3.3",
|
||||||
"zone.js": "^0.8.19"
|
"zone.js": "~0.8.26"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular/cli": "~1.7.3",
|
"@angular-devkit/build-angular": "~0.7.0",
|
||||||
"@angular/compiler-cli": "^5.2.0",
|
"@angular/cli": "~6.1.5",
|
||||||
"@angular/language-service": "^5.2.0",
|
"@angular/compiler-cli": "^6.1.7",
|
||||||
"@types/jasmine": "~2.8.3",
|
"@angular/language-service": "^6.1.7",
|
||||||
"@types/jasminewd2": "~2.0.2",
|
"@types/jasmine": "^2.8.8",
|
||||||
"@types/node": "~6.0.60",
|
"@types/jasminewd2": "~2.0.3",
|
||||||
"codelyzer": "^4.0.1",
|
"@types/node": "~8.9.4",
|
||||||
"jasmine-core": "~2.8.0",
|
"codelyzer": "~4.2.1",
|
||||||
|
"jasmine-core": "~2.99.1",
|
||||||
"jasmine-spec-reporter": "~4.2.1",
|
"jasmine-spec-reporter": "~4.2.1",
|
||||||
"karma": "~2.0.0",
|
"karma": "~1.7.1",
|
||||||
"karma-chrome-launcher": "~2.2.0",
|
"karma-chrome-launcher": "~2.2.0",
|
||||||
"karma-coverage-istanbul-reporter": "^1.2.1",
|
"karma-coverage-istanbul-reporter": "^2.0.4",
|
||||||
"karma-jasmine": "~1.1.0",
|
"karma-jasmine": "^1.1.2",
|
||||||
"karma-jasmine-html-reporter": "^0.2.2",
|
"karma-jasmine-html-reporter": "^0.2.2",
|
||||||
"protractor": "~5.1.2",
|
"protractor": "^5.4.1",
|
||||||
"ts-node": "~4.1.0",
|
"ts-node": "~5.0.1",
|
||||||
"tslint": "~5.9.1",
|
"tslint": "~5.9.1",
|
||||||
"typescript": "~2.5.3"
|
"typescript": "~2.7.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
src/app/admin/admin-routing.module.ts
Normal file → Executable file
23
src/app/admin/admin-routing.module.ts
Normal file → Executable file
@ -11,63 +11,56 @@ import { SlideResolverService } from './slide-resolver.service';
|
|||||||
import { SlideService } from '../shared/service/slide.service';
|
import { SlideService } from '../shared/service/slide.service';
|
||||||
import { DashboardComponent } from './dashboard/dashboard.component';
|
import { DashboardComponent } from './dashboard/dashboard.component';
|
||||||
|
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: 'admin',
|
path: 'admin',
|
||||||
redirectTo: '/dashboard',
|
redirectTo: '/dashboard',
|
||||||
pathMatch: 'full'
|
pathMatch: 'full',
|
||||||
// canActivate: [AuthGuardService, RoleGuardService],
|
|
||||||
}, {
|
}, {
|
||||||
path: 'dashboard',
|
path: 'dashboard',
|
||||||
component: DashboardComponent,
|
component: DashboardComponent,
|
||||||
// canActivate: [AuthGuardService, RoleGuardService],
|
|
||||||
}, {
|
}, {
|
||||||
path: 'admin/teams',
|
path: 'admin/teams',
|
||||||
component: TeamListComponent,
|
component: TeamListComponent,
|
||||||
// canActivate: [AuthGuardService, RoleGuardService],
|
|
||||||
resolve: {
|
resolve: {
|
||||||
teams: TeamService,
|
teams: TeamService,
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
path: 'admin/team/new',
|
path: 'admin/team/new',
|
||||||
component: TeamEditorComponent,
|
component: TeamEditorComponent,
|
||||||
// canActivate: [AuthGuardService, RoleGuardService],
|
|
||||||
}, {
|
}, {
|
||||||
path: 'admin/team/edit/:id',
|
path: 'admin/team/edit/:id',
|
||||||
component: TeamEditorComponent,
|
component: TeamEditorComponent,
|
||||||
// canActivate: [AuthGuardService, RoleGuardService],
|
|
||||||
resolve: {
|
resolve: {
|
||||||
team: TeamResolverService,
|
team: TeamResolverService,
|
||||||
}
|
},
|
||||||
}, {
|
}, {
|
||||||
path: 'admin/slides',
|
path: 'admin/slides',
|
||||||
component: SlideListComponent,
|
component: SlideListComponent,
|
||||||
// canActivate: [AuthGuardService, RoleGuardService],
|
|
||||||
resolve: {
|
resolve: {
|
||||||
slides: SlideService,
|
slides: SlideService,
|
||||||
teams: TeamService,
|
teams: TeamService,
|
||||||
}
|
},
|
||||||
}, {
|
}, {
|
||||||
path: 'admin/slide/new',
|
path: 'admin/slide/new',
|
||||||
component: SlideEditorComponent,
|
component: SlideEditorComponent,
|
||||||
// canActivate: [AuthGuardService, RoleGuardService],
|
|
||||||
resolve: {
|
resolve: {
|
||||||
teams: TeamService,
|
teams: TeamService,
|
||||||
}
|
},
|
||||||
}, {
|
}, {
|
||||||
path: 'admin/slide/edit/:id',
|
path: 'admin/slide/edit/:id',
|
||||||
component: SlideEditorComponent,
|
component: SlideEditorComponent,
|
||||||
// canActivate: [AuthGuardService, RoleGuardService],
|
|
||||||
resolve: {
|
resolve: {
|
||||||
slide: SlideResolverService,
|
slide: SlideResolverService,
|
||||||
teams: TeamService,
|
teams: TeamService,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forChild(routes)],
|
imports: [RouterModule.forChild(routes)],
|
||||||
exports: [RouterModule]
|
exports: [RouterModule]
|
||||||
})
|
})
|
||||||
export class AdminRoutingModule {
|
export class AdminRoutingModule {}
|
||||||
}
|
|
||||||
|
|||||||
2
src/app/admin/dashboard/dashboard.component.html
Normal file → Executable file
2
src/app/admin/dashboard/dashboard.component.html
Normal file → Executable file
@ -2,7 +2,7 @@
|
|||||||
<h1 class="ui dividing header">Dashboard</h1>
|
<h1 class="ui dividing header">Dashboard</h1>
|
||||||
|
|
||||||
<div class="ui four cards">
|
<div class="ui four cards">
|
||||||
<a class="ui raised yellow card" [routerLink]="['/commit-tracker']">
|
<a class="ui raised yellow card" (click)="startSlideShow()" *ngIf="hasTeamSelected">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="header">Start slideshow</div>
|
<div class="header">Start slideshow</div>
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
|
|||||||
13
src/app/admin/dashboard/dashboard.component.ts
Normal file → Executable file
13
src/app/admin/dashboard/dashboard.component.ts
Normal file → Executable file
@ -1,5 +1,7 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
|
import {SettingsService} from '../../shared/service/settings.service';
|
||||||
|
import {SlideShowService} from '../../display/slide-show.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-dashboard',
|
selector: 'app-dashboard',
|
||||||
@ -8,10 +10,19 @@ import { Title } from '@angular/platform-browser';
|
|||||||
})
|
})
|
||||||
export class DashboardComponent implements OnInit {
|
export class DashboardComponent implements OnInit {
|
||||||
|
|
||||||
constructor(private titleService: Title) { }
|
constructor(private titleService: Title,
|
||||||
|
private settingService: SettingsService,
|
||||||
|
private slideShowService: SlideShowService) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.titleService.setTitle('Dashboard : MTAStv');
|
this.titleService.setTitle('Dashboard : MTAStv');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get hasTeamSelected(): boolean {
|
||||||
|
return this.settingService.team.id !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public startSlideShow() {
|
||||||
|
this.slideShowService.startWithFirstSlide();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
52
src/app/admin/slide-editor/slide-editor.component.html
Normal file → Executable file
52
src/app/admin/slide-editor/slide-editor.component.html
Normal file → Executable file
@ -2,31 +2,55 @@
|
|||||||
<h1 class="ui dividing header">Slide editor</h1>
|
<h1 class="ui dividing header">Slide editor</h1>
|
||||||
<form class="ui form" #slideEditorForm (ngSubmit)="saveSlide()">
|
<form class="ui form" #slideEditorForm (ngSubmit)="saveSlide()">
|
||||||
<div class="two fields">
|
<div class="two fields">
|
||||||
<div class="eight wide field">
|
<div class="six wide field">
|
||||||
<label for="slide_name">Slide title</label>
|
<label for="slide_name">Slide title</label>
|
||||||
<input id="slide_name" type="text" name="slide_name" [(ngModel)]="slide.title">
|
<input id="slide_name" type="text" name="slide_name" [(ngModel)]="slide.title">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="two wide field">
|
||||||
|
<label>Visibility</label>
|
||||||
|
<div class="field">
|
||||||
|
<sui-radio-button name="slide_visibility" value="public" [(ngModel)]="slide.visibility">Public</sui-radio-button>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<sui-radio-button name="slide_visibility" value="team" [(ngModel)]="slide.visibility">Team</sui-radio-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="eight wide field">
|
<div class="eight wide field">
|
||||||
<label for="team">Visible to this team</label>
|
<label for="teams">Visible to this team</label>
|
||||||
<sui-select class="selection"
|
<sui-multi-select class="selection"
|
||||||
id="team"
|
id="teams"
|
||||||
name="team"
|
name="teams"
|
||||||
[(ngModel)]="slide.team"
|
[(ngModel)]="slide.teams"
|
||||||
|
[isDisabled]="slide.visibility=='public'"
|
||||||
labelField="name"
|
labelField="name"
|
||||||
[isSearchable]="true"
|
[isSearchable]="true"
|
||||||
#select>
|
#select>
|
||||||
<sui-select-option [value]="emptyTeam"></sui-select-option>
|
|
||||||
<sui-select-option *ngFor="let team of teams" [value]="team"></sui-select-option>
|
<sui-select-option *ngFor="let team of teams" [value]="team"></sui-select-option>
|
||||||
</sui-select>
|
</sui-multi-select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="inline fields">
|
||||||
|
<label>Slide type</label>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
|
<sui-radio-button name="slide_type" value="markdown" [(ngModel)]="slide.type">Markdown</sui-radio-button>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<sui-radio-button name="slide_type" value="iframe" [(ngModel)]="slide.type">Iframe</sui-radio-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field" *ngIf="isMarkdown">
|
||||||
<label for="slide_data">Slide data</label>
|
<label for="slide_data">Slide data</label>
|
||||||
<textarea id="slide_data" rows="30" name="slide_data" [(ngModel)]="slide.slideData"></textarea>
|
<textarea id="slide_data" rows="30" name="slide_data" [(ngModel)]="slide.slideData"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="field" *ngIf="isIframe" [class.error]="checkError(slideUrl)">
|
||||||
|
<label for="slide_url">Slide url</label>
|
||||||
|
<input id="slide_url" type="url" name="slide_url"
|
||||||
|
[(ngModel)]="slide.slideUrl" #slideUrl="ngModel">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="five wide field">
|
<div class="five wide field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
@ -43,7 +67,8 @@
|
|||||||
[class.primary]="canPreview"
|
[class.primary]="canPreview"
|
||||||
[class.disabled]="!canPreview"
|
[class.disabled]="!canPreview"
|
||||||
(click)="preview()"><i class="search icon"></i>Preview</button>
|
(click)="preview()"><i class="search icon"></i>Preview</button>
|
||||||
<a class="ui button orange"
|
<a *ngIf="isMarkdown"
|
||||||
|
class="ui button orange"
|
||||||
href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet"
|
href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet"
|
||||||
target="_blank"><i class="question circle outline icon"></i>MD howto</a>
|
target="_blank"><i class="question circle outline icon"></i>MD howto</a>
|
||||||
<a class="ui button"
|
<a class="ui button"
|
||||||
@ -54,5 +79,10 @@
|
|||||||
[data]="renderedPreview"
|
[data]="renderedPreview"
|
||||||
[preview]="true"
|
[preview]="true"
|
||||||
[(visible)]="previewVisible"
|
[(visible)]="previewVisible"
|
||||||
*ngIf="previewVisible"></app-slide>
|
*ngIf="previewVisible && isMarkdown"></app-slide>
|
||||||
|
<app-slide-iframe class="preview"
|
||||||
|
[data]="slide.slideUrl"
|
||||||
|
[preview]="true"
|
||||||
|
[(visible)]="previewVisible"
|
||||||
|
*ngIf="previewVisible && isIframe"></app-slide-iframe>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
34
src/app/admin/slide-editor/slide-editor.component.ts
Normal file → Executable file
34
src/app/admin/slide-editor/slide-editor.component.ts
Normal file → Executable file
@ -3,9 +3,10 @@ import { ActivatedRoute, Router } from '@angular/router';
|
|||||||
import {Title} from '@angular/platform-browser';
|
import {Title} from '@angular/platform-browser';
|
||||||
|
|
||||||
import * as marked from 'marked';
|
import * as marked from 'marked';
|
||||||
import { Slide } from '../../shared/slide';
|
import {Slide, SlideType, SlideVisibility} from '../../shared/slide';
|
||||||
import {SlideService} from '../../shared/service/slide.service';
|
import {SlideService} from '../../shared/service/slide.service';
|
||||||
import {Team} from '../../shared/team';
|
import {Team} from '../../shared/team';
|
||||||
|
import {NgModel} from '@angular/forms';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-slide-editor',
|
selector: 'app-slide-editor',
|
||||||
@ -14,7 +15,6 @@ import { Team } from '../../shared/team';
|
|||||||
})
|
})
|
||||||
export class SlideEditorComponent implements OnInit {
|
export class SlideEditorComponent implements OnInit {
|
||||||
private md;
|
private md;
|
||||||
public emptyTeam: Team = new Team();
|
|
||||||
public slide: Slide;
|
public slide: Slide;
|
||||||
public teams: Array<Team> = [];
|
public teams: Array<Team> = [];
|
||||||
public renderedPreview: String = '';
|
public renderedPreview: String = '';
|
||||||
@ -25,7 +25,6 @@ export class SlideEditorComponent implements OnInit {
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router) {
|
private router: Router) {
|
||||||
this.md = marked.setOptions({});
|
this.md = marked.setOptions({});
|
||||||
this.emptyTeam.name = 'All teams';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@ -36,9 +35,11 @@ export class SlideEditorComponent implements OnInit {
|
|||||||
}) => {
|
}) => {
|
||||||
this.teams = data.teams;
|
this.teams = data.teams;
|
||||||
this.slide = data.slide ? data.slide : new Slide;
|
this.slide = data.slide ? data.slide : new Slide;
|
||||||
this.slide.team = this.slide.team === null
|
this.slide.teams = this.teams.filter(
|
||||||
? this.emptyTeam
|
team => this.slide.teams.some(
|
||||||
: this.teams.find(team => team.id === this.slide.team.id);
|
st => team.id === st.id
|
||||||
|
)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,16 +54,31 @@ export class SlideEditorComponent implements OnInit {
|
|||||||
get canSave(): boolean {
|
get canSave(): boolean {
|
||||||
return [
|
return [
|
||||||
this.slide.title,
|
this.slide.title,
|
||||||
this.slide.slideData
|
this.isMarkdown ? this.slide.slideData : this.slide.slideUrl
|
||||||
].every(field => field.trim().length > 0);
|
].every(field => field.trim().length > 0) && (
|
||||||
|
this.slide.visibility === SlideVisibility.Public ||
|
||||||
|
this.slide.visibility === SlideVisibility.Team && this.slide.teams.length > 0
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get canPreview(): boolean {
|
get canPreview(): boolean {
|
||||||
return this.slide.slideData.trim().length > 0;
|
return this.isMarkdown && this.slide.slideData.trim().length > 0 || this.isIframe && this.slide.slideUrl.trim().length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public preview() {
|
public preview() {
|
||||||
this.previewVisible = true;
|
this.previewVisible = true;
|
||||||
this.renderedPreview = this.md.parse(this.slide.slideData);
|
this.renderedPreview = this.md.parse(this.slide.slideData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isMarkdown(): boolean {
|
||||||
|
return this.slide.type === SlideType.MarkDown;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isIframe(): boolean {
|
||||||
|
return this.slide.type === SlideType.IFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
public checkError(fieldModel: NgModel): boolean {
|
||||||
|
return fieldModel.invalid && (fieldModel.dirty || fieldModel.touched);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
src/app/admin/slide-list/slide-list.component.html
Normal file → Executable file
6
src/app/admin/slide-list/slide-list.component.html
Normal file → Executable file
@ -19,8 +19,8 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th class="collapsing"></th>
|
<th class="collapsing"></th>
|
||||||
<th><i class="large address book outline icon"></i>Slide title</th>
|
<th><i class="large address book outline icon"></i>Slide title</th>
|
||||||
<th class="collapsing"><i class="large users icon"></i>Owner team</th>
|
<th class="collapsing"><i class="large users icon"></i>Visible to</th>
|
||||||
<th class="collapsing"><i class="large check square outline icon"></i>Visible</th>
|
<th class="collapsing"><i class="large check square outline icon"></i>Active</th>
|
||||||
<th class="collapsing"><i class="large arrows alternate vertical icon"></i>Order</th>
|
<th class="collapsing"><i class="large arrows alternate vertical icon"></i>Order</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -33,7 +33,7 @@
|
|||||||
class="large link red fitted trash alternate outline icon"></i></a>
|
class="large link red fitted trash alternate outline icon"></i></a>
|
||||||
</td>
|
</td>
|
||||||
<td>{{slide.title}}</td>
|
<td>{{slide.title}}</td>
|
||||||
<td class="collapsing">{{slideTeam(slide.team)}}</td>
|
<td class="collapsing">{{slideTeam(slide)}}</td>
|
||||||
<td class="center aligned"><i class="large icon" [ngClass]="visibleClass(slide)"></i></td>
|
<td class="center aligned"><i class="large icon" [ngClass]="visibleClass(slide)"></i></td>
|
||||||
<td class="center aligned">
|
<td class="center aligned">
|
||||||
<a title="Up" (click)="moveUp(slide)"><i
|
<a title="Up" (click)="moveUp(slide)"><i
|
||||||
|
|||||||
17
src/app/admin/slide-list/slide-list.component.ts
Normal file → Executable file
17
src/app/admin/slide-list/slide-list.component.ts
Normal file → Executable file
@ -3,7 +3,7 @@ import { ActivatedRoute } from '@angular/router';
|
|||||||
import {Title} from '@angular/platform-browser';
|
import {Title} from '@angular/platform-browser';
|
||||||
|
|
||||||
import {SlideService} from '../../shared/service/slide.service';
|
import {SlideService} from '../../shared/service/slide.service';
|
||||||
import { Slide } from '../../shared/slide';
|
import {Slide, SlideVisibility} from '../../shared/slide';
|
||||||
import {Team} from '../../shared/team';
|
import {Team} from '../../shared/team';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -38,7 +38,7 @@ export class SlideListComponent implements OnInit {
|
|||||||
return this.selectedTeam === this.emptyTeam
|
return this.selectedTeam === this.emptyTeam
|
||||||
? this.slideService.slides
|
? this.slideService.slides
|
||||||
: this.slideService.slides.filter(
|
: this.slideService.slides.filter(
|
||||||
slide => slide.team == null || slide.team.id === this.selectedTeam.id
|
slide => slide.teams == null || slide.teams.some(s => s.id === this.selectedTeam.id)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,8 +46,10 @@ export class SlideListComponent implements OnInit {
|
|||||||
this.slideService.slides = slides;
|
this.slideService.slides = slides;
|
||||||
}
|
}
|
||||||
|
|
||||||
public slideTeam(team: Team): String {
|
public slideTeam(slide: Slide): string {
|
||||||
return team === null ? 'All teams' : team.name;
|
return slide.visibility === SlideVisibility.Team && slide.teams.length > 0
|
||||||
|
? slide.teams.map(team => team.name).join(', ')
|
||||||
|
: 'All teams';
|
||||||
}
|
}
|
||||||
|
|
||||||
public moveUp(slide: Slide) {
|
public moveUp(slide: Slide) {
|
||||||
@ -106,4 +108,11 @@ export class SlideListComponent implements OnInit {
|
|||||||
'red times': !slide.isVisible
|
'red times': !slide.isVisible
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public visibilityIcon(slide: Slide) {
|
||||||
|
return {
|
||||||
|
'green globe': slide.visibility === SlideVisibility.Public,
|
||||||
|
'green users': slide.visibility === SlideVisibility.Team
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
src/app/admin/slide-resolver.service.ts
Normal file → Executable file
2
src/app/admin/slide-resolver.service.ts
Normal file → Executable file
@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
import { environment } from '../../environments/environment';
|
import { environment } from '../../environments/environment';
|
||||||
import { Slide } from '../shared/slide';
|
import { Slide } from '../shared/slide';
|
||||||
|
|||||||
181
src/app/admin/team-editor/team-editor.component.html
Normal file → Executable file
181
src/app/admin/team-editor/team-editor.component.html
Normal file → Executable file
@ -1,20 +1,186 @@
|
|||||||
<div class="ui main container">
|
<div class="ui main container">
|
||||||
<h1 class="ui dividing header">Team editor</h1>
|
<h1 class="ui dividing header">Team editor</h1>
|
||||||
<form class="ui form" #teamEditorForm (ngSubmit)="saveTeam()">
|
<form class="ui form" #teamEditorForm (ngSubmit)="saveTeam(f)" #f="ngForm">
|
||||||
<div class="six wide field">
|
<div class="two inline fields">
|
||||||
|
<div class="six wide field" [class.error]="checkError(teamName)">
|
||||||
<label for="team_name">Team name</label>
|
<label for="team_name">Team name</label>
|
||||||
<input id="team_name" type="text" name="team_name" [(ngModel)]="team.name">
|
<input id="team_name" type="text" name="team_name"
|
||||||
|
required [(ngModel)]="team.name" #teamName="ngModel">
|
||||||
</div>
|
</div>
|
||||||
<div class="six wide field">
|
<div class="six wide field">
|
||||||
<label for="team_name"> </label>
|
<label> </label>
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" id="team_is_active" name="team_is_active"
|
<input type="checkbox" id="team_is_active" name="team_is_active"
|
||||||
[(ngModel)]="team.isActive">
|
[(ngModel)]="team.isActive">
|
||||||
<label for="team_is_active">Active</label>
|
<label for="team_is_active">Active</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h4 class="ui dividing header">Team members</h4>
|
<h4 class="ui dividing header">Built-in slides</h4>
|
||||||
|
<div class="three inline fields">
|
||||||
|
<div class="three wide field">
|
||||||
|
<label for="team_name"> </label>
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input type="checkbox" id="kanban_enabled" name="kanban_enabled"
|
||||||
|
[(ngModel)]="team.kanbanEnabled">
|
||||||
|
<label for="kanban_enabled">Kanban board</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="three wide field">
|
||||||
|
<label for="team_name"> </label>
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input type="checkbox" id="commit_tracker_enabled" name="commit_tracker_enabled"
|
||||||
|
[(ngModel)]="team.commitTrackerEnabled">
|
||||||
|
<label for="commit_tracker_enabled">Commit-tracker</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="three wide field">
|
||||||
|
<label for="team_name"> </label>
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input type="checkbox" id="watched_enabled" name="watched_enabled"
|
||||||
|
[(ngModel)]="team.watchedEnabled">
|
||||||
|
<label for="watched_enabled">Watched</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-container *ngIf="team.kanbanEnabled">
|
||||||
|
<h3 class="ui dividing header">Kanban configuration</h3>
|
||||||
|
<div class="six wide field" [class.error]="checkError(filterId)">
|
||||||
|
<label for="filter_id">Jira filter id</label>
|
||||||
|
<input id="filter_id" type="number" name="filter_id"
|
||||||
|
required minlength="4" min="1" [required]="team.kanbanEnabled"
|
||||||
|
[(ngModel)]="team.filterId" #filterId="ngModel">
|
||||||
|
</div>
|
||||||
|
<h5 class="ui dividing header">Daily standup timer</h5>
|
||||||
|
<div class="three inline fields">
|
||||||
|
<div class="two wide field">
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input type="checkbox" id="daily_lock_timer" name="daily_lock_timer"
|
||||||
|
[(ngModel)]="team.dailyLockEnabled">
|
||||||
|
<label for="daily_lock_timer">Enabled</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="four wide field" [class.error]="checkError(startTime)">
|
||||||
|
<label for="daily_start">Starts</label>
|
||||||
|
<div class="ui left icon input">
|
||||||
|
<input type="time" id="daily_start" name="daily_start"
|
||||||
|
min="9:00" max="15:00" [required]="team.dailyLockEnabled && team.kanbanEnabled"
|
||||||
|
[(ngModel)]="team.dailyStartTime" #startTime="ngModel">
|
||||||
|
<i class="time icon"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="four wide field" [class.error]="checkError(endTime)">
|
||||||
|
<label for="daily_end">Ends</label>
|
||||||
|
<div class="ui left icon input">
|
||||||
|
<input type="time" id="daily_end" name="daily_end"
|
||||||
|
min="9:00" max="15:00" [required]="team.dailyLockEnabled && team.kanbanEnabled"
|
||||||
|
[(ngModel)]="team.dailyEndTime" #endTime="ngModel">
|
||||||
|
<i class="time icon"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 class="ui dividing header">Columns</h4>
|
||||||
|
<div class="four fields">
|
||||||
|
<div class="six wide field">
|
||||||
|
<label>Jira status</label>
|
||||||
|
<input type="text" name="column1_js"
|
||||||
|
placeholder="Jira column name" [(ngModel)]="team.backlogColumn.jiraStatusName">
|
||||||
|
</div>
|
||||||
|
<div class="four wide field">
|
||||||
|
<label>Display name</label>
|
||||||
|
<input type="text" name="column1_l"
|
||||||
|
placeholder="Kanban board header" [(ngModel)]="team.backlogColumn.label">
|
||||||
|
</div>
|
||||||
|
<div class="two wide field">
|
||||||
|
<label>WIP limit</label>
|
||||||
|
<input type="text" name="column1_wip"
|
||||||
|
placeholder="WIP limit" [(ngModel)]="team.backlogColumn.wipLimit">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="four fields">
|
||||||
|
<div class="six wide field">
|
||||||
|
<input type="text" name="column2_js"
|
||||||
|
placeholder="Jira column name" [(ngModel)]="team.inprogressColumn.jiraStatusName">
|
||||||
|
</div>
|
||||||
|
<div class="four wide field">
|
||||||
|
<input type="text" name="column2_l"
|
||||||
|
placeholder="Kanban board header" [(ngModel)]="team.inprogressColumn.label">
|
||||||
|
</div>
|
||||||
|
<div class="two wide field">
|
||||||
|
<input type="text" name="column2_wip"
|
||||||
|
placeholder="WIP limit" [(ngModel)]="team.inprogressColumn.wipLimit">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="four fields">
|
||||||
|
<div class="six wide field">
|
||||||
|
<input type="text" name="column3_js"
|
||||||
|
placeholder="Jira column name" [(ngModel)]="team.verificationColumn.jiraStatusName">
|
||||||
|
</div>
|
||||||
|
<div class="four wide field">
|
||||||
|
<input type="text" name="column3_l"
|
||||||
|
placeholder="Kanban board header" [(ngModel)]="team.verificationColumn.label">
|
||||||
|
</div>
|
||||||
|
<div class="two wide field">
|
||||||
|
<input type="text" name="column3_wip"
|
||||||
|
placeholder="WIP limit" [(ngModel)]="team.verificationColumn.wipLimit">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="four fields">
|
||||||
|
<div class="six wide field">
|
||||||
|
<input type="text" name="column4_js"
|
||||||
|
placeholder="Jira column name" [(ngModel)]="team.doneColumn.jiraStatusName">
|
||||||
|
</div>
|
||||||
|
<div class="four wide field">
|
||||||
|
<input type="text" name="column4_l"
|
||||||
|
placeholder="Kanban board header" [(ngModel)]="team.doneColumn.label">
|
||||||
|
</div>
|
||||||
|
<div class="two wide field">
|
||||||
|
<input type="text" name="column4_wip"
|
||||||
|
placeholder="WIP limit" [(ngModel)]="team.doneColumn.wipLimit">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 class="ui dividing header">Labels</h4>
|
||||||
|
<div class="three inline fields">
|
||||||
|
<div class="two wide field">
|
||||||
|
<button type="button" class="ui fluid button"
|
||||||
|
[class.positive]="canAddLabel"
|
||||||
|
[class.disabled]="!canAddLabel"
|
||||||
|
(keydown.enter)="handleEnter($event)"
|
||||||
|
(click)="addLabel()">Add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="five wide field">
|
||||||
|
<input type="text" #labelInput
|
||||||
|
name="label_name"
|
||||||
|
placeholder="Label text"
|
||||||
|
(keydown.enter)="handleLabelEnter($event)"
|
||||||
|
[(ngModel)]="label.name">
|
||||||
|
</div>
|
||||||
|
<ng-template let-option #optionTemplate>
|
||||||
|
<span class="ui tiny {{option}} label">{{option}}</span>
|
||||||
|
</ng-template>
|
||||||
|
<sui-select class="ui right floated selection"
|
||||||
|
id="label_color"
|
||||||
|
name="label_color"
|
||||||
|
[(ngModel)]="label.color"
|
||||||
|
[optionTemplate]="optionTemplate"
|
||||||
|
[isSearchable]="false"
|
||||||
|
#labelSelect>
|
||||||
|
<sui-select-option *ngFor="let labelColor of labelColors" [value]="labelColor"></sui-select-option>
|
||||||
|
</sui-select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span *ngFor="let label of team.labels"
|
||||||
|
class="ui medium {{label.color}} label">{{label.name}}<i class="large delete icon"
|
||||||
|
(click)="removeLabel(label)"></i></span>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<h3 class="ui dividing header">Team members</h3>
|
||||||
<div class="three inline fields">
|
<div class="three inline fields">
|
||||||
<div class="two wide field">
|
<div class="two wide field">
|
||||||
<button type="button" class="ui fluid button"
|
<button type="button" class="ui fluid button"
|
||||||
@ -39,6 +205,7 @@
|
|||||||
[(ngModel)]="member.name">
|
[(ngModel)]="member.name">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h4 class="ui dividing header"></h4>
|
<h4 class="ui dividing header"></h4>
|
||||||
<table class="ui celled definition table" *ngIf="team.members.length">
|
<table class="ui celled definition table" *ngIf="team.members.length">
|
||||||
<thead>
|
<thead>
|
||||||
@ -59,8 +226,8 @@
|
|||||||
</table>
|
</table>
|
||||||
|
|
||||||
<button type="submit" class="ui button"
|
<button type="submit" class="ui button"
|
||||||
[class.positive]="canSave"
|
[class.positive]="canSave(f)"
|
||||||
[class.disabled]="!canSave"><i class="save outline icon"></i>Save changes
|
[class.disabled]="!canSave(f)"><i class="save outline icon"></i>Save changes
|
||||||
</button>
|
</button>
|
||||||
<a class="ui button"
|
<a class="ui button"
|
||||||
[routerLink]="['/admin/teams']"><i class="left angle icon"></i>Back to teams list</a>
|
[routerLink]="['/admin/teams']"><i class="left angle icon"></i>Back to teams list</a>
|
||||||
|
|||||||
72
src/app/admin/team-editor/team-editor.component.ts
Normal file → Executable file
72
src/app/admin/team-editor/team-editor.component.ts
Normal file → Executable file
@ -1,11 +1,12 @@
|
|||||||
import { Component, ElementRef, HostBinding, OnInit, ViewChild } from '@angular/core';
|
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
import { TeamService } from '../../shared/service/team.service';
|
import { TeamService } from '../../shared/service/team.service';
|
||||||
import { Team } from '../../shared/team';
|
import { Team } from '../../shared/team';
|
||||||
import { Member } from '../../shared/member';
|
import { Member } from '../../shared/member';
|
||||||
import { slideInOutAnimation } from '../../shared/slide-in-out-animation';
|
import { Label } from '../../shared/label';
|
||||||
|
import {NgForm, NgModel} from '@angular/forms';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-team-editor',
|
selector: 'app-team-editor',
|
||||||
@ -13,8 +14,10 @@ import { slideInOutAnimation } from '../../shared/slide-in-out-animation';
|
|||||||
styleUrls: ['./team-editor.component.css']
|
styleUrls: ['./team-editor.component.css']
|
||||||
})
|
})
|
||||||
export class TeamEditorComponent implements OnInit {
|
export class TeamEditorComponent implements OnInit {
|
||||||
|
@ViewChild('labelInput') labelInputElement: ElementRef;
|
||||||
@ViewChild('signumInput') signumInputElement: ElementRef;
|
@ViewChild('signumInput') signumInputElement: ElementRef;
|
||||||
public team: Team;
|
public team: Team;
|
||||||
|
public label: Label = new Label();
|
||||||
public member: Member = new Member();
|
public member: Member = new Member();
|
||||||
|
|
||||||
constructor(private teamService: TeamService,
|
constructor(private teamService: TeamService,
|
||||||
@ -28,6 +31,57 @@ export class TeamEditorComponent implements OnInit {
|
|||||||
this.route.data.subscribe((data: { team: Team }) => this.team = data.team ? data.team : new Team());
|
this.route.data.subscribe((data: { team: Team }) => this.team = data.team ? data.team : new Team());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get labelColors(): Array<string> {
|
||||||
|
return [
|
||||||
|
'red',
|
||||||
|
'orange',
|
||||||
|
'yellow',
|
||||||
|
'olive',
|
||||||
|
'green',
|
||||||
|
'teal',
|
||||||
|
'blue',
|
||||||
|
'violet',
|
||||||
|
'purple',
|
||||||
|
'pink',
|
||||||
|
'brown',
|
||||||
|
'grey',
|
||||||
|
'black',
|
||||||
|
'white'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
get canAddLabel(): boolean {
|
||||||
|
try {
|
||||||
|
return [this.label.name, this.label.color].every(field => field.length !== 0)
|
||||||
|
&& this.team.labels.every(label => label.name !== this.label.name);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public addLabel() {
|
||||||
|
this.team.labels = this.team.labels
|
||||||
|
.concat(Object.assign({}, this.label))
|
||||||
|
.sort((a: Label, b: Label) => a.name < b.name ? -1 : 1);
|
||||||
|
this.label = new Label();
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeLabel(label: Label) {
|
||||||
|
this.team.labels = this.team.labels.filter(teamLabel => teamLabel !== label);
|
||||||
|
}
|
||||||
|
|
||||||
|
public handleLabelEnter(ev: KeyboardEvent) {
|
||||||
|
ev.preventDefault();
|
||||||
|
if (this.canAddLabel) {
|
||||||
|
this.addLabel();
|
||||||
|
this.focusLabelField();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public focusLabelField() {
|
||||||
|
this.labelInputElement.nativeElement.focus();
|
||||||
|
}
|
||||||
|
|
||||||
get canAddMember(): boolean {
|
get canAddMember(): boolean {
|
||||||
try {
|
try {
|
||||||
return [this.member.name, this.member.signum].every(field => field.length !== 0)
|
return [this.member.name, this.member.signum].every(field => field.length !== 0)
|
||||||
@ -62,18 +116,22 @@ export class TeamEditorComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get canSave(): boolean {
|
public canSave(form: NgForm): boolean {
|
||||||
return [
|
return form.valid && [
|
||||||
this.team.name.trim(),
|
this.team.name.trim(),
|
||||||
this.team.members
|
this.team.members
|
||||||
].every(field => field.length > 0);
|
].every(field => field.length > 0) && this.team.filterId > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public saveTeam() {
|
public saveTeam(form: NgForm) {
|
||||||
if (this.canSave) {
|
if (this.canSave(form)) {
|
||||||
this.teamService.persist(this.team).subscribe(
|
this.teamService.persist(this.team).subscribe(
|
||||||
() => this.router.navigate(['/admin/teams'])
|
() => this.router.navigate(['/admin/teams'])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public checkError(fieldModel: NgModel): boolean {
|
||||||
|
return fieldModel.invalid && (fieldModel.dirty || fieldModel.touched);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
src/app/admin/team-resolver.service.ts
Normal file → Executable file
2
src/app/admin/team-resolver.service.ts
Normal file → Executable file
@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
import { Team } from '../shared/team';
|
import { Team } from '../shared/team';
|
||||||
import { environment } from '../../environments/environment';
|
import { environment } from '../../environments/environment';
|
||||||
|
|||||||
9
src/app/app.component.css
Normal file → Executable file
9
src/app/app.component.css
Normal file → Executable file
@ -0,0 +1,9 @@
|
|||||||
|
.pause-indicator {
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 999;
|
||||||
|
background-color: lightgrey;
|
||||||
|
color: red;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
5
src/app/app.component.html
Normal file → Executable file
5
src/app/app.component.html
Normal file → Executable file
@ -1 +1,4 @@
|
|||||||
<router-outlet></router-outlet>
|
<div class="pause-indicator" *ngIf="paused">Slideshow is paused</div>
|
||||||
|
<main [@routerTransition]="getAnimationData(o)">
|
||||||
|
<router-outlet #o="outlet"></router-outlet>
|
||||||
|
</main>
|
||||||
|
|||||||
51
src/app/app.component.ts
Normal file → Executable file
51
src/app/app.component.ts
Normal file → Executable file
@ -1,12 +1,57 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, HostListener, OnInit } from '@angular/core';
|
||||||
import { TimerService } from './shared/service/timer.service';
|
import { TimerService } from './shared/service/timer.service';
|
||||||
|
import { SlideShowService } from './display/slide-show.service';
|
||||||
|
import { Router, RouterOutlet } from '@angular/router';
|
||||||
|
import { slideInOutAnimation } from './shared/slide-in-out-animation';
|
||||||
|
import { AnimationDirection, SettingsService } from './shared/service/settings.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrls: ['./app.component.css']
|
styleUrls: ['./app.component.css'],
|
||||||
|
animations: [slideInOutAnimation],
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnInit {
|
export class AppComponent implements OnInit {
|
||||||
constructor(private timerService: TimerService) {}
|
constructor(private timerService: TimerService,
|
||||||
|
private slideShowService: SlideShowService,
|
||||||
|
private settings: SettingsService,
|
||||||
|
private router: Router) {}
|
||||||
|
|
||||||
public ngOnInit() {}
|
public ngOnInit() {}
|
||||||
|
|
||||||
|
@HostListener('document:keyup', ['$event.key'])
|
||||||
|
private keyPressed(key: string) {
|
||||||
|
if (this.timerService.autoSwitch) {
|
||||||
|
switch (key) {
|
||||||
|
case 'Home':
|
||||||
|
this.router.navigate(['/dashboard']);
|
||||||
|
break;
|
||||||
|
case ' ':
|
||||||
|
this.timerService.togglePause();
|
||||||
|
break;
|
||||||
|
case 'ArrowLeft':
|
||||||
|
this.timerService.pause();
|
||||||
|
this.slideShowService.prevSlide();
|
||||||
|
break;
|
||||||
|
case 'ArrowRight':
|
||||||
|
this.timerService.pause();
|
||||||
|
this.slideShowService.nextSlide();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get paused(): boolean {
|
||||||
|
return this.timerService.paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAnimationData(outlet: RouterOutlet) {
|
||||||
|
return {
|
||||||
|
value: outlet.activatedRouteData.state ? outlet.activatedRouteData.state : false,
|
||||||
|
params: {
|
||||||
|
offsetEnter: this.settings.animationDirection === AnimationDirection.LEFT ? -100 : 100,
|
||||||
|
offsetLeave: this.settings.animationDirection === AnimationDirection.LEFT ? 100 : -100,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
src/app/display/commit-tracker/commit-tracker.component.css
Normal file → Executable file
8
src/app/display/commit-tracker/commit-tracker.component.css
Normal file → Executable file
@ -1,5 +1,11 @@
|
|||||||
:host {
|
:host {
|
||||||
background-color: #444;
|
background-color: #444;
|
||||||
|
position: fixed;
|
||||||
|
display: block;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.label.inprogress {
|
.ui.label.inprogress {
|
||||||
@ -43,6 +49,8 @@
|
|||||||
|
|
||||||
.ui.jira-avatar.image > img {
|
.ui.jira-avatar.image > img {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
max-width: 45px;
|
||||||
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
2
src/app/display/commit-tracker/commit-tracker.component.html
Normal file → Executable file
2
src/app/display/commit-tracker/commit-tracker.component.html
Normal file → Executable file
@ -1,5 +1,5 @@
|
|||||||
<div class="ui main wide-container dark">
|
<div class="ui main wide-container dark">
|
||||||
<table *ngIf="commits?.length" class="ui large padded inverted celled2 table">
|
<table *ngIf="commits?.length" class="ui large padded inverted table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="collapsing"><i class="user icon"></i>Owner</th>
|
<th class="collapsing"><i class="user icon"></i>Owner</th>
|
||||||
|
|||||||
12
src/app/display/commit-tracker/commit-tracker.component.ts
Normal file → Executable file
12
src/app/display/commit-tracker/commit-tracker.component.ts
Normal file → Executable file
@ -1,10 +1,8 @@
|
|||||||
import { Component, HostBinding, OnDestroy, OnInit } from '@angular/core';
|
import {Component, OnDestroy, OnInit} from '@angular/core';
|
||||||
import {Title} from '@angular/platform-browser';
|
import {Title} from '@angular/platform-browser';
|
||||||
import {ActivatedRoute} from '@angular/router';
|
import {ActivatedRoute} from '@angular/router';
|
||||||
import { Subscription } from 'rxjs/Subscription';
|
import {Subscription, timer} from 'rxjs';
|
||||||
import { TimerObservable } from 'rxjs/observable/TimerObservable';
|
|
||||||
|
|
||||||
import { slideInOutAnimation } from '../../shared/slide-in-out-animation';
|
|
||||||
import {CommitTrackerService} from '../../shared/service/commit-tracker.service';
|
import {CommitTrackerService} from '../../shared/service/commit-tracker.service';
|
||||||
import {SettingsService} from '../../shared/service/settings.service';
|
import {SettingsService} from '../../shared/service/settings.service';
|
||||||
import {Commit} from '../../shared/commit';
|
import {Commit} from '../../shared/commit';
|
||||||
@ -20,16 +18,12 @@ const DEFAULT_AVATAR = '/assets/riddler.png';
|
|||||||
selector: 'app-commit-tracker',
|
selector: 'app-commit-tracker',
|
||||||
templateUrl: './commit-tracker.component.html',
|
templateUrl: './commit-tracker.component.html',
|
||||||
styleUrls: ['./commit-tracker.component.css'],
|
styleUrls: ['./commit-tracker.component.css'],
|
||||||
animations: [slideInOutAnimation]
|
|
||||||
})
|
})
|
||||||
export class CommitTrackerComponent implements OnInit, OnDestroy {
|
export class CommitTrackerComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
public CommitStatus = CommitStatus;
|
public CommitStatus = CommitStatus;
|
||||||
private refreshCommitTrackerTimer: Subscription;
|
private refreshCommitTrackerTimer: Subscription;
|
||||||
|
|
||||||
@HostBinding('@slideInOutAnimation')
|
|
||||||
slideIn = false;
|
|
||||||
|
|
||||||
constructor(private commitTrackerService: CommitTrackerService,
|
constructor(private commitTrackerService: CommitTrackerService,
|
||||||
private settings: SettingsService,
|
private settings: SettingsService,
|
||||||
private titleService: Title,
|
private titleService: Title,
|
||||||
@ -40,7 +34,7 @@ export class CommitTrackerComponent implements OnInit, OnDestroy {
|
|||||||
this.titleService.setTitle('Commit-tracker : MTAStv');
|
this.titleService.setTitle('Commit-tracker : MTAStv');
|
||||||
this.route.data.subscribe((data: { commits: Array<Commit> }) => this.commits = data.commits);
|
this.route.data.subscribe((data: { commits: Array<Commit> }) => this.commits = data.commits);
|
||||||
|
|
||||||
const timerCT = TimerObservable.create(TIMER_COMMITTRACKER_REFRESH, TIMER_COMMITTRACKER_REFRESH);
|
const timerCT = timer(TIMER_COMMITTRACKER_REFRESH, TIMER_COMMITTRACKER_REFRESH);
|
||||||
this.refreshCommitTrackerTimer = timerCT.subscribe(() => {
|
this.refreshCommitTrackerTimer = timerCT.subscribe(() => {
|
||||||
this.commitTrackerService.getTeamCommits(this.settings.team.members.map(member => member.signum))
|
this.commitTrackerService.getTeamCommits(this.settings.team.members.map(member => member.signum))
|
||||||
.subscribe(commits => this.commits = commits);
|
.subscribe(commits => this.commits = commits);
|
||||||
|
|||||||
58
src/app/display/display-routing.module.ts
Normal file → Executable file
58
src/app/display/display-routing.module.ts
Normal file → Executable file
@ -9,85 +9,115 @@ import { SlideShowComponent } from './slide-show/slide-show.component';
|
|||||||
import { SlideResolverService } from '../admin/slide-resolver.service';
|
import { SlideResolverService } from '../admin/slide-resolver.service';
|
||||||
import { KanbanBoardComponent } from './kanban-board/kanban-board.component';
|
import { KanbanBoardComponent } from './kanban-board/kanban-board.component';
|
||||||
import { KanbanService } from './shared';
|
import { KanbanService } from './shared';
|
||||||
|
import { WatchersComponent } from './watchers/watchers.component';
|
||||||
|
import { WatcherService } from './shared/watcher.service';
|
||||||
|
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: 'slideshow/:id',
|
path: 'slideshow/:id',
|
||||||
component: SlideShowComponent,
|
component: SlideShowComponent,
|
||||||
// canActivate: [AuthGuardService, RoleGuardService],
|
|
||||||
resolve: {
|
resolve: {
|
||||||
slide: SlideResolverService,
|
slide: SlideResolverService,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
autoSwitchable: false
|
autoSwitchable: false,
|
||||||
|
state: 'slideshow',
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
path: 'slideshow-odd/:id',
|
path: 'slideshow-odd/:id',
|
||||||
component: SlideShowComponent,
|
component: SlideShowComponent,
|
||||||
// canActivate: [AuthGuardService, RoleGuardService],
|
|
||||||
resolve: {
|
resolve: {
|
||||||
slide: SlideResolverService,
|
slide: SlideResolverService,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
autoSwitchable: true
|
autoSwitchable: true,
|
||||||
|
state: 'slideshow-odd',
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
path: 'slideshow-even/:id',
|
path: 'slideshow-even/:id',
|
||||||
component: SlideShowComponent,
|
component: SlideShowComponent,
|
||||||
// canActivate: [AuthGuardService, RoleGuardService],
|
|
||||||
resolve: {
|
resolve: {
|
||||||
slide: SlideResolverService,
|
slide: SlideResolverService,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
autoSwitchable: true
|
autoSwitchable: true,
|
||||||
|
state: 'slideshow-even',
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
path: 'commit-tracker',
|
path: 'commit-tracker',
|
||||||
component: CommitTrackerComponent,
|
component: CommitTrackerComponent,
|
||||||
// canActivate: [AuthGuardService, RoleGuardService],
|
|
||||||
resolve: {
|
resolve: {
|
||||||
commits: CommitTrackerService,
|
commits: CommitTrackerService,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
autoSwitchable: true
|
autoSwitchable: true,
|
||||||
|
state: 'commit-tracker',
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
path: 'commit-tracker-fixed',
|
path: 'commit-tracker-fixed',
|
||||||
component: CommitTrackerComponent,
|
component: CommitTrackerComponent,
|
||||||
// canActivate: [AuthGuardService, RoleGuardService],
|
|
||||||
resolve: {
|
resolve: {
|
||||||
commits: CommitTrackerService,
|
commits: CommitTrackerService,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
autoSwitchable: false,
|
||||||
|
state: 'commit-tracker-fixed',
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
path: 'kanban',
|
path: 'kanban',
|
||||||
component: KanbanBoardComponent,
|
component: KanbanBoardComponent,
|
||||||
// canActivate: [AuthGuardService, RoleGuardService],
|
|
||||||
resolve: {
|
resolve: {
|
||||||
kanbanBoard: KanbanService,
|
kanbanBoard: KanbanService,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
autoSwitchable: true
|
autoSwitchable: true,
|
||||||
|
state: 'kanban',
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
path: 'kanban-fixed',
|
path: 'kanban-fixed',
|
||||||
component: KanbanBoardComponent,
|
component: KanbanBoardComponent,
|
||||||
// canActivate: [AuthGuardService, RoleGuardService],
|
|
||||||
resolve: {
|
resolve: {
|
||||||
kanbanBoard: KanbanService,
|
kanbanBoard: KanbanService,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
autoSwitchable: false
|
autoSwitchable: false,
|
||||||
|
state: 'kanban-fixed',
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
path: 'watchers',
|
||||||
|
component: WatchersComponent,
|
||||||
|
resolve: {
|
||||||
|
watchers: WatcherService,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
autoSwitchable: true,
|
||||||
|
state: 'watchers',
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
path: 'watchers-fixed',
|
||||||
|
component: WatchersComponent,
|
||||||
|
resolve: {
|
||||||
|
watchers: WatcherService,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
autoSwitchable: false,
|
||||||
|
state: 'watchers-fixed',
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
path: 'settings',
|
path: 'settings',
|
||||||
component: SettingsComponent,
|
component: SettingsComponent,
|
||||||
// canActivate: [AuthGuardService, RoleGuardService],
|
|
||||||
resolve: {
|
resolve: {
|
||||||
teams: TeamService,
|
teams: TeamService,
|
||||||
},
|
},
|
||||||
|
data: {
|
||||||
|
autoSwitchable: true,
|
||||||
|
// state: 'settings',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forChild(routes)],
|
imports: [RouterModule.forChild(routes)],
|
||||||
exports: [RouterModule]
|
exports: [RouterModule]
|
||||||
|
|||||||
6
src/app/display/display.module.ts
Normal file → Executable file
6
src/app/display/display.module.ts
Normal file → Executable file
@ -16,6 +16,8 @@ import { PrefixJiraIdPipe } from './shared/prefix-jira-id.pipe';
|
|||||||
import { PriorityColorPipe } from './shared/priority-color.pipe';
|
import { PriorityColorPipe } from './shared/priority-color.pipe';
|
||||||
import { ShortenTextPipe } from './shared/shorten-text.pipe';
|
import { ShortenTextPipe } from './shared/shorten-text.pipe';
|
||||||
import { KanbanService } from './shared';
|
import { KanbanService } from './shared';
|
||||||
|
import { SlideIframeComponent } from './slide-iframe/slide-iframe.component';
|
||||||
|
import { WatchersComponent } from './watchers/watchers.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@ -24,7 +26,7 @@ import { KanbanService } from './shared';
|
|||||||
SuiModule,
|
SuiModule,
|
||||||
DisplayRoutingModule
|
DisplayRoutingModule
|
||||||
],
|
],
|
||||||
exports: [SlideComponent],
|
exports: [SlideComponent, SlideIframeComponent],
|
||||||
declarations: [
|
declarations: [
|
||||||
CommitTrackerComponent,
|
CommitTrackerComponent,
|
||||||
SettingsComponent,
|
SettingsComponent,
|
||||||
@ -37,6 +39,8 @@ import { KanbanService } from './shared';
|
|||||||
PrefixJiraIdPipe,
|
PrefixJiraIdPipe,
|
||||||
PriorityColorPipe,
|
PriorityColorPipe,
|
||||||
ShortenTextPipe,
|
ShortenTextPipe,
|
||||||
|
SlideIframeComponent,
|
||||||
|
WatchersComponent,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
SlideShowService,
|
SlideShowService,
|
||||||
|
|||||||
10
src/app/display/kanban-board/kanban-board.component.css
Normal file → Executable file
10
src/app/display/kanban-board/kanban-board.component.css
Normal file → Executable file
@ -1,7 +1,13 @@
|
|||||||
:host {
|
:host {
|
||||||
display: inline-block;
|
position: fixed;
|
||||||
|
display: block;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
overflow: hidden;
|
overflow-x: hidden;
|
||||||
|
overflow-y: scroll;
|
||||||
background-color: #444;
|
background-color: #444;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|||||||
8
src/app/display/kanban-board/kanban-board.component.html
Normal file → Executable file
8
src/app/display/kanban-board/kanban-board.component.html
Normal file → Executable file
@ -1,18 +1,18 @@
|
|||||||
<div class="ui main fullwide-container">
|
<div class="ui main fullwide-container">
|
||||||
<div class="ui grid">
|
<div class="ui grid">
|
||||||
<div app-kanban-entry-item class="four wide column"
|
<div app-kanban-entry-item class="four wide column"
|
||||||
rowHeading="INBOX"
|
[rowHeading]="backlogLabel"
|
||||||
[kanbanEntries]="kanbanBoard.inbox"></div>
|
[kanbanEntries]="kanbanBoard.inbox"></div>
|
||||||
<div app-kanban-entry-item class="four wide column" [ngClass]="inprogressWipClass"
|
<div app-kanban-entry-item class="four wide column" [ngClass]="inprogressWipClass"
|
||||||
rowHeading="INPROGRESS"
|
[rowHeading]="inProgressLabel"
|
||||||
[wipLimit]="inprogressWipLimit" [wipCount]="inprogressWipCount"
|
[wipLimit]="inprogressWipLimit" [wipCount]="inprogressWipCount"
|
||||||
[kanbanEntries]="kanbanBoard.inProgress"></div>
|
[kanbanEntries]="kanbanBoard.inProgress"></div>
|
||||||
<div app-kanban-entry-item class="four wide column" [ngClass]="verificationWipClass"
|
<div app-kanban-entry-item class="four wide column" [ngClass]="verificationWipClass"
|
||||||
rowHeading="VERIFICATION"
|
[rowHeading]="verificationLabel"
|
||||||
[wipLimit]="verificationWipLimit" [wipCount]="verificationWipCount"
|
[wipLimit]="verificationWipLimit" [wipCount]="verificationWipCount"
|
||||||
[kanbanEntries]="kanbanBoard.verification"></div>
|
[kanbanEntries]="kanbanBoard.verification"></div>
|
||||||
<div app-kanban-entry-item class="four wide column"
|
<div app-kanban-entry-item class="four wide column"
|
||||||
rowHeading="DÖNER"
|
[rowHeading]="doneLabel"
|
||||||
[kanbanEntries]="kanbanBoard.done"></div>
|
[kanbanEntries]="kanbanBoard.done"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
40
src/app/display/kanban-board/kanban-board.component.ts
Normal file → Executable file
40
src/app/display/kanban-board/kanban-board.component.ts
Normal file → Executable file
@ -1,34 +1,28 @@
|
|||||||
import { Component, HostBinding, OnInit } from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {Title} from '@angular/platform-browser';
|
import {Title} from '@angular/platform-browser';
|
||||||
import {ActivatedRoute} from '@angular/router';
|
import {ActivatedRoute} from '@angular/router';
|
||||||
|
|
||||||
import {KanbanBoard, KanbanEntry, KanbanService,} from '../shared';
|
import {KanbanBoard, KanbanEntry, KanbanService,} from '../shared';
|
||||||
import { slideInOutAnimation } from '../../shared/slide-in-out-animation';
|
import {SettingsService} from '../../shared/service/settings.service';
|
||||||
|
|
||||||
const WIP_LIMIT_INPROGRESS = 12;
|
|
||||||
const WIP_LIMIT_VERIFICATION = 8;
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-kanban-board',
|
selector: 'app-kanban-board',
|
||||||
templateUrl: './kanban-board.component.html',
|
templateUrl: './kanban-board.component.html',
|
||||||
styleUrls: ['./kanban-board.component.css'],
|
styleUrls: ['./kanban-board.component.css'],
|
||||||
animations: [slideInOutAnimation]
|
|
||||||
})
|
})
|
||||||
export class KanbanBoardComponent implements OnInit {
|
export class KanbanBoardComponent implements OnInit {
|
||||||
|
|
||||||
@HostBinding('@slideInOutAnimation')
|
|
||||||
slideIn = true;
|
|
||||||
|
|
||||||
constructor(private titleService: Title,
|
constructor(private titleService: Title,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private kanbanService: KanbanService) {
|
private kanbanService: KanbanService,
|
||||||
|
private settings: SettingsService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set page title, and handle preloaded kanbanBoard data
|
* Set page title, and handle preloaded kanbanBoard data
|
||||||
*/
|
*/
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.titleService.setTitle('TaurusXFT : Kanban board');
|
this.titleService.setTitle(`${this.settings.team.name} : Kanban board`);
|
||||||
this.route.data.subscribe((data: {
|
this.route.data.subscribe((data: {
|
||||||
kanbanBoard: KanbanBoard,
|
kanbanBoard: KanbanBoard,
|
||||||
}) => {
|
}) => {
|
||||||
@ -45,7 +39,7 @@ export class KanbanBoardComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get inprogressWipLimit(): number {
|
get inprogressWipLimit(): number {
|
||||||
return WIP_LIMIT_INPROGRESS;
|
return this.settings.team.inprogressColumn.wipLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
get inprogressWipCount(): number {
|
get inprogressWipCount(): number {
|
||||||
@ -64,12 +58,12 @@ export class KanbanBoardComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
get inprogressWipClass() {
|
get inprogressWipClass() {
|
||||||
return {
|
return {
|
||||||
'over-wip': this.inprogressWipCount > WIP_LIMIT_INPROGRESS,
|
'over-wip': this.inprogressWipCount > this.settings.team.inprogressColumn.wipLimit,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
get verificationWipLimit(): number {
|
get verificationWipLimit(): number {
|
||||||
return WIP_LIMIT_VERIFICATION;
|
return this.settings.team.verificationColumn.wipLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
get verificationWipCount(): number {
|
get verificationWipCount(): number {
|
||||||
@ -88,7 +82,23 @@ export class KanbanBoardComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
get verificationWipClass() {
|
get verificationWipClass() {
|
||||||
return {
|
return {
|
||||||
'over-wip': this.verificationWipCount > WIP_LIMIT_VERIFICATION,
|
'over-wip': this.verificationWipCount > this.settings.team.verificationColumn.wipLimit,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get backlogLabel(): string {
|
||||||
|
return this.settings.team.backlogColumn.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
get inProgressLabel(): string {
|
||||||
|
return this.settings.team.inprogressColumn.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
get verificationLabel(): string {
|
||||||
|
return this.settings.team.verificationColumn.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
get doneLabel(): string {
|
||||||
|
return this.settings.team.doneColumn.label;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
src/app/display/kanban-entry-item/kanban-entry-item.component.html
Normal file → Executable file
8
src/app/display/kanban-entry-item/kanban-entry-item.component.html
Normal file → Executable file
@ -10,10 +10,10 @@
|
|||||||
[ngClass]="entryClass(kanbanEntry)">
|
[ngClass]="entryClass(kanbanEntry)">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="task-description">
|
<div class="task-description">
|
||||||
<ng-template [ngIf]="hasLabels(kanbanEntry)">
|
<span *ngIf="kanbanEntry.epicName"
|
||||||
<span *ngFor="let label of kanbanEntry.labels"
|
class="ui mini olive right floated label">{{kanbanEntry.epicName}}</span>
|
||||||
class="ui mini {{labelClass(label)}} right floated label">{{label|uppercase|blockedDays:kanbanEntry.daysBlocked}}</span>
|
<span *ngFor="let label of filteredLabels(kanbanEntry)"
|
||||||
</ng-template>
|
class="ui mini {{labelClass(label)}} right floated label">{{label|blockedDays:kanbanEntry.daysBlocked}}</span>
|
||||||
<span *ngIf="wasBlocked(kanbanEntry)" class="ui mini {{labelClass('blocked')}} right floated label">{{kanbanEntry.daysBlocked}}D</span>
|
<span *ngIf="wasBlocked(kanbanEntry)" class="ui mini {{labelClass('blocked')}} right floated label">{{kanbanEntry.daysBlocked}}D</span>
|
||||||
<div *ngIf="!hasMultiAssignee(kanbanEntry)" class="ui jira-avatar floated image">
|
<div *ngIf="!hasMultiAssignee(kanbanEntry)" class="ui jira-avatar floated image">
|
||||||
<img src="{{avatarUrl(kanbanEntry.assignee?.avatar)}}" [title]="kanbanEntry.assignee?.name">
|
<img src="{{avatarUrl(kanbanEntry.assignee?.avatar)}}" [title]="kanbanEntry.assignee?.name">
|
||||||
|
|||||||
53
src/app/display/kanban-entry-item/kanban-entry-item.component.ts
Normal file → Executable file
53
src/app/display/kanban-entry-item/kanban-entry-item.component.ts
Normal file → Executable file
@ -2,19 +2,10 @@ import { Component, Input } from '@angular/core';
|
|||||||
|
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
import { JiraAssignee, KanbanEntry } from '../shared';
|
import { JiraAssignee, KanbanEntry } from '../shared';
|
||||||
|
import { SettingsService } from '../../shared/service/settings.service';
|
||||||
|
|
||||||
const DEFAULT_AVATAR = '/assets/riddler.png';
|
const DEFAULT_AVATAR = '/assets/riddler.png';
|
||||||
const JIRA_BOARD_BASE_HREF = 'https://jirapducc.mo.ca.am.ericsson.se/browse/';
|
const JIRA_BOARD_BASE_HREF = 'https://cc-jira.rnd.ki.sw.ericsson.se/browse/';
|
||||||
|
|
||||||
const labelColors = {
|
|
||||||
TSP: 'teal',
|
|
||||||
MTAS: 'orange',
|
|
||||||
INTERNAL: 'yellow',
|
|
||||||
TEAM: 'yellow',
|
|
||||||
BLOCKED: 'red',
|
|
||||||
SPIKE: 'purple',
|
|
||||||
EXPEDITE: 'pink',
|
|
||||||
};
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-kanban-entry-item,[app-kanban-entry-item]',
|
selector: 'app-kanban-entry-item,[app-kanban-entry-item]',
|
||||||
@ -27,8 +18,7 @@ export class KanbanEntryItemComponent {
|
|||||||
@Input() wipLimit = 0;
|
@Input() wipLimit = 0;
|
||||||
@Input() wipCount = 0;
|
@Input() wipCount = 0;
|
||||||
|
|
||||||
constructor() {
|
constructor(private settingService: SettingsService) {}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the full url of the assignee avatar,
|
* Returns the full url of the assignee avatar,
|
||||||
@ -38,17 +28,9 @@ export class KanbanEntryItemComponent {
|
|||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
public avatarUrl(avatarPath: string): string {
|
public avatarUrl(avatarPath: string): string {
|
||||||
return environment.apiUrl + (avatarPath ? avatarPath : DEFAULT_AVATAR);
|
return environment.apiUrl + (avatarPath
|
||||||
}
|
? avatarPath
|
||||||
|
: DEFAULT_AVATAR);
|
||||||
/**
|
|
||||||
* Returns true if issue has any labels attached
|
|
||||||
*
|
|
||||||
* @param {KanbanEntry} entry
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
public hasLabels(entry: KanbanEntry): boolean {
|
|
||||||
return entry.labels.length > 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,12 +40,17 @@ export class KanbanEntryItemComponent {
|
|||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
public labelClass(label: string): string {
|
public labelClass(label: string): string {
|
||||||
try {
|
if (this.settingService.team.labels) {
|
||||||
return labelColors[label.toUpperCase()];
|
const color = this.settingService.team.labels.find(
|
||||||
} catch (e) {
|
teamLabel => teamLabel.name.toLocaleLowerCase() === label.toLocaleLowerCase()
|
||||||
return 'white';
|
).color;
|
||||||
|
|
||||||
|
if (color !== null) {
|
||||||
|
return color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return 'white';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add 'bottom-separator' class to every item that is the last of its priority type
|
* Add 'bottom-separator' class to every item that is the last of its priority type
|
||||||
@ -77,6 +64,15 @@ export class KanbanEntryItemComponent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public filteredLabels(kanbanEntry: KanbanEntry): Array<string> {
|
||||||
|
if (this.settingService.team.labels) {
|
||||||
|
return kanbanEntry.labels.filter(entryLabel => this.settingService.team.labels.some(
|
||||||
|
teamLabel => teamLabel.name.toLocaleLowerCase() === entryLabel.toLocaleLowerCase()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate jira issue href
|
* Generate jira issue href
|
||||||
*
|
*
|
||||||
@ -119,4 +115,5 @@ export class KanbanEntryItemComponent {
|
|||||||
public getAssignees(kanbanEntry: KanbanEntry): Array<JiraAssignee> {
|
public getAssignees(kanbanEntry: KanbanEntry): Array<JiraAssignee> {
|
||||||
return [].concat([kanbanEntry.assignee], kanbanEntry.additionalAssignees);
|
return [].concat([kanbanEntry.assignee], kanbanEntry.additionalAssignees);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
2
src/app/display/settings/settings.component.html
Normal file → Executable file
2
src/app/display/settings/settings.component.html
Normal file → Executable file
@ -18,7 +18,7 @@
|
|||||||
<div class="four wide field">
|
<div class="four wide field">
|
||||||
<label for="slide_interval">Slide duration: {{slideInterval}}ms</label>
|
<label for="slide_interval">Slide duration: {{slideInterval}}ms</label>
|
||||||
<input id="slide_interval" name="slide_interval"
|
<input id="slide_interval" name="slide_interval"
|
||||||
type="range" min="1000" max="120000" step="100"
|
type="range" min="5000" max="120000" step="250"
|
||||||
[(ngModel)]="slideInterval">
|
[(ngModel)]="slideInterval">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
10
src/app/display/settings/settings.component.ts
Normal file → Executable file
10
src/app/display/settings/settings.component.ts
Normal file → Executable file
@ -15,20 +15,20 @@ export class SettingsComponent implements OnInit {
|
|||||||
public slideInterval: number;
|
public slideInterval: number;
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute,
|
constructor(private route: ActivatedRoute,
|
||||||
private settingsService: SettingsService,
|
private settings: SettingsService,
|
||||||
private router: Router) {}
|
private router: Router) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.route.data.subscribe((data: {teams: Array<Team>}) => {
|
this.route.data.subscribe((data: {teams: Array<Team>}) => {
|
||||||
this.teams = data.teams;
|
this.teams = data.teams;
|
||||||
this.selectedTeam = this.teams.find(team => team.id === this.settingsService.team.id);
|
this.selectedTeam = this.teams.find(team => team.id === this.settings.team.id);
|
||||||
this.slideInterval = this.settingsService.slideInterval;
|
this.slideInterval = this.settings.slideInterval;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public saveSettings() {
|
public saveSettings() {
|
||||||
this.settingsService.team = this.selectedTeam;
|
this.settings.team = this.selectedTeam;
|
||||||
this.settingsService.slideInterval = this.slideInterval;
|
this.settings.slideInterval = this.slideInterval;
|
||||||
this.router.navigate(['/admin']);
|
this.router.navigate(['/admin']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
src/app/display/shared/kanban-board.model.ts
Normal file → Executable file
2
src/app/display/shared/kanban-board.model.ts
Normal file → Executable file
@ -1,4 +1,4 @@
|
|||||||
import {KanbanEntry} from "./kanban-entry.model";
|
import {KanbanEntry} from './kanban-entry.model';
|
||||||
|
|
||||||
export class KanbanBoard {
|
export class KanbanBoard {
|
||||||
public inbox: Array<KanbanEntry>;
|
public inbox: Array<KanbanEntry>;
|
||||||
|
|||||||
7
src/app/display/shared/kanban-entry.model.ts
Normal file → Executable file
7
src/app/display/shared/kanban-entry.model.ts
Normal file → Executable file
@ -1,12 +1,13 @@
|
|||||||
import {JiraIssueType} from "./jira-issue-type.model";
|
import {JiraIssueType} from './jira-issue-type.model';
|
||||||
import {JiraStatus} from "./jira-status.model";
|
import {JiraStatus} from './jira-status.model';
|
||||||
import {JiraAssignee} from "./jira-assignee.model";
|
import {JiraAssignee} from './jira-assignee.model';
|
||||||
|
|
||||||
export class KanbanEntry {
|
export class KanbanEntry {
|
||||||
public id: number;
|
public id: number;
|
||||||
public key: string;
|
public key: string;
|
||||||
public summary: string;
|
public summary: string;
|
||||||
public issueType: JiraIssueType;
|
public issueType: JiraIssueType;
|
||||||
|
public epicName: string;
|
||||||
public status: JiraStatus;
|
public status: JiraStatus;
|
||||||
public assignee: JiraAssignee;
|
public assignee: JiraAssignee;
|
||||||
public additionalAssignees: Array<JiraAssignee> = [];
|
public additionalAssignees: Array<JiraAssignee> = [];
|
||||||
|
|||||||
14
src/app/display/shared/kanban.service.ts
Normal file → Executable file
14
src/app/display/shared/kanban.service.ts
Normal file → Executable file
@ -1,10 +1,13 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { ActivatedRouteSnapshot } from '@angular/router';
|
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
import { KanbanBoard } from './kanban-board.model';
|
import { KanbanBoard } from './kanban-board.model';
|
||||||
|
import { SettingsService } from '../../shared/service/settings.service';
|
||||||
|
import { TeamService } from '../../shared/service/team.service';
|
||||||
|
import { flatMap } from 'rxjs/operators';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class KanbanService {
|
export class KanbanService {
|
||||||
@ -12,16 +15,21 @@ export class KanbanService {
|
|||||||
|
|
||||||
private cachedKanbanBoard: KanbanBoard = new KanbanBoard();
|
private cachedKanbanBoard: KanbanBoard = new KanbanBoard();
|
||||||
|
|
||||||
constructor(private httpService: HttpClient) {
|
constructor(private httpService: HttpClient,
|
||||||
|
private teamService: TeamService,
|
||||||
|
private settingService: SettingsService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an observable instance to the kanban board api
|
* Returns an observable instance to the kanban board api
|
||||||
|
* Reloads team data before, to refresh team config
|
||||||
*
|
*
|
||||||
* @returns {Observable<KanbanBoard>}
|
* @returns {Observable<KanbanBoard>}
|
||||||
*/
|
*/
|
||||||
public getList(): Observable<KanbanBoard> {
|
public getList(): Observable<KanbanBoard> {
|
||||||
return this.httpService.get<KanbanBoard>(this.url);
|
return this.teamService.get(this.settingService.team.id).pipe(
|
||||||
|
flatMap(() => this.httpService.get<KanbanBoard>(`${this.url}/${this.settingService.team.id}`))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
13
src/app/display/shared/watched-issue.model.ts
Executable file
13
src/app/display/shared/watched-issue.model.ts
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
export class WatchedIssue {
|
||||||
|
public issue = '';
|
||||||
|
public summary = '';
|
||||||
|
public assignee = '';
|
||||||
|
public comment: WatchedIssueComment;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WatchedIssueComment {
|
||||||
|
public signum = '';
|
||||||
|
public name = '';
|
||||||
|
public content = '';
|
||||||
|
public date = '';
|
||||||
|
}
|
||||||
15
src/app/display/shared/watcher.service.spec.ts
Normal file
15
src/app/display/shared/watcher.service.spec.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { TestBed, inject } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { WatcherService } from './watcher.service';
|
||||||
|
|
||||||
|
describe('WatcherService', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [WatcherService]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', inject([WatcherService], (service: WatcherService) => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
}));
|
||||||
|
});
|
||||||
62
src/app/display/shared/watcher.service.ts
Executable file
62
src/app/display/shared/watcher.service.ts
Executable file
@ -0,0 +1,62 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import { environment } from '../../../environments/environment';
|
||||||
|
import { KanbanBoard } from './kanban-board.model';
|
||||||
|
import { SettingsService } from '../../shared/service/settings.service';
|
||||||
|
import { TeamService } from '../../shared/service/team.service';
|
||||||
|
import { flatMap } from 'rxjs/operators';
|
||||||
|
import {WatchedIssue} from './watched-issue.model';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class WatcherService {
|
||||||
|
private url = environment.apiUrl + '/api/watched';
|
||||||
|
|
||||||
|
private cachedWatchers: Array<WatchedIssue> = [];
|
||||||
|
|
||||||
|
constructor(private httpService: HttpClient,
|
||||||
|
private teamService: TeamService,
|
||||||
|
private settingService: SettingsService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an observable instance to the kanban board api
|
||||||
|
* Reloads team data before, to refresh team config
|
||||||
|
*
|
||||||
|
* @returns {Observable<KanbanBoard>}
|
||||||
|
*/
|
||||||
|
public getList(): Observable<Array<WatchedIssue>> {
|
||||||
|
return this.teamService.get(this.settingService.team.id).pipe(
|
||||||
|
flatMap(() => this.httpService.get<Array<WatchedIssue>>(`${this.url}/${this.settingService.team.id}`))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route preload resolver
|
||||||
|
*
|
||||||
|
* @param {ActivatedRouteSnapshot} route
|
||||||
|
* @returns {Promise<KanbanBoard>}
|
||||||
|
*/
|
||||||
|
public resolve(route: ActivatedRouteSnapshot): Promise<Array<WatchedIssue> | boolean> {
|
||||||
|
return this.getList().toPromise().then(result => result ? result : false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload the board
|
||||||
|
*/
|
||||||
|
public reload() {
|
||||||
|
this.getList().subscribe(result => this.cachedWatchers = result);
|
||||||
|
}
|
||||||
|
|
||||||
|
get watchers(): Array<WatchedIssue> {
|
||||||
|
return this.cachedWatchers;
|
||||||
|
}
|
||||||
|
|
||||||
|
set watchers(kanbanBoard: Array<WatchedIssue>) {
|
||||||
|
this.cachedWatchers = kanbanBoard;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/app/display/slide-iframe/slide-iframe.component.css
Executable file
21
src/app/display/slide-iframe/slide-iframe.component.css
Executable file
@ -0,0 +1,21 @@
|
|||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 3px;
|
||||||
|
background: #222;
|
||||||
|
}
|
||||||
|
:host.preview {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
1
src/app/display/slide-iframe/slide-iframe.component.html
Executable file
1
src/app/display/slide-iframe/slide-iframe.component.html
Executable file
@ -0,0 +1 @@
|
|||||||
|
<iframe [src]="sanitizedUrl"></iframe>
|
||||||
25
src/app/display/slide-iframe/slide-iframe.component.spec.ts
Normal file
25
src/app/display/slide-iframe/slide-iframe.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { SlideIframeComponent } from './slide-iframe.component';
|
||||||
|
|
||||||
|
describe('SlideIframeComponent', () => {
|
||||||
|
let component: SlideIframeComponent;
|
||||||
|
let fixture: ComponentFixture<SlideIframeComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ SlideIframeComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(SlideIframeComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
29
src/app/display/slide-iframe/slide-iframe.component.ts
Executable file
29
src/app/display/slide-iframe/slide-iframe.component.ts
Executable file
@ -0,0 +1,29 @@
|
|||||||
|
import {Component, EventEmitter, HostListener, Input, OnInit, Output} from '@angular/core';
|
||||||
|
import {DomSanitizer, SafeResourceUrl} from '@angular/platform-browser';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-slide-iframe',
|
||||||
|
templateUrl: './slide-iframe.component.html',
|
||||||
|
styleUrls: ['./slide-iframe.component.css']
|
||||||
|
})
|
||||||
|
export class SlideIframeComponent implements OnInit {
|
||||||
|
@Input() data = '';
|
||||||
|
@Input() preview = false;
|
||||||
|
|
||||||
|
@Input() visible = true;
|
||||||
|
@Output() visibleChange = new EventEmitter();
|
||||||
|
|
||||||
|
constructor(private sanitizer: DomSanitizer) {}
|
||||||
|
ngOnInit() {}
|
||||||
|
|
||||||
|
@HostListener('click')
|
||||||
|
public hide() {
|
||||||
|
if (this.preview) {
|
||||||
|
this.visibleChange.emit(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get sanitizedUrl(): SafeResourceUrl {
|
||||||
|
return this.sanitizer.bypassSecurityTrustResourceUrl(this.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
88
src/app/display/slide-show.service.ts
Normal file → Executable file
88
src/app/display/slide-show.service.ts
Normal file → Executable file
@ -1,46 +1,86 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Slide } from '../shared/slide';
|
import { Slide, SlideVisibility } from '../shared/slide';
|
||||||
import { SlideService } from '../shared/service/slide.service';
|
import { SlideService } from '../shared/service/slide.service';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { SettingsService } from '../shared/service/settings.service';
|
import { AnimationDirection, SettingsService } from '../shared/service/settings.service';
|
||||||
|
import { TwoWayLinkedList } from '../shared/two-way-linked-list';
|
||||||
|
import { SlideWrapper, WrappedType } from '../shared/slide-wrapper';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SlideShowService {
|
export class SlideShowService {
|
||||||
|
|
||||||
private oddEven = false;
|
private oddEven = false;
|
||||||
private currentSlideIndex = -1;
|
private cachedSlides: TwoWayLinkedList<SlideWrapper> = new TwoWayLinkedList<SlideWrapper>();
|
||||||
private slides: Array<Slide> = [];
|
|
||||||
|
|
||||||
constructor(private slideService: SlideService,
|
constructor(private slideService: SlideService,
|
||||||
private settingsService: SettingsService,
|
private settings: SettingsService,
|
||||||
private router: Router) {
|
private router: Router) {
|
||||||
this.reloadSlides();
|
this.reloadSlides();
|
||||||
}
|
}
|
||||||
|
|
||||||
public nextSlide() {
|
public startWithFirstSlide() {
|
||||||
if (this.currentSlideIndex === this.slides.length - 1) {
|
this.settings.animationDirection = AnimationDirection.RIGHT;
|
||||||
// this.currentSlideIndex++;
|
this.reloadSlides(() => this.switchToWrappedSlide(this.cachedSlides.first));
|
||||||
// this.router.navigate(['/kanban']);
|
}
|
||||||
// } else if (this.currentSlideIndex === this.slides.length) {
|
|
||||||
this.currentSlideIndex = -1;
|
public prevSlide() {
|
||||||
this.reloadSlides();
|
this.settings.animationDirection = AnimationDirection.LEFT;
|
||||||
this.router.navigate(['/commit-tracker']);
|
if (this.cachedSlides.isFirst()) {
|
||||||
|
this.reloadSlides(() => this.switchToWrappedSlide(this.cachedSlides.last));
|
||||||
} else {
|
} else {
|
||||||
this.oddEven = !this.oddEven;
|
const prevSlide = this.cachedSlides.prev();
|
||||||
this.currentSlideIndex++;
|
this.switchToWrappedSlide(prevSlide);
|
||||||
this.router.navigate([
|
|
||||||
this.oddEven ? '/slideshow-odd' : '/slideshow-even',
|
|
||||||
this.slides[this.currentSlideIndex].id
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private reloadSlides() {
|
public nextSlide() {
|
||||||
const team = this.settingsService.team;
|
this.settings.animationDirection = AnimationDirection.RIGHT;
|
||||||
|
if (this.cachedSlides.isLast()) {
|
||||||
|
this.reloadSlides(() => this.switchToWrappedSlide(this.cachedSlides.first));
|
||||||
|
} else {
|
||||||
|
const nextSlide = this.cachedSlides.next();
|
||||||
|
this.switchToWrappedSlide(nextSlide);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private switchToWrappedSlide(wrappedSlide: SlideWrapper) {
|
||||||
|
if (WrappedType.BUILTIN === wrappedSlide.type) {
|
||||||
|
this.router.navigate([wrappedSlide.slideRoute]);
|
||||||
|
} else if (WrappedType.USER === wrappedSlide.type) {
|
||||||
|
this.oddEven = !this.oddEven;
|
||||||
|
this.router.navigate([
|
||||||
|
this.oddEven ? '/slideshow-odd' : '/slideshow-even',
|
||||||
|
wrappedSlide.slideData.id
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
throw Error(`Unknown slide type: ${wrappedSlide.type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private reloadSlides(onReloadFinish: () => void = null) {
|
||||||
this.slideService.list().subscribe(
|
this.slideService.list().subscribe(
|
||||||
slides => this.slides = slides.filter(
|
slides => {
|
||||||
slide => slide.team === null || slide.team.id === team.id
|
this.cachedSlides.clear();
|
||||||
)
|
slides.filter(
|
||||||
|
(slide: Slide) => slide.isVisible && (slide.visibility === SlideVisibility.Public || slide.teams.some(
|
||||||
|
s => s.id === this.settings.team.id
|
||||||
|
))
|
||||||
|
).map(slide => this.cachedSlides.push(new SlideWrapper(WrappedType.USER, slide)));
|
||||||
|
|
||||||
|
if (this.settings.team.kanbanEnabled) {
|
||||||
|
this.cachedSlides.push(new SlideWrapper(WrappedType.BUILTIN, '/kanban'));
|
||||||
|
}
|
||||||
|
if (this.settings.team.commitTrackerEnabled) {
|
||||||
|
this.cachedSlides.push(new SlideWrapper(WrappedType.BUILTIN, '/commit-tracker'));
|
||||||
|
}
|
||||||
|
if (this.settings.team.watchedEnabled) {
|
||||||
|
this.cachedSlides.push(new SlideWrapper(WrappedType.BUILTIN, '/watchers'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null !== onReloadFinish) {
|
||||||
|
onReloadFinish();
|
||||||
|
}
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
src/app/display/slide-show/slide-show.component.css
Normal file → Executable file
8
src/app/display/slide-show/slide-show.component.css
Normal file → Executable file
@ -0,0 +1,8 @@
|
|||||||
|
:host {
|
||||||
|
position: fixed;
|
||||||
|
display: block;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
3
src/app/display/slide-show/slide-show.component.html
Normal file → Executable file
3
src/app/display/slide-show/slide-show.component.html
Normal file → Executable file
@ -1 +1,2 @@
|
|||||||
<app-slide [data]="renderedSlide"></app-slide>
|
<app-slide *ngIf="isMarkdown" [data]="renderedSlide"></app-slide>
|
||||||
|
<app-slide-iframe *ngIf="isIframe" [data]="slide.slideUrl"></app-slide-iframe>
|
||||||
|
|||||||
17
src/app/display/slide-show/slide-show.component.ts
Normal file → Executable file
17
src/app/display/slide-show/slide-show.component.ts
Normal file → Executable file
@ -1,24 +1,19 @@
|
|||||||
import { Component, HostBinding, OnInit } from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {ActivatedRoute} from '@angular/router';
|
import {ActivatedRoute} from '@angular/router';
|
||||||
import {Title} from '@angular/platform-browser';
|
import {Title} from '@angular/platform-browser';
|
||||||
|
|
||||||
import * as marked from 'marked';
|
import * as marked from 'marked';
|
||||||
import { slideInOutAnimation } from '../../shared/slide-in-out-animation';
|
import {Slide, SlideType} from '../../shared/slide';
|
||||||
import { Slide } from '../../shared/slide';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-slide-show',
|
selector: 'app-slide-show',
|
||||||
templateUrl: './slide-show.component.html',
|
templateUrl: './slide-show.component.html',
|
||||||
styleUrls: ['./slide-show.component.css'],
|
styleUrls: ['./slide-show.component.css'],
|
||||||
animations: [slideInOutAnimation]
|
|
||||||
})
|
})
|
||||||
export class SlideShowComponent implements OnInit {
|
export class SlideShowComponent implements OnInit {
|
||||||
private md;
|
private md;
|
||||||
public slide: Slide;
|
public slide: Slide;
|
||||||
|
|
||||||
@HostBinding('@slideInOutAnimation')
|
|
||||||
slideIn = true;
|
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute,
|
constructor(private route: ActivatedRoute,
|
||||||
private titleService: Title) {
|
private titleService: Title) {
|
||||||
this.md = marked.setOptions({});
|
this.md = marked.setOptions({});
|
||||||
@ -34,4 +29,12 @@ export class SlideShowComponent implements OnInit {
|
|||||||
get renderedSlide(): String {
|
get renderedSlide(): String {
|
||||||
return this.md.parse(this.slide.slideData);
|
return this.md.parse(this.slide.slideData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isMarkdown(): boolean {
|
||||||
|
return this.slide.type === SlideType.MarkDown;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isIframe(): boolean {
|
||||||
|
return this.slide.type === SlideType.IFrame;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
src/app/display/slide/slide.component.ts
Normal file → Executable file
1
src/app/display/slide/slide.component.ts
Normal file → Executable file
@ -12,7 +12,6 @@ export class SlideComponent implements OnInit {
|
|||||||
@Input() visible = true;
|
@Input() visible = true;
|
||||||
@Output() visibleChange = new EventEmitter();
|
@Output() visibleChange = new EventEmitter();
|
||||||
|
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
ngOnInit() {}
|
ngOnInit() {}
|
||||||
|
|
||||||
|
|||||||
58
src/app/display/watchers/watchers.component.css
Executable file
58
src/app/display/watchers/watchers.component.css
Executable file
@ -0,0 +1,58 @@
|
|||||||
|
:host {
|
||||||
|
background-color: #444;
|
||||||
|
position: fixed;
|
||||||
|
display: block;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* avatar */
|
||||||
|
.ui.jira-avatar.image {
|
||||||
|
width: 45px;
|
||||||
|
height: auto;
|
||||||
|
/*font-size: 1em;*/
|
||||||
|
margin-right: 4px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.jira-avatar.image > img {
|
||||||
|
border-radius: 4px;
|
||||||
|
max-width: 45px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* items */
|
||||||
|
.ui.items .item .header {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.items .item .meta {
|
||||||
|
color: lightgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* comments */
|
||||||
|
.ui.comments .comment .metadata {
|
||||||
|
color: lightgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.comments .comment .text {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.comments .comment .author {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* header */
|
||||||
|
h1.massive {
|
||||||
|
font-size: 72px;
|
||||||
|
position: absolute;
|
||||||
|
top: calc(50% - 157px);
|
||||||
|
left: calc(50% - 541px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.comments {
|
||||||
|
max-width: initial;
|
||||||
|
}
|
||||||
48
src/app/display/watchers/watchers.component.html
Executable file
48
src/app/display/watchers/watchers.component.html
Executable file
@ -0,0 +1,48 @@
|
|||||||
|
<div class="ui main wide-container dark">
|
||||||
|
<table class="ui large padded inverted celled2 table" *ngIf="watchers.length">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="collapsing"><i class="user icon"></i></th>
|
||||||
|
<th><i class="tasks icon"></i>JIRA</th>
|
||||||
|
<th><i class="envelope open outline icon"></i>Last comment</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let issue of watchers">
|
||||||
|
<td class="collapsing">
|
||||||
|
<div class="ui jira-avatar image">
|
||||||
|
<img src="{{avatarUrl(issue.assignee)}}" [title]="issue.assignee">
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="five wide">
|
||||||
|
<div class="ui items">
|
||||||
|
<div class="item">
|
||||||
|
<div class="content">
|
||||||
|
<a class="header">{{issue.issue}}</a>
|
||||||
|
<div class="meta">{{issue.summary}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="ui fluid comments">
|
||||||
|
<div class="comment">
|
||||||
|
<a class="avatar"><img src="{{avatarUrl(issue.comment.signum)}}"></a>
|
||||||
|
<div class="content">
|
||||||
|
<span class="author">{{issue.comment.name}}</span>
|
||||||
|
<div class="metadata"><span class="date">{{issue.comment.date}}</span></div>
|
||||||
|
<div class="text">{{issue.comment.content|shortenText:500}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div *ngIf="!watchers.length">
|
||||||
|
<h1 class="ui massive green center aligned icon header">
|
||||||
|
<i class="ui massive check circle outline icon"></i>
|
||||||
|
<div class="content">No watched item needs attention.</div>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
25
src/app/display/watchers/watchers.component.spec.ts
Normal file
25
src/app/display/watchers/watchers.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { WatchersComponent } from './watchers.component';
|
||||||
|
|
||||||
|
describe('WatchersComponent', () => {
|
||||||
|
let component: WatchersComponent;
|
||||||
|
let fixture: ComponentFixture<WatchersComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ WatchersComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(WatchersComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
43
src/app/display/watchers/watchers.component.ts
Executable file
43
src/app/display/watchers/watchers.component.ts
Executable file
@ -0,0 +1,43 @@
|
|||||||
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {Title} from '@angular/platform-browser';
|
||||||
|
import {ActivatedRoute} from '@angular/router';
|
||||||
|
import {SettingsService} from '../../shared/service/settings.service';
|
||||||
|
import {WatcherService} from '../shared/watcher.service';
|
||||||
|
import {WatchedIssue} from '../shared/watched-issue.model';
|
||||||
|
import {environment} from '../../../environments/environment';
|
||||||
|
|
||||||
|
const DEFAULT_AVATAR = '/assets/riddler.png';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-watchers',
|
||||||
|
templateUrl: './watchers.component.html',
|
||||||
|
styleUrls: ['./watchers.component.css'],
|
||||||
|
})
|
||||||
|
export class WatchersComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor(private titleService: Title,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private watcherService: WatcherService,
|
||||||
|
private settingService: SettingsService) { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.titleService.setTitle(`${this.settingService.team.name} : Watched issue activity`);
|
||||||
|
this.route.data.subscribe((data: {
|
||||||
|
watchers: Array<WatchedIssue>,
|
||||||
|
}) => {
|
||||||
|
this.watchers = data.watchers;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get watchers(): Array<WatchedIssue> {
|
||||||
|
return this.watcherService.watchers;
|
||||||
|
}
|
||||||
|
|
||||||
|
set watchers(watchers: Array<WatchedIssue>) {
|
||||||
|
this.watcherService.watchers = watchers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public avatarUrl(signum: string): string {
|
||||||
|
return environment.apiUrl + (signum ? `/avatars/${signum}` : DEFAULT_AVATAR);
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/app/shared/kanban-column.ts
Normal file
5
src/app/shared/kanban-column.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export class KanbanColumn {
|
||||||
|
jiraStatusName = '';
|
||||||
|
label = '';
|
||||||
|
wipLimit = 0;
|
||||||
|
}
|
||||||
4
src/app/shared/label.ts
Executable file
4
src/app/shared/label.ts
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
export class Label {
|
||||||
|
name: string;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
2
src/app/shared/service/commit-tracker.service.ts
Normal file → Executable file
2
src/app/shared/service/commit-tracker.service.ts
Normal file → Executable file
@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
import { Commit } from '../commit';
|
import { Commit } from '../commit';
|
||||||
|
|||||||
12
src/app/shared/service/self-updater.service.ts
Normal file → Executable file
12
src/app/shared/service/self-updater.service.ts
Normal file → Executable file
@ -1,12 +1,12 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Observable } from 'rxjs/Observable';
|
|
||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SelfUpdaterService {
|
export class SelfUpdaterService {
|
||||||
|
|
||||||
private appRevision: number = 0;
|
private appRevision = 0;
|
||||||
private initFailed = false;
|
private initFailed = false;
|
||||||
|
|
||||||
constructor(private httpClient: HttpClient,
|
constructor(private httpClient: HttpClient,
|
||||||
@ -16,7 +16,7 @@ export class SelfUpdaterService {
|
|||||||
() => {
|
() => {
|
||||||
console.log(
|
console.log(
|
||||||
'%c Couldn\'t load initial revision data from server. Self update disabled.',
|
'%c Couldn\'t load initial revision data from server. Self update disabled.',
|
||||||
'background: #222; color: #bada55;'
|
'background: #222; color: #FFC300;'
|
||||||
);
|
);
|
||||||
this.initFailed = true;
|
this.initFailed = true;
|
||||||
}
|
}
|
||||||
@ -31,7 +31,11 @@ export class SelfUpdaterService {
|
|||||||
if (!this.initFailed) {
|
if (!this.initFailed) {
|
||||||
this.getDeployedRevision().subscribe(revision => {
|
this.getDeployedRevision().subscribe(revision => {
|
||||||
if (revision > this.appRevision) {
|
if (revision > this.appRevision) {
|
||||||
this.locationService.go('/');
|
console.log(
|
||||||
|
`%c Version change detected (${this.appRevision}=>${revision}), reloading app.`,
|
||||||
|
'background: #222; color: #BADA55;'
|
||||||
|
);
|
||||||
|
window.location.reload(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/app/shared/service/settings.service.ts
Normal file → Executable file
10
src/app/shared/service/settings.service.ts
Normal file → Executable file
@ -1,6 +1,5 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable, Subject } from 'rxjs';
|
||||||
import { Subject } from 'rxjs/Subject';
|
|
||||||
import { Team } from '../team';
|
import { Team } from '../team';
|
||||||
|
|
||||||
const DEFAULT_SLIDE_INTERVAL = 30000;
|
const DEFAULT_SLIDE_INTERVAL = 30000;
|
||||||
@ -14,6 +13,8 @@ export class SettingsService {
|
|||||||
private teamSubject: Subject<Team> = new Subject<Team>();
|
private teamSubject: Subject<Team> = new Subject<Team>();
|
||||||
private intervalSubject: Subject<number> = new Subject<number>();
|
private intervalSubject: Subject<number> = new Subject<number>();
|
||||||
|
|
||||||
|
public animationDirection: AnimationDirection = AnimationDirection.RIGHT;
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
get team(): Team {
|
get team(): Team {
|
||||||
@ -52,3 +53,8 @@ export class SettingsService {
|
|||||||
return this.teamSubject.asObservable();
|
return this.teamSubject.asObservable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum AnimationDirection {
|
||||||
|
LEFT,
|
||||||
|
RIGHT,
|
||||||
|
}
|
||||||
|
|||||||
18
src/app/shared/service/slide.service.ts
Normal file → Executable file
18
src/app/shared/service/slide.service.ts
Normal file → Executable file
@ -1,10 +1,13 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
import { Slide } from '../slide';
|
import { Slide } from '../slide';
|
||||||
|
import { TeamService } from './team.service';
|
||||||
|
import { SettingsService } from './settings.service';
|
||||||
|
import { flatMap } from 'rxjs/operators';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SlideService implements Resolve<Array<Slide>>{
|
export class SlideService implements Resolve<Array<Slide>>{
|
||||||
@ -13,14 +16,14 @@ export class SlideService implements Resolve<Array<Slide>>{
|
|||||||
private apiEndPointPosition = environment.apiUrl + '/api/slide-position';
|
private apiEndPointPosition = environment.apiUrl + '/api/slide-position';
|
||||||
private cachedSlides: Array<Slide> = [];
|
private cachedSlides: Array<Slide> = [];
|
||||||
|
|
||||||
constructor(private httpClient: HttpClient) {}
|
constructor(private httpClient: HttpClient,
|
||||||
|
private teamService: TeamService,
|
||||||
|
private settings: SettingsService) {}
|
||||||
|
|
||||||
private static prepareSlideData(slide: Slide) {
|
private static prepareSlideData(slide: Slide) {
|
||||||
const slideToSave = <any>Object.assign({}, slide);
|
const slideToSave = <any>Object.assign({}, slide);
|
||||||
try {
|
try {
|
||||||
slideToSave.team = slideToSave.team.id === null
|
slideToSave.teams = slide.teams.map(team => team.id);
|
||||||
? null
|
|
||||||
: slideToSave.team.id;
|
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
return slideToSave;
|
return slideToSave;
|
||||||
}
|
}
|
||||||
@ -30,6 +33,11 @@ export class SlideService implements Resolve<Array<Slide>>{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public list(): Observable<Array<Slide>> {
|
public list(): Observable<Array<Slide>> {
|
||||||
|
if (this.settings.team.id) {
|
||||||
|
return this.teamService.get(this.settings.team.id).pipe(
|
||||||
|
flatMap( () => this.httpClient.get<Array<Slide>>(this.apiEndPoint))
|
||||||
|
);
|
||||||
|
}
|
||||||
return this.httpClient.get<Array<Slide>>(this.apiEndPoint);
|
return this.httpClient.get<Array<Slide>>(this.apiEndPoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
20
src/app/shared/service/team.service.ts
Normal file → Executable file
20
src/app/shared/service/team.service.ts
Normal file → Executable file
@ -1,10 +1,12 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
import { Team } from '../team';
|
import { Team } from '../team';
|
||||||
|
import {SettingsService} from './settings.service';
|
||||||
|
import {map} from 'rxjs/operators';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TeamService implements Resolve<Array<Team>> {
|
export class TeamService implements Resolve<Array<Team>> {
|
||||||
@ -12,7 +14,8 @@ export class TeamService implements Resolve<Array<Team>> {
|
|||||||
private apiEndPoint = environment.apiUrl + '/api/team';
|
private apiEndPoint = environment.apiUrl + '/api/team';
|
||||||
private cachedTeams: Array<Team> = [];
|
private cachedTeams: Array<Team> = [];
|
||||||
|
|
||||||
constructor(private httpClient: HttpClient) { }
|
constructor(private httpClient: HttpClient,
|
||||||
|
private settingsService: SettingsService) { }
|
||||||
|
|
||||||
public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<Array<Team>> {
|
public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<Array<Team>> {
|
||||||
return this.list().toPromise();
|
return this.list().toPromise();
|
||||||
@ -22,6 +25,12 @@ export class TeamService implements Resolve<Array<Team>> {
|
|||||||
return this.httpClient.get<Array<Team>>(this.apiEndPoint);
|
return this.httpClient.get<Array<Team>>(this.apiEndPoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get(id: number): Observable<Team> {
|
||||||
|
return this.httpClient.get<Team>(`${this.apiEndPoint}/${id}`).pipe(
|
||||||
|
map(team => this.updateSettingsWhenSelected(team))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public persist(team: Team): Observable<Team> {
|
public persist(team: Team): Observable<Team> {
|
||||||
return team.id === null
|
return team.id === null
|
||||||
? this.create(team)
|
? this.create(team)
|
||||||
@ -47,4 +56,11 @@ export class TeamService implements Resolve<Array<Team>> {
|
|||||||
set teams(teams: Array<Team>) {
|
set teams(teams: Array<Team>) {
|
||||||
this.cachedTeams = teams;
|
this.cachedTeams = teams;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private updateSettingsWhenSelected(team: Team): Team {
|
||||||
|
if (this.settingsService.team.id === team.id) {
|
||||||
|
this.settingsService.team = team;
|
||||||
|
}
|
||||||
|
return team;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
55
src/app/shared/service/timer.service.ts
Normal file → Executable file
55
src/app/shared/service/timer.service.ts
Normal file → Executable file
@ -1,19 +1,21 @@
|
|||||||
import { Injectable, OnDestroy } from '@angular/core';
|
import { Injectable, OnDestroy } from '@angular/core';
|
||||||
import { Subject } from 'rxjs/Subject';
|
|
||||||
import { SettingsService } from './settings.service';
|
import { SettingsService } from './settings.service';
|
||||||
import { SelfUpdaterService } from './self-updater.service';
|
import { SelfUpdaterService } from './self-updater.service';
|
||||||
import { TimerObservable } from 'rxjs/observable/TimerObservable';
|
import { Subject, timer, Subscription } from 'rxjs';
|
||||||
import { Subscription } from 'rxjs/Subscription';
|
|
||||||
import { ActivationStart, Router } from '@angular/router';
|
import { ActivationStart, Router } from '@angular/router';
|
||||||
import { SlideShowService } from '../../display/slide-show.service';
|
import { SlideShowService } from '../../display/slide-show.service';
|
||||||
import 'rxjs/add/operator/filter';
|
import { filter, switchMap } from 'rxjs/operators';
|
||||||
import 'rxjs/add/operator/switchMap';
|
import * as isWithinRange from 'date-fns/is_within_range';
|
||||||
|
import * as min from 'date-fns/min';
|
||||||
|
import * as max from 'date-fns/max';
|
||||||
|
|
||||||
const TIMER_UPDATE_POLL_INTERVAL = 30000;
|
const TIMER_UPDATE_POLL_INTERVAL = 30000;
|
||||||
|
const TIME_SEPARATOR = ':';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TimerService implements OnDestroy {
|
export class TimerService implements OnDestroy {
|
||||||
private autoSwitch = false;
|
public paused = false;
|
||||||
|
public autoSwitch = false;
|
||||||
private slideShowTimer: Subscription;
|
private slideShowTimer: Subscription;
|
||||||
private selfUpdateCheckerTimer: Subscription;
|
private selfUpdateCheckerTimer: Subscription;
|
||||||
private slideTimerSubject: Subject<number> = new Subject<number>();
|
private slideTimerSubject: Subject<number> = new Subject<number>();
|
||||||
@ -24,18 +26,19 @@ export class TimerService implements OnDestroy {
|
|||||||
private router: Router,
|
private router: Router,
|
||||||
private slideShowService: SlideShowService) {
|
private slideShowService: SlideShowService) {
|
||||||
|
|
||||||
const timerSUC = TimerObservable.create(TIMER_UPDATE_POLL_INTERVAL, TIMER_UPDATE_POLL_INTERVAL);
|
const timerSUC = timer(TIMER_UPDATE_POLL_INTERVAL, TIMER_UPDATE_POLL_INTERVAL);
|
||||||
this.selfUpdateCheckerTimer = timerSUC.subscribe(() => {
|
this.selfUpdateCheckerTimer = timerSUC.subscribe(() => {
|
||||||
this.selfUpdaterService.checkAndReloadIfNecessary();
|
this.selfUpdaterService.checkAndReloadIfNecessary();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.slideShowTimer = this.slideTimerSubject.switchMap((period: number) => TimerObservable.create(period))
|
this.slideShowTimer = this.slideTimerSubject
|
||||||
|
.pipe(switchMap((period: number) => timer(period)))
|
||||||
.subscribe(() => this.changeSlide());
|
.subscribe(() => this.changeSlide());
|
||||||
this.setSlideTimer(this.settings.slideInterval);
|
this.setSlideTimer(this.settings.slideInterval);
|
||||||
|
|
||||||
this.autoSwitch = false;
|
this.autoSwitch = false;
|
||||||
this.router.events
|
this.router.events
|
||||||
.filter(event => event instanceof ActivationStart)
|
.pipe(filter(event => event instanceof ActivationStart))
|
||||||
.subscribe((event: ActivationStart) => this.autoSwitch = !!event.snapshot.data.autoSwitchable);
|
.subscribe((event: ActivationStart) => this.autoSwitch = !!event.snapshot.data.autoSwitchable);
|
||||||
|
|
||||||
this.slideIntervalSubscription = this.settings.slideIntervalChanged.subscribe(
|
this.slideIntervalSubscription = this.settings.slideIntervalChanged.subscribe(
|
||||||
@ -50,13 +53,45 @@ export class TimerService implements OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private changeSlide() {
|
private changeSlide() {
|
||||||
if (this.autoSwitch) {
|
if (!this.paused) {
|
||||||
|
if (this.autoSwitch && this.isDuringDailyStandup()) {
|
||||||
|
this.router.navigate(['/kanban']);
|
||||||
|
}
|
||||||
|
if (this.autoSwitch && !this.isDuringDailyStandup()) {
|
||||||
this.slideShowService.nextSlide();
|
this.slideShowService.nextSlide();
|
||||||
}
|
}
|
||||||
this.setSlideTimer(this.settings.slideInterval);
|
this.setSlideTimer(this.settings.slideInterval);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public setSlideTimer(delay: number) {
|
public setSlideTimer(delay: number) {
|
||||||
this.slideTimerSubject.next(delay);
|
this.slideTimerSubject.next(delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public togglePause() {
|
||||||
|
this.paused = !this.paused;
|
||||||
|
if (!this.paused) {
|
||||||
|
this.setSlideTimer(this.settings.slideInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public pause() {
|
||||||
|
this.paused = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private isDuringDailyStandup(): boolean {
|
||||||
|
if (this.settings.team.dailyLockEnabled) {
|
||||||
|
const now = new Date();
|
||||||
|
const startsParts = this.settings.team.dailyStartTime.split(TIME_SEPARATOR).map(part => +part);
|
||||||
|
const endsParts = this.settings.team.dailyEndTime.split(TIME_SEPARATOR).map(part => +part);
|
||||||
|
const times = [
|
||||||
|
new Date(now.getFullYear(), now.getMonth(), now.getDate(), startsParts[0], startsParts[1]),
|
||||||
|
new Date(now.getFullYear(), now.getMonth(), now.getDate(), endsParts[0], endsParts[1])
|
||||||
|
];
|
||||||
|
const startsAt = min(...times);
|
||||||
|
const endsAt = max(...times);
|
||||||
|
return isWithinRange(now, startsAt, endsAt);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
46
src/app/shared/slide-in-out-animation.ts
Normal file → Executable file
46
src/app/shared/slide-in-out-animation.ts
Normal file → Executable file
@ -1,42 +1,34 @@
|
|||||||
// import the required animation functions from the angular animations module
|
// import the required animation functions from the angular animations module
|
||||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
import {animate, group, query, state, style, transition, trigger} from '@angular/animations';
|
||||||
|
|
||||||
|
|
||||||
export const slideInOutAnimation =
|
export const slideInOutAnimation =
|
||||||
// trigger name for attaching this animation to an element using the [@triggerName] syntax
|
trigger('routerTransition', [
|
||||||
trigger('slideInOutAnimation', [
|
transition('* <=> *', [
|
||||||
|
query(':enter, :leave', style({
|
||||||
// end state styles for route container (host)
|
|
||||||
state('*', style({
|
|
||||||
// the view covers the whole screen with a semi tranparent background
|
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: 0
|
bottom: 0
|
||||||
})),
|
}), {
|
||||||
|
optional: true
|
||||||
// route 'enter' transition
|
|
||||||
transition(':enter', [
|
|
||||||
// styles at start of transition
|
|
||||||
style({
|
|
||||||
// start with the content positioned off the right of the screen,
|
|
||||||
// -400% is required instead of -100% because the negative position adds to the width of the element
|
|
||||||
transform: 'translateX(100%)'
|
|
||||||
}),
|
}),
|
||||||
|
group([
|
||||||
// animation and styles at end of transition
|
query(':enter', [
|
||||||
|
style({ transform: 'translateX({{offsetEnter}}%)'}),
|
||||||
animate('.75s cubic-bezier(0.175, 0.885, 0.32, 1.275)', style({
|
animate('.75s cubic-bezier(0.175, 0.885, 0.32, 1.275)', style({
|
||||||
// transition the right position to 0 which slides the content into view
|
transform: 'translateX(0%)'
|
||||||
transform: 'translateX(0)'
|
|
||||||
}))
|
}))
|
||||||
]),
|
], {
|
||||||
|
optional: true
|
||||||
// route 'leave' transition
|
}),
|
||||||
transition(':leave', [
|
query(':leave', [
|
||||||
// animation and styles at end of transition
|
style({ transform: 'translateX(0%)' }),
|
||||||
animate('.75s cubic-bezier(0.175, 0.885, 0.32, 1.275)', style({
|
animate('.75s cubic-bezier(0.175, 0.885, 0.32, 1.275)', style({
|
||||||
// transition the right position to -400% which slides the content out of view
|
transform: 'translateX({{offsetLeave}}%)'
|
||||||
transform: 'translateX(-100%)'
|
|
||||||
}))
|
}))
|
||||||
|
], { optional: true })
|
||||||
|
])
|
||||||
])
|
])
|
||||||
]);
|
]);
|
||||||
|
|||||||
23
src/app/shared/slide-wrapper.ts
Executable file
23
src/app/shared/slide-wrapper.ts
Executable file
@ -0,0 +1,23 @@
|
|||||||
|
import { Slide } from './slide';
|
||||||
|
|
||||||
|
export class SlideWrapper {
|
||||||
|
public type: WrappedType = null;
|
||||||
|
public slideData: Slide = null;
|
||||||
|
public slideRoute: string = null;
|
||||||
|
|
||||||
|
public constructor(type: WrappedType, data: string|Slide) {
|
||||||
|
this.type = type;
|
||||||
|
if (type === WrappedType.USER) {
|
||||||
|
this.slideData = data as Slide;
|
||||||
|
} else if (type === WrappedType.BUILTIN) {
|
||||||
|
this.slideRoute = data as string;
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unknown slide type: ${type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum WrappedType {
|
||||||
|
BUILTIN,
|
||||||
|
USER
|
||||||
|
}
|
||||||
23
src/app/shared/slide.ts
Normal file → Executable file
23
src/app/shared/slide.ts
Normal file → Executable file
@ -2,11 +2,24 @@ import { Team } from './team';
|
|||||||
|
|
||||||
export class Slide {
|
export class Slide {
|
||||||
id: number = null;
|
id: number = null;
|
||||||
title: String = '';
|
title = '';
|
||||||
team: Team = null;
|
type: SlideType = SlideType.MarkDown;
|
||||||
slideData: String = '';
|
visibility: SlideVisibility = SlideVisibility.Public;
|
||||||
|
teams: Array<Team> = [];
|
||||||
|
slideData = '';
|
||||||
|
slideUrl = '';
|
||||||
isVisible = true;
|
isVisible = true;
|
||||||
createdAt: String = null;
|
createdAt: string = null;
|
||||||
updatedAt: String = null;
|
updatedAt: string = null;
|
||||||
position = 0;
|
position = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum SlideType {
|
||||||
|
MarkDown = 'markdown',
|
||||||
|
IFrame = 'iframe',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SlideVisibility {
|
||||||
|
Public = 'public',
|
||||||
|
Team = 'team',
|
||||||
|
}
|
||||||
|
|||||||
16
src/app/shared/team.ts
Normal file → Executable file
16
src/app/shared/team.ts
Normal file → Executable file
@ -1,10 +1,24 @@
|
|||||||
import { Member } from './member';
|
import { Member } from './member';
|
||||||
|
import { KanbanColumn } from './kanban-column';
|
||||||
|
import { Label } from './label';
|
||||||
|
|
||||||
export class Team {
|
export class Team {
|
||||||
id: number = null;
|
id: number = null;
|
||||||
name: String = '';
|
name: String = '';
|
||||||
members: Array<Member> = [];
|
members: Array<Member> = [];
|
||||||
isActive = false;
|
filterId = 0;
|
||||||
|
kanbanEnabled = true;
|
||||||
|
commitTrackerEnabled = true;
|
||||||
|
watchedEnabled = true;
|
||||||
|
dailyLockEnabled = false;
|
||||||
|
dailyStartTime: string;
|
||||||
|
dailyEndTime: string;
|
||||||
|
backlogColumn: KanbanColumn = new KanbanColumn();
|
||||||
|
inprogressColumn: KanbanColumn = new KanbanColumn();
|
||||||
|
verificationColumn: KanbanColumn = new KanbanColumn();
|
||||||
|
doneColumn: KanbanColumn = new KanbanColumn();
|
||||||
|
labels: Array<Label> = [];
|
||||||
|
isActive = true;
|
||||||
createdAt: String = null;
|
createdAt: String = null;
|
||||||
updatedAt: String = null;
|
updatedAt: String = null;
|
||||||
}
|
}
|
||||||
|
|||||||
116
src/app/shared/two-way-linked-list.ts
Executable file
116
src/app/shared/two-way-linked-list.ts
Executable file
@ -0,0 +1,116 @@
|
|||||||
|
|
||||||
|
export class TwoWayLinkedList<T> {
|
||||||
|
private count = 0;
|
||||||
|
private entryNode: Node<T> = null;
|
||||||
|
private lastNode: Node<T> = null;
|
||||||
|
private nodePtr: Node<T> = null;
|
||||||
|
|
||||||
|
public push(newNode: T): TwoWayLinkedList<T> {
|
||||||
|
if (null === this.lastNode) {
|
||||||
|
this.entryNode = new Node<T>(newNode);
|
||||||
|
this.lastNode = this.entryNode;
|
||||||
|
this.nodePtr = this.entryNode;
|
||||||
|
this.entryNode
|
||||||
|
.setPrev(this.entryNode)
|
||||||
|
.setNext(this.entryNode);
|
||||||
|
} else {
|
||||||
|
const prevPtr = this.lastNode;
|
||||||
|
const nodeToAdd = new Node(newNode);
|
||||||
|
nodeToAdd.setNext(this.entryNode).setPrev(prevPtr);
|
||||||
|
prevPtr.setNext(nodeToAdd);
|
||||||
|
this.lastNode = nodeToAdd;
|
||||||
|
}
|
||||||
|
this.count++;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public pop(): T {
|
||||||
|
if (null === this.lastNode) {
|
||||||
|
throw new Error('No items in list');
|
||||||
|
}
|
||||||
|
|
||||||
|
let returnData: T;
|
||||||
|
if (this.entryNode === this.lastNode) {
|
||||||
|
returnData = this.lastNode.getData();
|
||||||
|
this.lastNode.setPrev(null).setNext(null);
|
||||||
|
this.entryNode = null;
|
||||||
|
this.lastNode = null;
|
||||||
|
this.nodePtr = null;
|
||||||
|
} else {
|
||||||
|
const newLast = this.lastNode.getPrev();
|
||||||
|
if (this.nodePtr === this.lastNode) {
|
||||||
|
this.nodePtr = newLast;
|
||||||
|
}
|
||||||
|
newLast.setNext(this.entryNode);
|
||||||
|
returnData = this.lastNode.getData();
|
||||||
|
this.lastNode.setPrev(null).setNext(null);
|
||||||
|
this.lastNode = newLast;
|
||||||
|
}
|
||||||
|
this.count--;
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public clear() {
|
||||||
|
while (this.count > 0) {
|
||||||
|
this.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public prev(): T {
|
||||||
|
this.nodePtr = this.nodePtr.getPrev();
|
||||||
|
return this.nodePtr.getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public next(): T {
|
||||||
|
this.nodePtr = this.nodePtr.getNext();
|
||||||
|
return this.nodePtr.getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public isFirst(): boolean {
|
||||||
|
return this.nodePtr === this.lastNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isLast(): boolean {
|
||||||
|
return this.nodePtr === this.lastNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get first(): T {
|
||||||
|
return this.entryNode.getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get last(): T {
|
||||||
|
return this.lastNode.getData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Node<T> {
|
||||||
|
private prev: Node<T> = null;
|
||||||
|
private next: Node<T> = null;
|
||||||
|
private readonly data: T = null;
|
||||||
|
|
||||||
|
constructor(data: T) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPrev(): Node<T> {
|
||||||
|
return this.prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setPrev(node: Node<T>): Node<T> {
|
||||||
|
this.prev = node;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getNext(): Node<T> {
|
||||||
|
return this.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setNext(node: Node<T>): Node<T> {
|
||||||
|
this.next = node;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getData(): T {
|
||||||
|
return this.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
2
src/environments/environment.prod.ts
Normal file → Executable file
2
src/environments/environment.prod.ts
Normal file → Executable file
@ -1,5 +1,5 @@
|
|||||||
export const environment = {
|
export const environment = {
|
||||||
production: true,
|
production: true,
|
||||||
apiUrl: 'http://ttt-api.tsp.eth.ericsson.se/mtastv-api',
|
apiUrl: 'https://mtoolbox.rnd.ki.sw.ericsson.se/mtastv-api',
|
||||||
commitTrackerApiUrl: 'https://mtas-trex.rnd.ki.sw.ericsson.se:8080/committracker/api/'
|
commitTrackerApiUrl: 'https://mtas-trex.rnd.ki.sw.ericsson.se:8080/committracker/api/'
|
||||||
};
|
};
|
||||||
|
|||||||
5
src/environments/environment.stg.ts
Executable file
5
src/environments/environment.stg.ts
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
export const environment = {
|
||||||
|
production: true,
|
||||||
|
apiUrl: 'http://ttt-api.tsp.eth.ericsson.se/mtastv-api',
|
||||||
|
commitTrackerApiUrl: 'https://mtas-trex.rnd.ki.sw.ericsson.se:8080/committracker/api/'
|
||||||
|
};
|
||||||
@ -10,7 +10,8 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"test.ts"
|
"test.ts",
|
||||||
|
"polyfills.ts"
|
||||||
],
|
],
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.spec.ts",
|
"**/*.spec.ts",
|
||||||
|
|||||||
@ -14,6 +14,8 @@
|
|||||||
"lib": [
|
"lib": [
|
||||||
"es2017",
|
"es2017",
|
||||||
"dom"
|
"dom"
|
||||||
]
|
],
|
||||||
|
"module": "es2015",
|
||||||
|
"baseUrl": "./"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -18,7 +18,6 @@
|
|||||||
"forin": true,
|
"forin": true,
|
||||||
"import-blacklist": [
|
"import-blacklist": [
|
||||||
true,
|
true,
|
||||||
"rxjs",
|
|
||||||
"rxjs/Rx"
|
"rxjs/Rx"
|
||||||
],
|
],
|
||||||
"import-spacing": true,
|
"import-spacing": true,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user