From e969edb26c8a861cbee313d0d0b326ab4134de3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Danyi?= Date: Fri, 13 Apr 2018 18:17:11 +0200 Subject: [PATCH] * settings * display * dashboard * self-updater * timers in app component --- .angular-cli.json | 4 +- package-lock.json | 10 + package.json | 2 + src/app/admin/admin-routing.module.ts | 41 ++- src/app/admin/admin.module.ts | 12 +- .../admin/dashboard/dashboard.component.css | 0 .../admin/dashboard/dashboard.component.html | 50 ++++ .../dashboard/dashboard.component.spec.ts | 25 ++ .../admin/dashboard/dashboard.component.ts | 17 ++ .../slide-editor/slide-editor.component.html | 57 +++- .../slide-editor/slide-editor.component.ts | 59 ++++- .../slide-list/slide-list.component.html | 33 ++- .../admin/slide-list/slide-list.component.ts | 45 +++- src/app/admin/slide-resolver.service.spec.ts | 15 ++ src/app/admin/slide-resolver.service.ts | 22 ++ .../team-editor/team-editor.component.html | 23 +- .../team-editor/team-editor.component.ts | 42 ++- .../admin/team-list/team-list.component.html | 18 +- .../admin/team-list/team-list.component.ts | 19 +- src/app/admin/team-resolver.service.ts | 5 +- src/app/app.component.ts | 66 ++++- src/app/app.module.ts | 6 +- src/app/display/display-routing.module.ts | 24 ++ src/app/display/display.module.ts | 10 +- .../display/settings/settings.component.css | 0 .../display/settings/settings.component.html | 30 +++ .../settings/settings.component.spec.ts | 25 ++ .../display/settings/settings.component.ts | 34 +++ .../slide-show/slide-show.component.css | 0 .../slide-show/slide-show.component.html | 1 + .../slide-show/slide-show.component.spec.ts | 25 ++ .../slide-show/slide-show.component.ts | 39 +++ src/app/display/slide/slide.component.css | 244 ++++++++++++++++++ src/app/display/slide/slide.component.html | 1 + src/app/display/slide/slide.component.spec.ts | 25 ++ src/app/display/slide/slide.component.ts | 25 ++ ...spec.ts => commit-tracker.service.spec.ts} | 0 .../shared/service/commit-tracker.service.ts | 11 +- .../service/self-updater.service.spec.ts | 15 ++ .../shared/service/self-updater.service.ts | 39 +++ .../shared/service/settings.service.spec.ts | 15 ++ src/app/shared/service/settings.service.ts | 40 +++ src/app/shared/service/slide.service.ts | 54 +++- src/app/shared/service/team.service.ts | 10 +- src/app/shared/slide-in-out-animation.ts | 43 +++ src/app/shared/slide.ts | 14 +- src/styles.css | 5 + 47 files changed, 1239 insertions(+), 61 deletions(-) create mode 100644 src/app/admin/dashboard/dashboard.component.css create mode 100644 src/app/admin/dashboard/dashboard.component.html create mode 100644 src/app/admin/dashboard/dashboard.component.spec.ts create mode 100644 src/app/admin/dashboard/dashboard.component.ts create mode 100644 src/app/admin/slide-resolver.service.spec.ts create mode 100644 src/app/admin/slide-resolver.service.ts create mode 100644 src/app/display/settings/settings.component.css create mode 100644 src/app/display/settings/settings.component.html create mode 100644 src/app/display/settings/settings.component.spec.ts create mode 100644 src/app/display/settings/settings.component.ts create mode 100644 src/app/display/slide-show/slide-show.component.css create mode 100644 src/app/display/slide-show/slide-show.component.html create mode 100644 src/app/display/slide-show/slide-show.component.spec.ts create mode 100644 src/app/display/slide-show/slide-show.component.ts create mode 100644 src/app/display/slide/slide.component.css create mode 100644 src/app/display/slide/slide.component.html create mode 100644 src/app/display/slide/slide.component.spec.ts create mode 100644 src/app/display/slide/slide.component.ts rename src/app/shared/service/{sommit-tracker.service.spec.ts => commit-tracker.service.spec.ts} (100%) create mode 100644 src/app/shared/service/self-updater.service.spec.ts create mode 100644 src/app/shared/service/self-updater.service.ts create mode 100644 src/app/shared/service/settings.service.spec.ts create mode 100644 src/app/shared/service/settings.service.ts create mode 100644 src/app/shared/slide-in-out-animation.ts diff --git a/.angular-cli.json b/.angular-cli.json index 8600971..755e33d 100644 --- a/.angular-cli.json +++ b/.angular-cli.json @@ -22,7 +22,9 @@ "../node_modules/semantic-ui-css/semantic.css", "styles.css" ], - "scripts": [], + "scripts": [ + "../node_modules/marked/lib/marked.js" + ], "environmentSource": "environments/environment.ts", "environments": { "dev": "environments/environment.ts", diff --git a/package-lock.json b/package-lock.json index ff86de6..e52105e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -290,6 +290,11 @@ "@types/jasmine": "2.8.6" } }, + "@types/marked": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-0.3.0.tgz", + "integrity": "sha512-CSf9YWJdX1DkTNu9zcNtdCcn6hkRtB5ILjbhRId4ZOQqx30fXmdecuaXhugQL6eyrhuXtaHJ7PHI+Vm7k9ZJjg==" + }, "@types/node": { "version": "6.0.102", "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.102.tgz", @@ -6836,6 +6841,11 @@ "object-visit": "1.0.1" } }, + "marked": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", + "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==" + }, "md5.js": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", diff --git a/package.json b/package.json index 0b8fd0f..6454df0 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,9 @@ "@angular/platform-browser": "^5.2.0", "@angular/platform-browser-dynamic": "^5.2.0", "@angular/router": "^5.2.0", + "@types/marked": "^0.3.0", "core-js": "^2.4.1", + "marked": "^0.3.19", "ng2-semantic-ui": "^0.9.7", "rxjs": "^5.5.6", "semantic-ui-css": "^2.3.1", diff --git a/src/app/admin/admin-routing.module.ts b/src/app/admin/admin-routing.module.ts index cca431d..414e388 100644 --- a/src/app/admin/admin-routing.module.ts +++ b/src/app/admin/admin-routing.module.ts @@ -5,22 +5,57 @@ import { TeamListComponent} from './team-list/team-list.component'; import { TeamService } from '../shared/service/team.service'; import { TeamResolverService } from './team-resolver.service'; import { TeamEditorComponent } from './team-editor/team-editor.component'; +import { SlideEditorComponent } from './slide-editor/slide-editor.component'; +import { SlideListComponent } from './slide-list/slide-list.component'; +import { SlideResolverService } from './slide-resolver.service'; +import { SlideService } from '../shared/service/slide.service'; +import { DashboardComponent } from './dashboard/dashboard.component'; const routes: Routes = [ { - path: 'admin/teams/list', + path: 'admin', + component: DashboardComponent, + // canActivate: [AuthGuardService, RoleGuardService], + }, { + path: 'admin/teams', component: TeamListComponent, // canActivate: [AuthGuardService, RoleGuardService], resolve: { teams: TeamService, - }, + } + }, { + path: 'admin/team/new', + component: TeamEditorComponent, + // canActivate: [AuthGuardService, RoleGuardService], }, { path: 'admin/team/edit/:id', component: TeamEditorComponent, // canActivate: [AuthGuardService, RoleGuardService], resolve: { team: TeamResolverService, - }, + } + }, { + path: 'admin/slides', + component: SlideListComponent, + // canActivate: [AuthGuardService, RoleGuardService], + resolve: { + slides: SlideService, + } + }, { + path: 'admin/slide/new', + component: SlideEditorComponent, + // canActivate: [AuthGuardService, RoleGuardService], + resolve: { + teams: TeamService, + } + }, { + path: 'admin/slide/edit/:id', + component: SlideEditorComponent, + // canActivate: [AuthGuardService, RoleGuardService], + resolve: { + slide: SlideResolverService, + teams: TeamService, + } } ]; diff --git a/src/app/admin/admin.module.ts b/src/app/admin/admin.module.ts index ca96824..f6a64bc 100644 --- a/src/app/admin/admin.module.ts +++ b/src/app/admin/admin.module.ts @@ -8,14 +8,20 @@ import { TeamEditorComponent } from './team-editor/team-editor.component'; import { SlideEditorComponent } from './slide-editor/slide-editor.component'; import { SlideListComponent } from './slide-list/slide-list.component'; import { TeamResolverService } from './team-resolver.service'; +import { SlideResolverService } from './slide-resolver.service'; +import { SuiModule } from 'ng2-semantic-ui'; +import { DashboardComponent } from './dashboard/dashboard.component'; +import { DisplayModule } from '../display/display.module'; @NgModule({ imports: [ CommonModule, FormsModule, - AdminRoutingModule + SuiModule, + AdminRoutingModule, + DisplayModule ], - declarations: [TeamListComponent, TeamEditorComponent, SlideEditorComponent, SlideListComponent], - providers: [TeamResolverService] + declarations: [TeamListComponent, TeamEditorComponent, SlideEditorComponent, SlideListComponent, DashboardComponent], + providers: [TeamResolverService, SlideResolverService] }) export class AdminModule { } diff --git a/src/app/admin/dashboard/dashboard.component.css b/src/app/admin/dashboard/dashboard.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/admin/dashboard/dashboard.component.html b/src/app/admin/dashboard/dashboard.component.html new file mode 100644 index 0000000..3b92002 --- /dev/null +++ b/src/app/admin/dashboard/dashboard.component.html @@ -0,0 +1,50 @@ +
+

