diff --git a/src/app/admin/dashboard/dashboard.component.html b/src/app/admin/dashboard/dashboard.component.html index 8fecb97..0c01d82 100755 --- a/src/app/admin/dashboard/dashboard.component.html +++ b/src/app/admin/dashboard/dashboard.component.html @@ -2,7 +2,7 @@

Dashboard

- +
Start slideshow
diff --git a/src/app/admin/dashboard/dashboard.component.ts b/src/app/admin/dashboard/dashboard.component.ts index e238f0b..ec1ddc7 100755 --- a/src/app/admin/dashboard/dashboard.component.ts +++ b/src/app/admin/dashboard/dashboard.component.ts @@ -1,6 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { Title } from '@angular/platform-browser'; import {SettingsService} from '../../shared/service/settings.service'; +import {SlideShowService} from '../../display/slide-show.service'; @Component({ selector: 'app-dashboard', @@ -10,7 +11,8 @@ import {SettingsService} from '../../shared/service/settings.service'; export class DashboardComponent implements OnInit { constructor(private titleService: Title, - private settingService: SettingsService) { } + private settingService: SettingsService, + private slideShowService: SlideShowService) { } ngOnInit() { this.titleService.setTitle('Dashboard : MTAStv'); @@ -20,4 +22,7 @@ export class DashboardComponent implements OnInit { return this.settingService.team.id !== null; } + public startSlideShow() { + this.slideShowService.startWithFirstSlide(); + } } diff --git a/src/app/admin/team-editor/team-editor.component.html b/src/app/admin/team-editor/team-editor.component.html index 43e4ad5..99a17e7 100755 --- a/src/app/admin/team-editor/team-editor.component.html +++ b/src/app/admin/team-editor/team-editor.component.html @@ -1,29 +1,59 @@

Team editor

-
-
- - -
-
- - -
-
-
- +
+
+ + +
+
+
- - + +
+
+

Built-in slides

+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +

Kanban configuration

-

Daily standup timer

+
+ + +
+
Daily standup timer
@@ -36,7 +66,7 @@
@@ -45,7 +75,7 @@
@@ -148,6 +178,7 @@ class="ui medium {{label.color}} label">{{label.name}}
+

Team members

diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 634e045..68b3ef4 100755 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -15,18 +15,20 @@ export class AppComponent implements OnInit { @HostListener('document:keyup', ['$event.key']) private keyPressed(key: string) { - switch (key) { - case ' ': - this.timerService.togglePause(); - break; - case 'ArrowLeft': - this.timerService.pause(); - this.slideShowService.prevSlide(); - break; - case 'ArrowRight': - this.timerService.pause(); - this.slideShowService.nextSlide(); - break; + if (this.timerService.autoSwitch) { + switch (key) { + case ' ': + this.timerService.togglePause(); + break; + case 'ArrowLeft': + this.timerService.pause(); + this.slideShowService.prevSlide(); + break; + case 'ArrowRight': + this.timerService.pause(); + this.slideShowService.nextSlide(); + break; + } } } diff --git a/src/app/display/slide-show.service.ts b/src/app/display/slide-show.service.ts index 66fa29f..9c448cc 100755 --- a/src/app/display/slide-show.service.ts +++ b/src/app/display/slide-show.service.ts @@ -1,76 +1,86 @@ -import {Injectable} from '@angular/core'; -import {Slide, SlideVisibility} from '../shared/slide'; -import {SlideService} from '../shared/service/slide.service'; -import {Router} from '@angular/router'; -import {AnimationDirection, SettingsService} from '../shared/service/settings.service'; +import { Injectable } from '@angular/core'; +import { Slide, SlideVisibility } from '../shared/slide'; +import { SlideService } from '../shared/service/slide.service'; +import { Router } from '@angular/router'; +import { AnimationDirection, SettingsService } from '../shared/service/settings.service'; +import { TwoWayLinkedList } from '../shared/two-way-linked-list'; +import { SlideWrapper, WrappedType } from '../shared/slide-wrapper'; @Injectable() export class SlideShowService { private oddEven = false; - private currentSlideIndex = -1; - private slides: Array = []; + private cachedSlides: TwoWayLinkedList = new TwoWayLinkedList(); constructor(private slideService: SlideService, - private settingsService: SettingsService, + private settings: SettingsService, private router: Router) { this.reloadSlides(); } + public startWithFirstSlide() { + this.settings.animationDirection = AnimationDirection.RIGHT; + this.reloadSlides(() => this.switchToWrappedSlide(this.cachedSlides.first)); + } + public prevSlide() { - this.settingsService.animationDirection = AnimationDirection.LEFT; - console.log('prev-in', this.slides.length, this.currentSlideIndex); - if (this.currentSlideIndex > this.slides.length) { - this.currentSlideIndex = this.slides.length; - this.router.navigate(['/watchers']); - } else if (this.currentSlideIndex === this.slides.length) { - this.currentSlideIndex--; - this.router.navigate(['/commit-tracker']); - } else if (this.currentSlideIndex < 0) { - this.currentSlideIndex = this.slides.length + 1; - this.reloadSlides(); - this.router.navigate(['/kanban']); + this.settings.animationDirection = AnimationDirection.LEFT; + if (this.cachedSlides.isFirst()) { + this.reloadSlides(() => this.switchToWrappedSlide(this.cachedSlides.last)); } else { - this.oddEven = !this.oddEven; - this.router.navigate([ - this.oddEven ? '/slideshow-odd' : '/slideshow-even', - this.slides[this.currentSlideIndex].id - ]); - this.currentSlideIndex--; + const prevSlide = this.cachedSlides.prev(); + this.switchToWrappedSlide(prevSlide); } - console.log('prev-out', this.slides.length, this.currentSlideIndex); } public nextSlide() { - this.settingsService.animationDirection = AnimationDirection.RIGHT; - console.log('next-in', this.slides.length, this.currentSlideIndex); - if (this.currentSlideIndex < 0) { - this.currentSlideIndex++; - this.router.navigate(['/kanban']); - } else if (this.currentSlideIndex === this.slides.length) { - this.currentSlideIndex++; - this.router.navigate(['/commit-tracker']); - } else if (this.currentSlideIndex > this.slides.length) { - this.currentSlideIndex = -1; - this.reloadSlides(); - this.router.navigate(['/watchers']); + 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', - this.slides[this.currentSlideIndex].id + wrappedSlide.slideData.id ]); - this.currentSlideIndex++; + } else { + throw Error(`Unknown slide type: ${wrappedSlide.type}`); } - console.log('next-out', this.slides.length, this.currentSlideIndex); } - private reloadSlides() { - const team = this.settingsService.team; + private reloadSlides(onReloadFinish: () => void = null) { this.slideService.list().subscribe( - slides => this.slides = slides.filter( - slide => slide.isVisible && (slide.visibility === SlideVisibility.Public || slide.teams.some(s => s.id === team.id)) - ) + slides => { + 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(); + } + } ); } } diff --git a/src/app/shared/service/slide.service.ts b/src/app/shared/service/slide.service.ts index 1772bf2..ea3135c 100755 --- a/src/app/shared/service/slide.service.ts +++ b/src/app/shared/service/slide.service.ts @@ -5,6 +5,9 @@ import { Observable } from 'rxjs'; import { environment } from '../../../environments/environment'; import { Slide } from '../slide'; +import { TeamService } from './team.service'; +import { SettingsService } from './settings.service'; +import { flatMap } from 'rxjs/operators'; @Injectable() export class SlideService implements Resolve>{ @@ -13,7 +16,9 @@ export class SlideService implements Resolve>{ private apiEndPointPosition = environment.apiUrl + '/api/slide-position'; private cachedSlides: Array = []; - constructor(private httpClient: HttpClient) {} + constructor(private httpClient: HttpClient, + private teamService: TeamService, + private settings: SettingsService) {} private static prepareSlideData(slide: Slide) { const slideToSave = Object.assign({}, slide); @@ -28,6 +33,11 @@ export class SlideService implements Resolve>{ } public list(): Observable> { + if (this.settings.team.id) { + return this.teamService.get(this.settings.team.id).pipe( + flatMap( () => this.httpClient.get>(this.apiEndPoint)) + ); + } return this.httpClient.get>(this.apiEndPoint); } diff --git a/src/app/shared/service/timer.service.ts b/src/app/shared/service/timer.service.ts index f7f4a55..c2c8547 100755 --- a/src/app/shared/service/timer.service.ts +++ b/src/app/shared/service/timer.service.ts @@ -15,7 +15,7 @@ const TIME_SEPARATOR = ':'; @Injectable() export class TimerService implements OnDestroy { public paused = false; - private autoSwitch = false; + public autoSwitch = false; private slideShowTimer: Subscription; private selfUpdateCheckerTimer: Subscription; private slideTimerSubject: Subject = new Subject(); @@ -85,8 +85,8 @@ export class TimerService implements OnDestroy { 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.getDay(), startsParts[0], startsParts[1]), - new Date(now.getFullYear(), now.getMonth(), now.getDay(), endsParts[0], endsParts[1]) + 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); diff --git a/src/app/shared/slide-wrapper.ts b/src/app/shared/slide-wrapper.ts new file mode 100755 index 0000000..36fe180 --- /dev/null +++ b/src/app/shared/slide-wrapper.ts @@ -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 +} diff --git a/src/app/shared/team.ts b/src/app/shared/team.ts index c85fe65..8b2aca8 100755 --- a/src/app/shared/team.ts +++ b/src/app/shared/team.ts @@ -7,7 +7,10 @@ export class Team { name: String = ''; members: Array = []; filterId = 0; - dailyLockEnabled: false; + kanbanEnabled = true; + commitTrackerEnabled = true; + watchedEnabled = true; + dailyLockEnabled = false; dailyStartTime: string; dailyEndTime: string; backlogColumn: KanbanColumn = new KanbanColumn(); diff --git a/src/app/shared/two-way-linked-list.ts b/src/app/shared/two-way-linked-list.ts new file mode 100755 index 0000000..4f3c531 --- /dev/null +++ b/src/app/shared/two-way-linked-list.ts @@ -0,0 +1,116 @@ + +export class TwoWayLinkedList { + private count = 0; + private entryNode: Node = null; + private lastNode: Node = null; + private nodePtr: Node = null; + + public push(newNode: T): TwoWayLinkedList { + if (null === this.lastNode) { + this.entryNode = new Node(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 { + private prev: Node = null; + private next: Node = null; + private readonly data: T = null; + + constructor(data: T) { + this.data = data; + } + + public getPrev(): Node { + return this.prev; + } + + public setPrev(node: Node): Node { + this.prev = node; + return this; + } + + public getNext(): Node { + return this.next; + } + + public setNext(node: Node): Node { + this.next = node; + return this; + } + + public getData(): T { + return this.data; + } +}