Dashboard

+ + +
diff --git a/src/app/admin/dashboard/dashboard.component.spec.ts b/src/app/admin/dashboard/dashboard.component.spec.ts new file mode 100644 index 0000000..9c996c3 --- /dev/null +++ b/src/app/admin/dashboard/dashboard.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DashboardComponent } from './dashboard.component'; + +describe('DashboardComponent', () => { + let component: DashboardComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ DashboardComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DashboardComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/admin/dashboard/dashboard.component.ts b/src/app/admin/dashboard/dashboard.component.ts new file mode 100644 index 0000000..c611a7e --- /dev/null +++ b/src/app/admin/dashboard/dashboard.component.ts @@ -0,0 +1,17 @@ +import { Component, OnInit } from '@angular/core'; +import { Title } from '@angular/platform-browser'; + +@Component({ + selector: 'app-dashboard', + templateUrl: './dashboard.component.html', + styleUrls: ['./dashboard.component.css'] +}) +export class DashboardComponent implements OnInit { + + constructor(private titleService: Title) { } + + ngOnInit() { + this.titleService.setTitle('Dashboard : MTAStv'); + } + +} diff --git a/src/app/admin/slide-editor/slide-editor.component.html b/src/app/admin/slide-editor/slide-editor.component.html index cdca987..c51d636 100644 --- a/src/app/admin/slide-editor/slide-editor.component.html +++ b/src/app/admin/slide-editor/slide-editor.component.html @@ -1,3 +1,54 @@ -

- slide-editor works! -

+
+

Slide editor

+
+
+
+ + +
+ +
+ + + + + +
+
+ +
+ + +
+ + +
+
+ + +
+
+ + + + Back to slides list +
+ + +
diff --git a/src/app/admin/slide-editor/slide-editor.component.ts b/src/app/admin/slide-editor/slide-editor.component.ts index 6c6bb23..5f5eb51 100644 --- a/src/app/admin/slide-editor/slide-editor.component.ts +++ b/src/app/admin/slide-editor/slide-editor.component.ts @@ -1,4 +1,11 @@ import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Title } from '@angular/platform-browser'; + +import * as marked from 'marked'; +import { Slide } from '../../shared/slide'; +import { SlideService } from '../../shared/service/slide.service'; +import { Team } from '../../shared/team'; @Component({ selector: 'app-slide-editor', @@ -6,10 +13,56 @@ import { Component, OnInit } from '@angular/core'; styleUrls: ['./slide-editor.component.css'] }) export class SlideEditorComponent implements OnInit { + private md; + public emptyTeam: Team = new Team(); + public slide: Slide; + public teams: Array = []; + public renderedPreview: String = ''; + public previewVisible = false; - constructor() { } - - ngOnInit() { + constructor(private slideService: SlideService, + private titleService: Title, + private route: ActivatedRoute, + private router: Router) { + this.md = marked.setOptions({}); + this.emptyTeam.name = 'All teams'; } + ngOnInit() { + this.titleService.setTitle('Edit slide : MTAStv'); + this.route.data.subscribe((data: { + slide: Slide, + teams: Array + }) => { + this.teams = data.teams; + this.slide = data.slide ? data.slide : new Slide; + this.slide.team = this.slide.team === null + ? this.emptyTeam + : this.teams.find(team => team.id === this.slide.team.id); + }); + } + + public saveSlide() { + if (this.canSave) { + this.slideService + .persist(this.slide) + .subscribe(result => this.router.navigate(['/admin/slides'])); + } + } + + get canSave(): boolean { + return [ + this.slide.title, + this.slide.slideData + ].every(field => field.trim().length > 0); + } + + get canPreview(): boolean { + return this.slide.slideData.trim().length > 0; + } + + public preview() { + this.previewVisible = true; + this.renderedPreview = this.md.parse(this.slide.slideData); + } } diff --git a/src/app/admin/slide-list/slide-list.component.html b/src/app/admin/slide-list/slide-list.component.html index a7c5219..8953079 100644 --- a/src/app/admin/slide-list/slide-list.component.html +++ b/src/app/admin/slide-list/slide-list.component.html @@ -1,3 +1,30 @@ -

- slide-list works! -

+
+

Slides

+ New slide + Back to dashboard + + + + + + + + + + + + + + + + + +
Slide titleOwner teamVisible
+ + + {{slide.title}}{{slideTeam(slide.team)}}
+
diff --git a/src/app/admin/slide-list/slide-list.component.ts b/src/app/admin/slide-list/slide-list.component.ts index fe12c15..489a00d 100644 --- a/src/app/admin/slide-list/slide-list.component.ts +++ b/src/app/admin/slide-list/slide-list.component.ts @@ -1,4 +1,10 @@ import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Title } from '@angular/platform-browser'; + +import { SlideService } from '../../shared/service/slide.service'; +import { Slide } from '../../shared/slide'; +import { Team } from '../../shared/team'; @Component({ selector: 'app-slide-list', @@ -7,9 +13,42 @@ import { Component, OnInit } from '@angular/core'; }) export class SlideListComponent implements OnInit { - constructor() { } - - ngOnInit() { + constructor(private slideService: SlideService, + private titleService: Title, + private route: ActivatedRoute) { } + ngOnInit() { + this.titleService.setTitle('Slides : MTAStv'); + this.route.data.subscribe((data: {slides: Array}) => this.slides = data.slides); + } + + get slides(): Array { + return this.slideService.slides; + } + + set slides(slides: Array) { + this.slideService.slides = slides; + } + + public slideTeam(team: Team): String { + return team === null ? 'All teams' : team.name; + } + + public delete(slide: Slide) { + if (confirm(`Are you sure you want to delete the slide '${slide.title}'`)) { + this.slideService.delete(slide).subscribe(result => { + if (result) { + this.slides = this.slides.filter(slideItem => slideItem !== slide); + } + }); + } + } + + public visibleClass(slide: Slide) { + return { + 'green check': slide.isVisible, + 'red times': !slide.isVisible + }; + } } diff --git a/src/app/admin/slide-resolver.service.spec.ts b/src/app/admin/slide-resolver.service.spec.ts new file mode 100644 index 0000000..56b541f --- /dev/null +++ b/src/app/admin/slide-resolver.service.spec.ts @@ -0,0 +1,15 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { SlideResolverService } from './slide-resolver.service'; + +describe('SlideResolverService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [SlideResolverService] + }); + }); + + it('should be created', inject([SlideResolverService], (service: SlideResolverService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/src/app/admin/slide-resolver.service.ts b/src/app/admin/slide-resolver.service.ts new file mode 100644 index 0000000..0fab2c2 --- /dev/null +++ b/src/app/admin/slide-resolver.service.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; +import { Observable } from 'rxjs/Observable'; + +import { environment } from '../../environments/environment'; +import { Slide } from '../shared/slide'; + +@Injectable() +export class SlideResolverService implements Resolve { + private apiEndPoint = environment.apiUrl + '/api/slide'; + + constructor(private httpClient: HttpClient) {} + + public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { + return this.getSlide(route.params['id']).toPromise(); + } + + public getSlide(id: Number): Observable { + return this.httpClient.get(`${this.apiEndPoint}/${id}`); + } +} diff --git a/src/app/admin/team-editor/team-editor.component.html b/src/app/admin/team-editor/team-editor.component.html index e87c10c..e2b057b 100644 --- a/src/app/admin/team-editor/team-editor.component.html +++ b/src/app/admin/team-editor/team-editor.component.html @@ -1,12 +1,18 @@

Team editor

-
- +
+ +
+ + +
+

Team members

@@ -14,24 +20,27 @@
-

- +
@@ -49,5 +58,11 @@
+ + Back to teams list diff --git a/src/app/admin/team-editor/team-editor.component.ts b/src/app/admin/team-editor/team-editor.component.ts index fc86668..d8d6fcb 100644 --- a/src/app/admin/team-editor/team-editor.component.ts +++ b/src/app/admin/team-editor/team-editor.component.ts @@ -1,10 +1,11 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, ElementRef, HostBinding, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { Title } from '@angular/platform-browser'; import { TeamService } from '../../shared/service/team.service'; import { Team } from '../../shared/team'; import { Member } from '../../shared/member'; +import { slideInOutAnimation } from '../../shared/slide-in-out-animation'; @Component({ selector: 'app-team-editor', @@ -12,7 +13,8 @@ import { Member } from '../../shared/member'; styleUrls: ['./team-editor.component.css'] }) export class TeamEditorComponent implements OnInit { - public team: Team = new Team(); + @ViewChild('signumInput') signumInputElement: ElementRef; + public team: Team; public member: Member = new Member(); constructor(private teamService: TeamService, @@ -23,31 +25,55 @@ export class TeamEditorComponent implements OnInit { ngOnInit() { this.titleService.setTitle('Team editor : MTAStv'); - this.route.data.subscribe((data: { team: Team }) => this.team = data.team); + this.route.data.subscribe((data: { team: Team }) => this.team = data.team ? data.team : new Team()); } get canAddMember(): boolean { 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) + && this.team.members.every(member => member.signum !== this.member.signum); } catch (e) { return false; } } public addMember() { - this.team.members = this.team.members.concat(Object.assign({}, this.member)); + this.team.members = this.team.members + .concat(Object.assign({}, this.member)) + .sort((a: Member, b: Member) => a.signum < b.signum ? -1 : 1); this.member = new Member(); } + public handleEnter(ev: KeyboardEvent) { + ev.preventDefault(); + if (this.canAddMember) { + this.addMember(); + this.focusSignumField(); + } + } + + public focusSignumField() { + this.signumInputElement.nativeElement.focus(); + } + public removeMember(signum: String) { if (confirm(`Remove the member with signum ${signum}?`)) { this.team.members = this.team.members.filter(member => member.signum !== signum); } } + get canSave(): boolean { + return [ + this.team.name.trim(), + this.team.members + ].every(field => field.length > 0); + } + public saveTeam() { - this.teamService.update(this.team).subscribe( - () => this.router.navigate(['/admin/teams/list']) - ); + if (this.canSave) { + this.teamService.persist(this.team).subscribe( + () => this.router.navigate(['/admin/teams']) + ); + } } } diff --git a/src/app/admin/team-list/team-list.component.html b/src/app/admin/team-list/team-list.component.html index b2d3cf8..21a36a8 100644 --- a/src/app/admin/team-list/team-list.component.html +++ b/src/app/admin/team-list/team-list.component.html @@ -1,22 +1,30 @@

Teams

+ New team + Back to dashboard - - - + + + + +
TeamMembersTeamMembersActive
+ class="large pencil alternate icon"> + {{team.name}} {{fancyMemberNames(team)}}
-
\ No newline at end of file + diff --git a/src/app/admin/team-list/team-list.component.ts b/src/app/admin/team-list/team-list.component.ts index 117949d..6d2f485 100644 --- a/src/app/admin/team-list/team-list.component.ts +++ b/src/app/admin/team-list/team-list.component.ts @@ -2,8 +2,8 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { Title } from '@angular/platform-browser'; -import { Team } from '../../shared/team'; import { TeamService } from '../../shared/service/team.service'; +import { Team } from '../../shared/team'; @Component({ selector: 'app-team-list', @@ -33,4 +33,21 @@ export class TeamListComponent implements OnInit { public fancyMemberNames(team: Team): String { return team.members.map(member => member.name).join(', '); } + + public delete(team: Team) { + if (confirm(`Remove the team '${team.name}'?`)) { + this.teamService.delete(team).subscribe(result => { + if (result) { + this.teams = this.teams.filter(teamItem => teamItem !== team); + } + }); + } + } + + public activeClass(team: Team) { + return { + 'green check': team.isActive, + 'red times': !team.isActive + }; + } } diff --git a/src/app/admin/team-resolver.service.ts b/src/app/admin/team-resolver.service.ts index cecd095..0d6c083 100644 --- a/src/app/admin/team-resolver.service.ts +++ b/src/app/admin/team-resolver.service.ts @@ -1,9 +1,10 @@ import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; +import { Observable } from 'rxjs/Observable'; + import { Team } from '../shared/team'; import { environment } from '../../environments/environment'; -import { HttpClient } from '@angular/common/http'; -import { Observable } from 'rxjs/Observable'; @Injectable() export class TeamResolverService implements Resolve { diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 7b0f672..58e1a52 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,10 +1,70 @@ -import { Component } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Subscription } from 'rxjs/Subscription'; +import { CommitTrackerService } from './shared/service/commit-tracker.service'; +import { SettingsService } from './shared/service/settings.service'; +import { SelfUpdaterService } from './shared/service/self-updater.service'; +import { ActivationEnd, Router } from '@angular/router'; +import { Subject } from 'rxjs/Subject'; +import { TimerObservable } from 'rxjs/observable/TimerObservable'; +import 'rxjs/add/operator/filter'; +import 'rxjs/add/operator/switchMap'; + + +const TIMER_UPDATE_POLL_INTERVAL = 30000; +const TIMER_COMMITTRACKER_REFRESH = 5000; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) -export class AppComponent { - title = 'app'; +export class AppComponent implements OnInit, OnDestroy { + private selfUpdateCheckerTimer: Subscription; + private refreshCommitTrackerTimer: Subscription; + private slideshowTimer: Subscription; + private autoSwitch = false; + + constructor(private commitTrackerService: CommitTrackerService, + private settings: SettingsService, + private selfUpdaterService: SelfUpdaterService, + private router: Router) {} + + public ngOnInit() { + const timerSUC = TimerObservable.create(TIMER_UPDATE_POLL_INTERVAL, TIMER_UPDATE_POLL_INTERVAL); + this.selfUpdateCheckerTimer = timerSUC.subscribe(() => { + this.selfUpdaterService.checkAndReloadIfNecessary(); + }); + + const timerCT = TimerObservable.create(TIMER_COMMITTRACKER_REFRESH, TIMER_COMMITTRACKER_REFRESH); + this.refreshCommitTrackerTimer = timerCT.subscribe(() => { + this.commitTrackerService.getTeamCommits(this.settings.team.members.map(member => member.signum)); + }); + + const timerSS = new Subject(); + this.slideshowTimer = timerSS.switchMap(period => TimerObservable.create(period)) + .subscribe(() => { + this.changeSlide(timerSS); + }); + timerSS.next(this.settings.slideInterval); + + this.autoSwitch = false; + this.router.events + .filter(event => event instanceof ActivationEnd) + .subscribe(event => { + this.autoSwitch = !!event.snapshot.data.autoSwitchable; + }); + } + + public ngOnDestroy() { + this.refreshCommitTrackerTimer.unsubscribe(); + this.selfUpdateCheckerTimer.unsubscribe(); + this.slideshowTimer.unsubscribe(); + } + + private changeSlide(timer: Subject) { + if (this.autoSwitch) { + console.log('Slide should have changed here'); + } + timer.next(this.settings.slideInterval); + } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 508cbf8..07339e7 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,5 +1,6 @@ import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { HttpClientModule } from '@angular/common/http'; import { FormsModule } from '@angular/forms'; import { SuiModule } from 'ng2-semantic-ui'; @@ -11,6 +12,8 @@ import { SlideService } from './shared/service/slide.service'; import { AdminModule } from './admin/admin.module'; import { DisplayModule } from './display/display.module'; import { CommitTrackerService } from './shared/service/commit-tracker.service'; +import { SettingsService } from './shared/service/settings.service'; +import { SelfUpdaterService } from './shared/service/self-updater.service'; @NgModule({ declarations: [ @@ -18,6 +21,7 @@ import { CommitTrackerService } from './shared/service/commit-tracker.service'; ], imports: [ BrowserModule, + BrowserAnimationsModule, HttpClientModule, FormsModule, SuiModule, @@ -25,7 +29,7 @@ import { CommitTrackerService } from './shared/service/commit-tracker.service'; AdminModule, AppRoutingModule, // must be last RouterModule import for ** route to work ], - providers: [TeamService, SlideService, CommitTrackerService], + providers: [TeamService, SlideService, CommitTrackerService, SettingsService, SelfUpdaterService], bootstrap: [AppComponent] }) export class AppModule { diff --git a/src/app/display/display-routing.module.ts b/src/app/display/display-routing.module.ts index ff69e15..5d6c6a6 100644 --- a/src/app/display/display-routing.module.ts +++ b/src/app/display/display-routing.module.ts @@ -3,15 +3,39 @@ import { Routes, RouterModule } from '@angular/router'; import { CommitTrackerComponent } from './commit-tracker/commit-tracker.component'; import { CommitTrackerService } from '../shared/service/commit-tracker.service'; +import { SettingsComponent } from './settings/settings.component'; +import { TeamService } from '../shared/service/team.service'; +import { SlideShowComponent } from './slide-show/slide-show.component'; +import { SlideResolverService } from '../admin/slide-resolver.service'; const routes: Routes = [ { + path: 'slideshow/:id', + component: SlideShowComponent, + // canActivate: [AuthGuardService, RoleGuardService], + resolve: { + slide: SlideResolverService, + }, + data: { + autoSwitchable: true + } + }, { path: 'commit-tracker', component: CommitTrackerComponent, // canActivate: [AuthGuardService, RoleGuardService], resolve: { commits: CommitTrackerService, }, + data: { + autoSwitchable: true + } + }, { + path: 'settings', + component: SettingsComponent, + // canActivate: [AuthGuardService, RoleGuardService], + resolve: { + teams: TeamService, + }, } ]; diff --git a/src/app/display/display.module.ts b/src/app/display/display.module.ts index dad6dc0..8d68310 100644 --- a/src/app/display/display.module.ts +++ b/src/app/display/display.module.ts @@ -1,14 +1,22 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; import { DisplayRoutingModule } from './display-routing.module'; import { CommitTrackerComponent } from './commit-tracker/commit-tracker.component'; +import { SettingsComponent } from './settings/settings.component'; +import { SuiModule } from 'ng2-semantic-ui'; +import { SlideComponent } from './slide/slide.component'; +import { SlideShowComponent } from './slide-show/slide-show.component'; @NgModule({ imports: [ CommonModule, + FormsModule, + SuiModule, DisplayRoutingModule ], - declarations: [CommitTrackerComponent] + exports: [SlideComponent], + declarations: [CommitTrackerComponent, SettingsComponent, SlideComponent, SlideShowComponent] }) export class DisplayModule { } diff --git a/src/app/display/settings/settings.component.css b/src/app/display/settings/settings.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/display/settings/settings.component.html b/src/app/display/settings/settings.component.html new file mode 100644 index 0000000..d451423 --- /dev/null +++ b/src/app/display/settings/settings.component.html @@ -0,0 +1,30 @@ +
+

TV settings

+ +
+
+
+ + + + +
+
+ + +
+
+ + Back to dashboard +
+
diff --git a/src/app/display/settings/settings.component.spec.ts b/src/app/display/settings/settings.component.spec.ts new file mode 100644 index 0000000..91588f3 --- /dev/null +++ b/src/app/display/settings/settings.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SettingsComponent } from './settings.component'; + +describe('SettingsComponent', () => { + let component: SettingsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SettingsComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SettingsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/display/settings/settings.component.ts b/src/app/display/settings/settings.component.ts new file mode 100644 index 0000000..fd9186d --- /dev/null +++ b/src/app/display/settings/settings.component.ts @@ -0,0 +1,34 @@ +import { Component, OnInit } from '@angular/core'; +import { Team } from '../../shared/team'; +import { ActivatedRoute, Router } from '@angular/router'; +import { SettingsService } from '../../shared/service/settings.service'; + +@Component({ + selector: 'app-settings', + templateUrl: './settings.component.html', + styleUrls: ['./settings.component.css'] +}) +export class SettingsComponent implements OnInit { + + public teams: Array = []; + public selectedTeam: Team; + public slideInterval: Number; + + constructor(private route: ActivatedRoute, + private settingsService: SettingsService, + private router: Router) {} + + ngOnInit() { + this.route.data.subscribe((data: {teams: Array}) => { + this.teams = data.teams; + this.selectedTeam = this.teams.find(team => team.id === this.settingsService.team.id); + this.slideInterval = this.settingsService.slideInterval; + }); + } + + public saveSettings() { + this.settingsService.team = this.selectedTeam; + this.settingsService.slideInterval = this.slideInterval; + this.router.navigate(['/admin']); + } +} diff --git a/src/app/display/slide-show/slide-show.component.css b/src/app/display/slide-show/slide-show.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/display/slide-show/slide-show.component.html b/src/app/display/slide-show/slide-show.component.html new file mode 100644 index 0000000..88cebed --- /dev/null +++ b/src/app/display/slide-show/slide-show.component.html @@ -0,0 +1 @@ + diff --git a/src/app/display/slide-show/slide-show.component.spec.ts b/src/app/display/slide-show/slide-show.component.spec.ts new file mode 100644 index 0000000..abcc1b7 --- /dev/null +++ b/src/app/display/slide-show/slide-show.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SlideShowComponent } from './slide-show.component'; + +describe('SlideShowComponent', () => { + let component: SlideShowComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SlideShowComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SlideShowComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/display/slide-show/slide-show.component.ts b/src/app/display/slide-show/slide-show.component.ts new file mode 100644 index 0000000..c2b10f0 --- /dev/null +++ b/src/app/display/slide-show/slide-show.component.ts @@ -0,0 +1,39 @@ +import { Component, HostBinding, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Title } from '@angular/platform-browser'; + +import * as marked from 'marked'; +import { slideInOutAnimation } from '../../shared/slide-in-out-animation'; +import { Slide } from '../../shared/slide'; + +@Component({ + selector: 'app-slide-show', + templateUrl: './slide-show.component.html', + styleUrls: ['./slide-show.component.css'], + animations: [slideInOutAnimation] +}) +export class SlideShowComponent implements OnInit { + private md; + public slide: Slide; + + @HostBinding('@slideInOutAnimation') + get slideIn() { + return ''; + } + + constructor(private route: ActivatedRoute, + private titleService: Title) { + this.md = marked.setOptions({}); + } + + ngOnInit() { + this.route.data.subscribe((data: {slide: Slide}) => { + this.slide = data.slide; + this.titleService.setTitle(`${this.slide.title} : MTAStv`); + }); + } + + get renderedSlide(): String { + return this.md.parse(this.slide.slideData); + } +} diff --git a/src/app/display/slide/slide.component.css b/src/app/display/slide/slide.component.css new file mode 100644 index 0000000..3703d02 --- /dev/null +++ b/src/app/display/slide/slide.component.css @@ -0,0 +1,244 @@ +:host { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 1; + background: #222; + font-family: "Source Sans Pro", Helvetica, sans-serif; + font-size: 42px; + font-weight: normal; + color: #fff; } + +section { + line-height: 1.3; + font-weight: inherit; } + +/********************************************* + * HEADERS + *********************************************/ +h1, +h2, +h3, +h4, +h5, +h6 { + margin: 0 0 20px 0; + color: #fff; + font-family: "Source Sans Pro", Helvetica, sans-serif; + font-weight: 600; + line-height: 1.2; + letter-spacing: normal; + text-transform: uppercase; + text-shadow: none; + word-wrap: break-word; } + +h1 { + font-size: 2.5em; } + +h2 { + font-size: 1.6em; } + +h3 { + font-size: 1.3em; } + +h4 { + font-size: 1em; } + +h1 { + text-shadow: none; } + +/********************************************* + * OTHER + *********************************************/ +p { + margin: 20px 0; + line-height: 1.3; } + +/* Ensure certain elements are never larger than the slide itself */ +img, +video, +iframe { + max-width: 95%; + max-height: 95%; } + +strong, +b { + font-weight: bold; } + +em { + font-style: italic; } + +ol, +dl, +ul { + display: inline-block; + text-align: left; + margin: 0 0 0 1em; } + +ol { + list-style-type: decimal; } + +ul { + list-style-type: disc; } + +ul ul { + list-style-type: square; } + +ul ul ul { + list-style-type: circle; } + +ul ul, +ul ol, +ol ol, +ol ul { + display: block; + margin-left: 40px; } + +dt { + font-weight: bold; } + +dd { + margin-left: 40px; } + +blockquote { + display: block; + position: relative; + width: 70%; + margin: 20px auto; + padding: 5px; + font-style: italic; + background: rgba(255, 255, 255, 0.05); + box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.2); } + +blockquote p:first-child, +blockquote p:last-child { + display: inline-block; } + +q { + font-style: italic; } + +pre { + display: block; + position: relative; + width: 90%; + margin: 20px auto; + text-align: left; + font-size: 0.55em; + font-family: monospace; + line-height: 1.2em; + word-wrap: break-word; + box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); } + +code { + font-family: monospace; + text-transform: none; } + +pre code { + display: block; + padding: 5px; + overflow: auto; + max-height: 400px; + word-wrap: normal; } + +table { + margin: auto; + border-collapse: collapse; + border-spacing: 0; } + +table th { + font-weight: bold; } + +table th, +table td { + text-align: left; + padding: 0.2em 0.5em 0.2em 0.5em; + border-bottom: 1px solid; } + +table th[align="center"], +table td[align="center"] { + text-align: center; } + +table th[align="right"], +table td[align="right"] { + text-align: right; } + +table tbody tr:last-child th, +table tbody tr:last-child td { + border-bottom: none; } + +sup { + vertical-align: super; } + +sub { + vertical-align: sub; } + +small { + display: inline-block; + font-size: 0.6em; + line-height: 1.2em; + vertical-align: top; } + +small * { + vertical-align: top; } + +/********************************************* + * LINKS + *********************************************/ +a { + color: #42affa; + text-decoration: none; + -webkit-transition: color .15s ease; + -moz-transition: color .15s ease; + transition: color .15s ease; } + +a:hover { + color: #8dcffc; + text-shadow: none; + border: none; } + +.roll span:after { + color: #fff; + background: #068de9; } + +/********************************************* + * IMAGES + *********************************************/ +section img { + margin: 15px 0px; + background: rgba(255, 255, 255, 0.12); + border: 4px solid #fff; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.15); } + +section img.plain { + border: 0; + box-shadow: none; } + +a img { + -webkit-transition: all .15s linear; + -moz-transition: all .15s linear; + transition: all .15s linear; } + +a:hover img { + background: rgba(255, 255, 255, 0.2); + border-color: #42affa; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.55); } + +/********************************************* + * NAVIGATION CONTROLS + *********************************************/ +.controls { + color: #42affa; } + +/********************************************* + * PROGRESS BAR + *********************************************/ +.progress { + background: rgba(0, 0, 0, 0.2); + color: #42affa; } + +.progress span { + -webkit-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); + -moz-transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); + transition: width 800ms cubic-bezier(0.26, 0.86, 0.44, 0.985); } \ No newline at end of file diff --git a/src/app/display/slide/slide.component.html b/src/app/display/slide/slide.component.html new file mode 100644 index 0000000..67745c7 --- /dev/null +++ b/src/app/display/slide/slide.component.html @@ -0,0 +1 @@ +
diff --git a/src/app/display/slide/slide.component.spec.ts b/src/app/display/slide/slide.component.spec.ts new file mode 100644 index 0000000..c82efb6 --- /dev/null +++ b/src/app/display/slide/slide.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SlideComponent } from './slide.component'; + +describe('SlideComponent', () => { + let component: SlideComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SlideComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SlideComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/display/slide/slide.component.ts b/src/app/display/slide/slide.component.ts new file mode 100644 index 0000000..6db9cc8 --- /dev/null +++ b/src/app/display/slide/slide.component.ts @@ -0,0 +1,25 @@ +import { Component, EventEmitter, HostListener, Input, OnInit, Output } from '@angular/core'; + +@Component({ + selector: 'app-slide', + templateUrl: './slide.component.html', + styleUrls: ['./slide.component.css'] +}) +export class SlideComponent implements OnInit { + @Input() data: String = ''; + @Input() preview = false; + + @Input() visible = true; + @Output() visibleChange = new EventEmitter(); + + + constructor() {} + ngOnInit() {} + + @HostListener('click') + public hide() { + if (this.preview) { + this.visibleChange.emit(false); + } + } +} diff --git a/src/app/shared/service/sommit-tracker.service.spec.ts b/src/app/shared/service/commit-tracker.service.spec.ts similarity index 100% rename from src/app/shared/service/sommit-tracker.service.spec.ts rename to src/app/shared/service/commit-tracker.service.spec.ts diff --git a/src/app/shared/service/commit-tracker.service.ts b/src/app/shared/service/commit-tracker.service.ts index dd1511d..3271e47 100644 --- a/src/app/shared/service/commit-tracker.service.ts +++ b/src/app/shared/service/commit-tracker.service.ts @@ -1,11 +1,11 @@ import { Injectable } from '@angular/core'; -import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http'; +import { HttpClient, HttpParams } from '@angular/common/http'; import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; import { Observable } from 'rxjs/Observable'; import { environment } from '../../../environments/environment'; import { Commit } from '../commit'; -import { TeamService } from './team.service'; +import { SettingsService } from './settings.service'; @Injectable() export class CommitTrackerService implements Resolve> { @@ -14,10 +14,13 @@ export class CommitTrackerService implements Resolve> { private cachedCommits: Array = []; constructor(private httpClient: HttpClient, - private teamService: TeamService) { } + private settingsService: SettingsService) { } public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise> { - return this.getTeamCommits(this.teamService.teamMembers).toPromise(); + const signums = this.settingsService.team + ? this.settingsService.team.members.map(member => member.signum) + : []; + return this.getTeamCommits(signums).toPromise(); } public getTeamCommits(signums: Array): Observable> { diff --git a/src/app/shared/service/self-updater.service.spec.ts b/src/app/shared/service/self-updater.service.spec.ts new file mode 100644 index 0000000..8be1bb2 --- /dev/null +++ b/src/app/shared/service/self-updater.service.spec.ts @@ -0,0 +1,15 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { SelfUpdaterService } from './self-updater.service'; + +describe('SelfUpdaterService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [SelfUpdaterService] + }); + }); + + it('should be created', inject([SelfUpdaterService], (service: SelfUpdaterService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/src/app/shared/service/self-updater.service.ts b/src/app/shared/service/self-updater.service.ts new file mode 100644 index 0000000..f76ae2f --- /dev/null +++ b/src/app/shared/service/self-updater.service.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs/Observable'; +import { Location } from '@angular/common'; + +@Injectable() +export class SelfUpdaterService { + + private appRevision: Number = 0; + private initFailed = false; + + constructor(private httpClient: HttpClient, + private locationService: Location) { + this.getDeployedRevision().subscribe( + revision => this.appRevision = revision, + () => { + console.log( + '%c Couldn\'t load initial revision data from server. Self update disabled.', + 'background: #222; color: #bada55;' + ); + this.initFailed = true; + } + ); + } + + private getDeployedRevision(): Observable { + return this.httpClient.get(this.locationService.prepareExternalUrl('/revision.json')); + } + + public checkAndReloadIfNecessary() { + if (!this.initFailed) { + this.getDeployedRevision().subscribe(revision => { + if (revision > this.appRevision) { + this.locationService.go('/'); + } + }); + } + } +} diff --git a/src/app/shared/service/settings.service.spec.ts b/src/app/shared/service/settings.service.spec.ts new file mode 100644 index 0000000..b7a9bdf --- /dev/null +++ b/src/app/shared/service/settings.service.spec.ts @@ -0,0 +1,15 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { SettingsService } from './settings.service'; + +describe('SettingsService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [SettingsService] + }); + }); + + it('should be created', inject([SettingsService], (service: SettingsService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/src/app/shared/service/settings.service.ts b/src/app/shared/service/settings.service.ts new file mode 100644 index 0000000..8bedb18 --- /dev/null +++ b/src/app/shared/service/settings.service.ts @@ -0,0 +1,40 @@ +import { Injectable } from '@angular/core'; +import { Team } from '../team'; + +const DEFAULT_SLIDE_INTERVAL = 30000; + +const SLIDE_INTERVAL_KEY = 'slide_interval'; +const SELECTED_TEAM_KEY = 'team'; + +@Injectable() +export class SettingsService { + + constructor() { + } + + get team(): Team { + try { + const team = JSON.parse(localStorage.getItem(SELECTED_TEAM_KEY)); + return team !== null ? team : new Team; + } catch (e) { + return new Team; + } + } + + set team(team: Team) { + localStorage.setItem(SELECTED_TEAM_KEY, JSON.stringify(team)); + } + + get slideInterval(): Number { + try { + const interval = JSON.parse(localStorage.getItem(SLIDE_INTERVAL_KEY)); + return interval !== null ? interval : DEFAULT_SLIDE_INTERVAL; + } catch (e) { + return DEFAULT_SLIDE_INTERVAL; + } + } + + set slideInterval(interval: Number) { + localStorage.setItem(SLIDE_INTERVAL_KEY, JSON.stringify(interval)); + } +} diff --git a/src/app/shared/service/slide.service.ts b/src/app/shared/service/slide.service.ts index 8bdb738..c89551e 100644 --- a/src/app/shared/service/slide.service.ts +++ b/src/app/shared/service/slide.service.ts @@ -1,8 +1,58 @@ import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; +import { Observable } from 'rxjs/Observable'; + +import { environment } from '../../../environments/environment'; +import { Slide } from '../slide'; @Injectable() -export class SlideService { +export class SlideService implements Resolve>{ - constructor() { } + private apiEndPoint = environment.apiUrl + '/api/slide'; + private cachedSlides: Array = []; + constructor(private httpClient: HttpClient) {} + + private static prepareSlideData(slide: Slide) { + const slideToSave = Object.assign({}, slide); + slideToSave.team = slideToSave.team.id === null + ? null + : slideToSave.team.id; + return slideToSave; + } + + public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise> { + return this.list().toPromise(); + } + + public list(): Observable> { + return this.httpClient.get>(this.apiEndPoint); + } + + public persist(slide: Slide): Observable { + return slide.id === null + ? this.create(slide) + : this.update(slide); + } + + public create(slide: Slide): Observable { + return this.httpClient.post(this.apiEndPoint, SlideService.prepareSlideData(slide)); + } + + public update(slide: Slide): Observable { + return this.httpClient.put(`${this.apiEndPoint}/${slide.id.toString()}`, SlideService.prepareSlideData(slide)); + } + + public delete(slide: Slide): Observable { + return this.httpClient.delete(`${this.apiEndPoint}/${slide.id.toString()}`); + } + + get slides(): Array { + return this.cachedSlides; + } + + set slides(slides: Array) { + this.cachedSlides = slides; + } } diff --git a/src/app/shared/service/team.service.ts b/src/app/shared/service/team.service.ts index 83446ee..4c5b6d0 100644 --- a/src/app/shared/service/team.service.ts +++ b/src/app/shared/service/team.service.ts @@ -22,6 +22,12 @@ export class TeamService implements Resolve> { return this.httpClient.get>(this.apiEndPoint); } + public persist(team: Team): Observable { + return team.id === null + ? this.create(team) + : this.update(team); + } + public create(team: Team) { return this.httpClient.post(this.apiEndPoint, team); } @@ -41,8 +47,4 @@ export class TeamService implements Resolve> { set teams(teams: Array) { this.cachedTeams = teams; } - - get teamMembers(): Array { - return ['eztoli', 'etamecs', 'esteist', 'emrtsis', 'erudvel']; - } } diff --git a/src/app/shared/slide-in-out-animation.ts b/src/app/shared/slide-in-out-animation.ts new file mode 100644 index 0000000..a0dfcc2 --- /dev/null +++ b/src/app/shared/slide-in-out-animation.ts @@ -0,0 +1,43 @@ +// import the required animation functions from the angular animations module +import { animate, state, style, transition, trigger } from '@angular/animations'; + +export const slideInOutAnimation = + // trigger name for attaching this animation to an element using the [@triggerName] syntax + trigger('slideInOutAnimation', [ + + // end state styles for route container (host) + state('*', style({ + // the view covers the whole screen with a semi tranparent background + position: 'fixed', + top: 0, + left: 0, + right: 0, + bottom: 0 + })), + + // 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 + right: '-400%' + }), + + // animation and styles at end of transition + animate('.5s ease-in-out', style({ + // transition the right position to 0 which slides the content into view + right: 0 + })) + ]), + + // route 'leave' transition + transition(':leave', [ + // animation and styles at end of transition + animate('.5s ease-in-out', style({ + // transition the right position to -400% which slides the content out of view + right: '-400%' + })) + ]) + ]); diff --git a/src/app/shared/slide.ts b/src/app/shared/slide.ts index 73abaec..59fa9a7 100644 --- a/src/app/shared/slide.ts +++ b/src/app/shared/slide.ts @@ -1,11 +1,11 @@ import { Team } from './team'; export class Slide { - id: Number; - title: String; - team: Team; - slideData: String; - isVisible: boolean; - createdAt: String; - updatedAt: String; + id: Number = null; + title: String = ''; + team: Team = null; + slideData: String = ''; + isVisible = true; + createdAt: String = null; + updatedAt: String = null; } diff --git a/src/styles.css b/src/styles.css index 7377efb..1ae6850 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1,4 +1,9 @@ /* You can add global styles to this file, and also import other style files */ +html, +body { + height: calc(100% - 2em); +} + .main.container { margin-top: 2em; }