* settings
* display * dashboard * self-updater * timers in app component
This commit is contained in:
parent
b0cbd691d5
commit
e969edb26c
@ -22,7 +22,9 @@
|
|||||||
"../node_modules/semantic-ui-css/semantic.css",
|
"../node_modules/semantic-ui-css/semantic.css",
|
||||||
"styles.css"
|
"styles.css"
|
||||||
],
|
],
|
||||||
"scripts": [],
|
"scripts": [
|
||||||
|
"../node_modules/marked/lib/marked.js"
|
||||||
|
],
|
||||||
"environmentSource": "environments/environment.ts",
|
"environmentSource": "environments/environment.ts",
|
||||||
"environments": {
|
"environments": {
|
||||||
"dev": "environments/environment.ts",
|
"dev": "environments/environment.ts",
|
||||||
|
|||||||
10
package-lock.json
generated
10
package-lock.json
generated
@ -290,6 +290,11 @@
|
|||||||
"@types/jasmine": "2.8.6"
|
"@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": {
|
"@types/node": {
|
||||||
"version": "6.0.102",
|
"version": "6.0.102",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.102.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.102.tgz",
|
||||||
@ -6836,6 +6841,11 @@
|
|||||||
"object-visit": "1.0.1"
|
"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": {
|
"md5.js": {
|
||||||
"version": "1.3.4",
|
"version": "1.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz",
|
||||||
|
|||||||
@ -21,7 +21,9 @@
|
|||||||
"@angular/platform-browser": "^5.2.0",
|
"@angular/platform-browser": "^5.2.0",
|
||||||
"@angular/platform-browser-dynamic": "^5.2.0",
|
"@angular/platform-browser-dynamic": "^5.2.0",
|
||||||
"@angular/router": "^5.2.0",
|
"@angular/router": "^5.2.0",
|
||||||
|
"@types/marked": "^0.3.0",
|
||||||
"core-js": "^2.4.1",
|
"core-js": "^2.4.1",
|
||||||
|
"marked": "^0.3.19",
|
||||||
"ng2-semantic-ui": "^0.9.7",
|
"ng2-semantic-ui": "^0.9.7",
|
||||||
"rxjs": "^5.5.6",
|
"rxjs": "^5.5.6",
|
||||||
"semantic-ui-css": "^2.3.1",
|
"semantic-ui-css": "^2.3.1",
|
||||||
|
|||||||
@ -5,22 +5,57 @@ import { TeamListComponent} from './team-list/team-list.component';
|
|||||||
import { TeamService } from '../shared/service/team.service';
|
import { TeamService } from '../shared/service/team.service';
|
||||||
import { TeamResolverService } from './team-resolver.service';
|
import { TeamResolverService } from './team-resolver.service';
|
||||||
import { TeamEditorComponent } from './team-editor/team-editor.component';
|
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 = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: 'admin/teams/list',
|
path: 'admin',
|
||||||
|
component: DashboardComponent,
|
||||||
|
// canActivate: [AuthGuardService, RoleGuardService],
|
||||||
|
}, {
|
||||||
|
path: 'admin/teams',
|
||||||
component: TeamListComponent,
|
component: TeamListComponent,
|
||||||
// canActivate: [AuthGuardService, RoleGuardService],
|
// canActivate: [AuthGuardService, RoleGuardService],
|
||||||
resolve: {
|
resolve: {
|
||||||
teams: TeamService,
|
teams: TeamService,
|
||||||
},
|
}
|
||||||
|
}, {
|
||||||
|
path: 'admin/team/new',
|
||||||
|
component: TeamEditorComponent,
|
||||||
|
// canActivate: [AuthGuardService, RoleGuardService],
|
||||||
}, {
|
}, {
|
||||||
path: 'admin/team/edit/:id',
|
path: 'admin/team/edit/:id',
|
||||||
component: TeamEditorComponent,
|
component: TeamEditorComponent,
|
||||||
// canActivate: [AuthGuardService, RoleGuardService],
|
// canActivate: [AuthGuardService, RoleGuardService],
|
||||||
resolve: {
|
resolve: {
|
||||||
team: TeamResolverService,
|
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,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -8,14 +8,20 @@ import { TeamEditorComponent } from './team-editor/team-editor.component';
|
|||||||
import { SlideEditorComponent } from './slide-editor/slide-editor.component';
|
import { SlideEditorComponent } from './slide-editor/slide-editor.component';
|
||||||
import { SlideListComponent } from './slide-list/slide-list.component';
|
import { SlideListComponent } from './slide-list/slide-list.component';
|
||||||
import { TeamResolverService } from './team-resolver.service';
|
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({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
AdminRoutingModule
|
SuiModule,
|
||||||
|
AdminRoutingModule,
|
||||||
|
DisplayModule
|
||||||
],
|
],
|
||||||
declarations: [TeamListComponent, TeamEditorComponent, SlideEditorComponent, SlideListComponent],
|
declarations: [TeamListComponent, TeamEditorComponent, SlideEditorComponent, SlideListComponent, DashboardComponent],
|
||||||
providers: [TeamResolverService]
|
providers: [TeamResolverService, SlideResolverService]
|
||||||
})
|
})
|
||||||
export class AdminModule { }
|
export class AdminModule { }
|
||||||
|
|||||||
0
src/app/admin/dashboard/dashboard.component.css
Normal file
0
src/app/admin/dashboard/dashboard.component.css
Normal file
50
src/app/admin/dashboard/dashboard.component.html
Normal file
50
src/app/admin/dashboard/dashboard.component.html
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<div class="ui main container">
|
||||||
|
<h1 class="ui dividing header">Dashboard</h1>
|
||||||
|
|
||||||
|
<div class="ui four cards">
|
||||||
|
<a class="ui raised yellow card" [routerLink]="['/commit-tracker']">
|
||||||
|
<div class="content">
|
||||||
|
<div class="header">Commit tracker</div>
|
||||||
|
<div class="meta">
|
||||||
|
<span class="category">Local</span>
|
||||||
|
</div>
|
||||||
|
<div class="description">
|
||||||
|
<p>View commits of the current configured team members.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a class="ui raised red card" [routerLink]="['/settings']">
|
||||||
|
<div class="content">
|
||||||
|
<div class="header">TV settings</div>
|
||||||
|
<div class="meta">
|
||||||
|
<span class="category">Local</span>
|
||||||
|
</div>
|
||||||
|
<div class="description">
|
||||||
|
<p>Change settings for this device, what team the device belongs to, and slideshow duration.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a class="ui raised green card" [routerLink]="['/admin/teams']">
|
||||||
|
<div class="content">
|
||||||
|
<div class="header">Teams</div>
|
||||||
|
<div class="meta">
|
||||||
|
<span class="category">Global</span>
|
||||||
|
</div>
|
||||||
|
<div class="description">
|
||||||
|
<p>Create teams, manage team members.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a class="ui raised blue card" [routerLink]="['/admin/slides']">
|
||||||
|
<div class="content">
|
||||||
|
<div class="header">Slides</div>
|
||||||
|
<div class="meta">
|
||||||
|
<span class="category">Global</span>
|
||||||
|
</div>
|
||||||
|
<div class="description">
|
||||||
|
<p>Create and edit slides for the TV, change slide visibility and assign the slides to individual teams</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
25
src/app/admin/dashboard/dashboard.component.spec.ts
Normal file
25
src/app/admin/dashboard/dashboard.component.spec.ts
Normal file
@ -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<DashboardComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ DashboardComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(DashboardComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
17
src/app/admin/dashboard/dashboard.component.ts
Normal file
17
src/app/admin/dashboard/dashboard.component.ts
Normal file
@ -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');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,3 +1,54 @@
|
|||||||
<p>
|
<div class="ui main container">
|
||||||
slide-editor works!
|
<h1 class="ui dividing header">Slide editor</h1>
|
||||||
</p>
|
<form class="ui form" #slideEditorForm (ngSubmit)="saveSlide()">
|
||||||
|
<div class="two fields">
|
||||||
|
<div class="eight wide field">
|
||||||
|
<label for="slide_name">Slide title</label>
|
||||||
|
<input id="slide_name" type="text" name="slide_name" [(ngModel)]="slide.title">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="eight wide field">
|
||||||
|
<label for="team">Visible to this team</label>
|
||||||
|
<sui-select class="selection"
|
||||||
|
id="team"
|
||||||
|
name="team"
|
||||||
|
[(ngModel)]="slide.team"
|
||||||
|
labelField="name"
|
||||||
|
[isSearchable]="true"
|
||||||
|
#select>
|
||||||
|
<sui-select-option [value]="emptyTeam"></sui-select-option>
|
||||||
|
<sui-select-option *ngFor="let team of teams" [value]="team"></sui-select-option>
|
||||||
|
</sui-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label for="slide_data">Slide data</label>
|
||||||
|
<textarea id="slide_data" rows="30" name="slide_data" [(ngModel)]="slide.slideData"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="five wide field">
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input type="checkbox" id="mustLowerCost" name="mustLowerCost"
|
||||||
|
[(ngModel)]="slide.isVisible">
|
||||||
|
<label for="mustLowerCost">Active</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="ui button"
|
||||||
|
[class.positive]="canSave"
|
||||||
|
[class.disabled]="!canSave">Save</button>
|
||||||
|
<button type="button" class="ui button"
|
||||||
|
[class.primary]="canPreview"
|
||||||
|
[class.disabled]="!canPreview"
|
||||||
|
(click)="preview()"><i class="search icon"></i>Preview</button>
|
||||||
|
<a class="ui button"
|
||||||
|
[routerLink]="['/admin/slides']"><i class="left angle icon"></i>Back to slides list</a>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<app-slide [data]="renderedPreview"
|
||||||
|
[preview]="true"
|
||||||
|
[(visible)]="previewVisible"
|
||||||
|
*ngIf="previewVisible"></app-slide>
|
||||||
|
</div>
|
||||||
|
|||||||
@ -1,4 +1,11 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
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({
|
@Component({
|
||||||
selector: 'app-slide-editor',
|
selector: 'app-slide-editor',
|
||||||
@ -6,10 +13,56 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
styleUrls: ['./slide-editor.component.css']
|
styleUrls: ['./slide-editor.component.css']
|
||||||
})
|
})
|
||||||
export class SlideEditorComponent implements OnInit {
|
export class SlideEditorComponent implements OnInit {
|
||||||
|
private md;
|
||||||
|
public emptyTeam: Team = new Team();
|
||||||
|
public slide: Slide;
|
||||||
|
public teams: Array<Team> = [];
|
||||||
|
public renderedPreview: String = '';
|
||||||
|
public previewVisible = false;
|
||||||
|
|
||||||
constructor() { }
|
constructor(private slideService: SlideService,
|
||||||
|
private titleService: Title,
|
||||||
ngOnInit() {
|
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<Team>
|
||||||
|
}) => {
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,30 @@
|
|||||||
<p>
|
<div class="ui main container">
|
||||||
slide-list works!
|
<h1 class="ui dividing header">Slides</h1>
|
||||||
</p>
|
<a class="ui primary button"
|
||||||
|
[routerLink]="['/admin/slide/new']"><i class="plus sign icon"></i>New slide</a>
|
||||||
|
<a class="ui button"
|
||||||
|
[routerLink]="['/admin']"><i class="left angle icon"></i>Back to dashboard</a>
|
||||||
|
<table *ngIf="slides?.length" class="ui large padded celled definition table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="collapsing"></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 check square outline icon"></i>Visible</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let slide of slides">
|
||||||
|
<td class="collapsing">
|
||||||
|
<a [routerLink]="['/admin/slide/edit', slide.id]" title="Change"><i
|
||||||
|
class="large pencil alternate icon"></i></a>
|
||||||
|
<a title="Delete" (click)="delete(slide)"><i
|
||||||
|
class="large red fitted trash alternate outline icon"></i></a>
|
||||||
|
</td>
|
||||||
|
<td>{{slide.title}}</td>
|
||||||
|
<td class="collapsing">{{slideTeam(slide.team)}}</td>
|
||||||
|
<td class="center aligned"><i class="large icon" [ngClass]="visibleClass(slide)"></i></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|||||||
@ -1,4 +1,10 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
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({
|
@Component({
|
||||||
selector: 'app-slide-list',
|
selector: 'app-slide-list',
|
||||||
@ -7,9 +13,42 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
})
|
})
|
||||||
export class SlideListComponent implements OnInit {
|
export class SlideListComponent implements OnInit {
|
||||||
|
|
||||||
constructor() { }
|
constructor(private slideService: SlideService,
|
||||||
|
private titleService: Title,
|
||||||
ngOnInit() {
|
private route: ActivatedRoute) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.titleService.setTitle('Slides : MTAStv');
|
||||||
|
this.route.data.subscribe((data: {slides: Array<Slide>}) => this.slides = data.slides);
|
||||||
|
}
|
||||||
|
|
||||||
|
get slides(): Array<Slide> {
|
||||||
|
return this.slideService.slides;
|
||||||
|
}
|
||||||
|
|
||||||
|
set slides(slides: Array<Slide>) {
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
15
src/app/admin/slide-resolver.service.spec.ts
Normal file
15
src/app/admin/slide-resolver.service.spec.ts
Normal file
@ -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();
|
||||||
|
}));
|
||||||
|
});
|
||||||
22
src/app/admin/slide-resolver.service.ts
Normal file
22
src/app/admin/slide-resolver.service.ts
Normal file
@ -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<Slide> {
|
||||||
|
private apiEndPoint = environment.apiUrl + '/api/slide';
|
||||||
|
|
||||||
|
constructor(private httpClient: HttpClient) {}
|
||||||
|
|
||||||
|
public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<Slide> {
|
||||||
|
return this.getSlide(route.params['id']).toPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSlide(id: Number): Observable<Slide> {
|
||||||
|
return this.httpClient.get<Slide>(`${this.apiEndPoint}/${id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,12 +1,18 @@
|
|||||||
<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()">
|
||||||
<div class="six wide field">
|
<div class="six wide field">
|
||||||
<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" [(ngModel)]="team.name">
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="ui positive button">Save changes</button>
|
<div class="six wide field">
|
||||||
|
<label for="team_name"> </label>
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input type="checkbox" id="team_is_active" name="team_is_active"
|
||||||
|
[(ngModel)]="team.isActive">
|
||||||
|
<label for="team_is_active">Active</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h4 class="ui dividing header">Team members</h4>
|
<h4 class="ui dividing header">Team members</h4>
|
||||||
<div class="three inline fields">
|
<div class="three inline fields">
|
||||||
@ -14,24 +20,27 @@
|
|||||||
<button type="button" class="ui fluid button"
|
<button type="button" class="ui fluid button"
|
||||||
[class.positive]="canAddMember"
|
[class.positive]="canAddMember"
|
||||||
[class.disabled]="!canAddMember"
|
[class.disabled]="!canAddMember"
|
||||||
|
(keydown.enter)="handleEnter($event)"
|
||||||
(click)="addMember()">Add
|
(click)="addMember()">Add
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="five wide field">
|
<div class="five wide field">
|
||||||
<input type="text"
|
<input type="text" #signumInput
|
||||||
name="member_signum"
|
name="member_signum"
|
||||||
placeholder="Signum"
|
placeholder="Signum"
|
||||||
|
(keydown.enter)="handleEnter($event)"
|
||||||
[(ngModel)]="member.signum">
|
[(ngModel)]="member.signum">
|
||||||
</div>
|
</div>
|
||||||
<div class="nine wide field">
|
<div class="nine wide field">
|
||||||
<input type="text"
|
<input type="text"
|
||||||
name="member_name"
|
name="member_name"
|
||||||
placeholder="Display name"
|
placeholder="Display name"
|
||||||
|
(keydown.enter)="handleEnter($event)"
|
||||||
[(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">
|
<table class="ui celled definition table" *ngIf="team.members.length">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="collapsing"></th>
|
<th class="collapsing"></th>
|
||||||
@ -49,5 +58,11 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<button type="submit" class="ui button"
|
||||||
|
[class.positive]="canSave"
|
||||||
|
[class.disabled]="!canSave"><i class="save outline icon"></i>Save changes
|
||||||
|
</button>
|
||||||
|
<a class="ui button"
|
||||||
|
[routerLink]="['/admin/teams']"><i class="left angle icon"></i>Back to teams list</a>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -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 { 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';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-team-editor',
|
selector: 'app-team-editor',
|
||||||
@ -12,7 +13,8 @@ import { Member } from '../../shared/member';
|
|||||||
styleUrls: ['./team-editor.component.css']
|
styleUrls: ['./team-editor.component.css']
|
||||||
})
|
})
|
||||||
export class TeamEditorComponent implements OnInit {
|
export class TeamEditorComponent implements OnInit {
|
||||||
public team: Team = new Team();
|
@ViewChild('signumInput') signumInputElement: ElementRef;
|
||||||
|
public team: Team;
|
||||||
public member: Member = new Member();
|
public member: Member = new Member();
|
||||||
|
|
||||||
constructor(private teamService: TeamService,
|
constructor(private teamService: TeamService,
|
||||||
@ -23,31 +25,55 @@ export class TeamEditorComponent implements OnInit {
|
|||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.titleService.setTitle('Team editor : MTAStv');
|
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 {
|
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)
|
||||||
|
&& this.team.members.every(member => member.signum !== this.member.signum);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public addMember() {
|
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();
|
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) {
|
public removeMember(signum: String) {
|
||||||
if (confirm(`Remove the member with signum ${signum}?`)) {
|
if (confirm(`Remove the member with signum ${signum}?`)) {
|
||||||
this.team.members = this.team.members.filter(member => member.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() {
|
public saveTeam() {
|
||||||
this.teamService.update(this.team).subscribe(
|
if (this.canSave) {
|
||||||
() => this.router.navigate(['/admin/teams/list'])
|
this.teamService.persist(this.team).subscribe(
|
||||||
|
() => this.router.navigate(['/admin/teams'])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,21 +1,29 @@
|
|||||||
<div class="ui main container">
|
<div class="ui main container">
|
||||||
<h1 class="ui dividing header">Teams</h1>
|
<h1 class="ui dividing header">Teams</h1>
|
||||||
|
<a class="ui primary button"
|
||||||
|
[routerLink]="['/admin/team/new']"><i class="plus sign icon"></i>New team</a>
|
||||||
|
<a class="ui button"
|
||||||
|
[routerLink]="['/admin']"><i class="left angle icon"></i>Back to dashboard</a>
|
||||||
<table *ngIf="teams?.length" class="ui large padded celled definition table">
|
<table *ngIf="teams?.length" class="ui large padded celled definition table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th class="collapsing"></th>
|
||||||
<th class="clickable"><i class="large address book outline icon"></i>Team</th>
|
<th><i class="large address book outline icon"></i>Team</th>
|
||||||
<th class="clickable"><i class="large users icon"></i>Members</th>
|
<th><i class="large users icon"></i>Members</th>
|
||||||
|
<th class="collapsing"><i class="large check square outline icon"></i>Active</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let team of teams">
|
<tr *ngFor="let team of teams">
|
||||||
<td class="collapsing">
|
<td class="collapsing">
|
||||||
<a [routerLink]="['/admin/team/edit', team.id]" title="Change"><i
|
<a [routerLink]="['/admin/team/edit', team.id]" title="Change"><i
|
||||||
class="large fitted pencil alternate icon"></i></a>
|
class="large pencil alternate icon"></i></a>
|
||||||
|
<a title="Delete" (click)="delete(team)"><i
|
||||||
|
class="large red fitted trash alternate outline icon"></i></a>
|
||||||
</td>
|
</td>
|
||||||
<td>{{team.name}}</td>
|
<td>{{team.name}}</td>
|
||||||
<td>{{fancyMemberNames(team)}}</td>
|
<td>{{fancyMemberNames(team)}}</td>
|
||||||
|
<td class="center aligned"><i class="large icon" [ngClass]="activeClass(team)"></i></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@ -2,8 +2,8 @@ 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 { Team } from '../../shared/team';
|
|
||||||
import { TeamService } from '../../shared/service/team.service';
|
import { TeamService } from '../../shared/service/team.service';
|
||||||
|
import { Team } from '../../shared/team';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-team-list',
|
selector: 'app-team-list',
|
||||||
@ -33,4 +33,21 @@ export class TeamListComponent implements OnInit {
|
|||||||
public fancyMemberNames(team: Team): String {
|
public fancyMemberNames(team: Team): String {
|
||||||
return team.members.map(member => member.name).join(', ');
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
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 { Team } from '../shared/team';
|
import { Team } from '../shared/team';
|
||||||
import { environment } from '../../environments/environment';
|
import { environment } from '../../environments/environment';
|
||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
import { Observable } from 'rxjs/Observable';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TeamResolverService implements Resolve<Team> {
|
export class TeamResolverService implements Resolve<Team> {
|
||||||
|
|||||||
@ -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({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrls: ['./app.component.css']
|
styleUrls: ['./app.component.css']
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent implements OnInit, OnDestroy {
|
||||||
title = 'app';
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { HttpClientModule } from '@angular/common/http';
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { SuiModule } from 'ng2-semantic-ui';
|
import { SuiModule } from 'ng2-semantic-ui';
|
||||||
@ -11,6 +12,8 @@ import { SlideService } from './shared/service/slide.service';
|
|||||||
import { AdminModule } from './admin/admin.module';
|
import { AdminModule } from './admin/admin.module';
|
||||||
import { DisplayModule } from './display/display.module';
|
import { DisplayModule } from './display/display.module';
|
||||||
import { CommitTrackerService } from './shared/service/commit-tracker.service';
|
import { CommitTrackerService } from './shared/service/commit-tracker.service';
|
||||||
|
import { SettingsService } from './shared/service/settings.service';
|
||||||
|
import { SelfUpdaterService } from './shared/service/self-updater.service';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -18,6 +21,7 @@ import { CommitTrackerService } from './shared/service/commit-tracker.service';
|
|||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
|
BrowserAnimationsModule,
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
SuiModule,
|
SuiModule,
|
||||||
@ -25,7 +29,7 @@ import { CommitTrackerService } from './shared/service/commit-tracker.service';
|
|||||||
AdminModule,
|
AdminModule,
|
||||||
AppRoutingModule, // must be last RouterModule import for ** route to work
|
AppRoutingModule, // must be last RouterModule import for ** route to work
|
||||||
],
|
],
|
||||||
providers: [TeamService, SlideService, CommitTrackerService],
|
providers: [TeamService, SlideService, CommitTrackerService, SettingsService, SelfUpdaterService],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
})
|
})
|
||||||
export class AppModule {
|
export class AppModule {
|
||||||
|
|||||||
@ -3,15 +3,39 @@ import { Routes, RouterModule } from '@angular/router';
|
|||||||
|
|
||||||
import { CommitTrackerComponent } from './commit-tracker/commit-tracker.component';
|
import { CommitTrackerComponent } from './commit-tracker/commit-tracker.component';
|
||||||
import { CommitTrackerService } from '../shared/service/commit-tracker.service';
|
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 = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
|
path: 'slideshow/:id',
|
||||||
|
component: SlideShowComponent,
|
||||||
|
// canActivate: [AuthGuardService, RoleGuardService],
|
||||||
|
resolve: {
|
||||||
|
slide: SlideResolverService,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
autoSwitchable: true
|
||||||
|
}
|
||||||
|
}, {
|
||||||
path: 'commit-tracker',
|
path: 'commit-tracker',
|
||||||
component: CommitTrackerComponent,
|
component: CommitTrackerComponent,
|
||||||
// canActivate: [AuthGuardService, RoleGuardService],
|
// canActivate: [AuthGuardService, RoleGuardService],
|
||||||
resolve: {
|
resolve: {
|
||||||
commits: CommitTrackerService,
|
commits: CommitTrackerService,
|
||||||
},
|
},
|
||||||
|
data: {
|
||||||
|
autoSwitchable: true
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
path: 'settings',
|
||||||
|
component: SettingsComponent,
|
||||||
|
// canActivate: [AuthGuardService, RoleGuardService],
|
||||||
|
resolve: {
|
||||||
|
teams: TeamService,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,22 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
|
||||||
import { DisplayRoutingModule } from './display-routing.module';
|
import { DisplayRoutingModule } from './display-routing.module';
|
||||||
import { CommitTrackerComponent } from './commit-tracker/commit-tracker.component';
|
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({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
SuiModule,
|
||||||
DisplayRoutingModule
|
DisplayRoutingModule
|
||||||
],
|
],
|
||||||
declarations: [CommitTrackerComponent]
|
exports: [SlideComponent],
|
||||||
|
declarations: [CommitTrackerComponent, SettingsComponent, SlideComponent, SlideShowComponent]
|
||||||
})
|
})
|
||||||
export class DisplayModule { }
|
export class DisplayModule { }
|
||||||
|
|||||||
0
src/app/display/settings/settings.component.css
Normal file
0
src/app/display/settings/settings.component.css
Normal file
30
src/app/display/settings/settings.component.html
Normal file
30
src/app/display/settings/settings.component.html
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<div class="ui main container">
|
||||||
|
<h1 class="ui dividing header">TV settings</h1>
|
||||||
|
|
||||||
|
<form class="ui form" #teamSelectorForm (ngSubmit)="saveSettings()">
|
||||||
|
<div class="two fields">
|
||||||
|
<div class="six wide field">
|
||||||
|
<label for="team">Team name</label>
|
||||||
|
<sui-select class="selection"
|
||||||
|
id="team"
|
||||||
|
name="team"
|
||||||
|
[(ngModel)]="selectedTeam"
|
||||||
|
[options]="options"
|
||||||
|
labelField="name"
|
||||||
|
[isSearchable]="false"
|
||||||
|
#select>
|
||||||
|
<sui-select-option *ngFor="let team of teams" [value]="team"></sui-select-option>
|
||||||
|
</sui-select>
|
||||||
|
</div>
|
||||||
|
<div class="four wide field">
|
||||||
|
<label for="slide_interval">Slide duration: {{slideInterval}}ms</label>
|
||||||
|
<input id="slide_interval" name="slide_interval"
|
||||||
|
type="range" min="1000" max="120000" step="100"
|
||||||
|
[(ngModel)]="slideInterval">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="ui positive button"><i class="save outline icon"></i>Save</button>
|
||||||
|
<a class="ui button"
|
||||||
|
[routerLink]="['/admin']"><i class="left angle icon"></i>Back to dashboard</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
25
src/app/display/settings/settings.component.spec.ts
Normal file
25
src/app/display/settings/settings.component.spec.ts
Normal file
@ -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<SettingsComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ SettingsComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(SettingsComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
34
src/app/display/settings/settings.component.ts
Normal file
34
src/app/display/settings/settings.component.ts
Normal file
@ -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<Team> = [];
|
||||||
|
public selectedTeam: Team;
|
||||||
|
public slideInterval: Number;
|
||||||
|
|
||||||
|
constructor(private route: ActivatedRoute,
|
||||||
|
private settingsService: SettingsService,
|
||||||
|
private router: Router) {}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.route.data.subscribe((data: {teams: Array<Team>}) => {
|
||||||
|
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']);
|
||||||
|
}
|
||||||
|
}
|
||||||
0
src/app/display/slide-show/slide-show.component.css
Normal file
0
src/app/display/slide-show/slide-show.component.css
Normal file
1
src/app/display/slide-show/slide-show.component.html
Normal file
1
src/app/display/slide-show/slide-show.component.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<app-slide [data]="renderedSlide"></app-slide>
|
||||||
25
src/app/display/slide-show/slide-show.component.spec.ts
Normal file
25
src/app/display/slide-show/slide-show.component.spec.ts
Normal file
@ -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<SlideShowComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ SlideShowComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(SlideShowComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
39
src/app/display/slide-show/slide-show.component.ts
Normal file
39
src/app/display/slide-show/slide-show.component.ts
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
244
src/app/display/slide/slide.component.css
Normal file
244
src/app/display/slide/slide.component.css
Normal file
@ -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); }
|
||||||
1
src/app/display/slide/slide.component.html
Normal file
1
src/app/display/slide/slide.component.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<div class="present" [innerHTML]="data"></div>
|
||||||
25
src/app/display/slide/slide.component.spec.ts
Normal file
25
src/app/display/slide/slide.component.spec.ts
Normal file
@ -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<SlideComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ SlideComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(SlideComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
25
src/app/display/slide/slide.component.ts
Normal file
25
src/app/display/slide/slide.component.ts
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,11 +1,11 @@
|
|||||||
import { Injectable } from '@angular/core';
|
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 { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
import { Commit } from '../commit';
|
import { Commit } from '../commit';
|
||||||
import { TeamService } from './team.service';
|
import { SettingsService } from './settings.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CommitTrackerService implements Resolve<Array<Commit>> {
|
export class CommitTrackerService implements Resolve<Array<Commit>> {
|
||||||
@ -14,10 +14,13 @@ export class CommitTrackerService implements Resolve<Array<Commit>> {
|
|||||||
private cachedCommits: Array<Commit> = [];
|
private cachedCommits: Array<Commit> = [];
|
||||||
|
|
||||||
constructor(private httpClient: HttpClient,
|
constructor(private httpClient: HttpClient,
|
||||||
private teamService: TeamService) { }
|
private settingsService: SettingsService) { }
|
||||||
|
|
||||||
public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<Array<Commit>> {
|
public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<Array<Commit>> {
|
||||||
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<String>): Observable<Array<Commit>> {
|
public getTeamCommits(signums: Array<String>): Observable<Array<Commit>> {
|
||||||
|
|||||||
15
src/app/shared/service/self-updater.service.spec.ts
Normal file
15
src/app/shared/service/self-updater.service.spec.ts
Normal file
@ -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();
|
||||||
|
}));
|
||||||
|
});
|
||||||
39
src/app/shared/service/self-updater.service.ts
Normal file
39
src/app/shared/service/self-updater.service.ts
Normal file
@ -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<Number> {
|
||||||
|
return this.httpClient.get<Number>(this.locationService.prepareExternalUrl('/revision.json'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public checkAndReloadIfNecessary() {
|
||||||
|
if (!this.initFailed) {
|
||||||
|
this.getDeployedRevision().subscribe(revision => {
|
||||||
|
if (revision > this.appRevision) {
|
||||||
|
this.locationService.go('/');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/app/shared/service/settings.service.spec.ts
Normal file
15
src/app/shared/service/settings.service.spec.ts
Normal file
@ -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();
|
||||||
|
}));
|
||||||
|
});
|
||||||
40
src/app/shared/service/settings.service.ts
Normal file
40
src/app/shared/service/settings.service.ts
Normal file
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,8 +1,58 @@
|
|||||||
import { Injectable } from '@angular/core';
|
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()
|
@Injectable()
|
||||||
export class SlideService {
|
export class SlideService implements Resolve<Array<Slide>>{
|
||||||
|
|
||||||
constructor() { }
|
private apiEndPoint = environment.apiUrl + '/api/slide';
|
||||||
|
private cachedSlides: Array<Slide> = [];
|
||||||
|
|
||||||
|
constructor(private httpClient: HttpClient) {}
|
||||||
|
|
||||||
|
private static prepareSlideData(slide: Slide) {
|
||||||
|
const slideToSave = <any>Object.assign({}, slide);
|
||||||
|
slideToSave.team = slideToSave.team.id === null
|
||||||
|
? null
|
||||||
|
: slideToSave.team.id;
|
||||||
|
return slideToSave;
|
||||||
|
}
|
||||||
|
|
||||||
|
public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<Array<Slide>> {
|
||||||
|
return this.list().toPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
public list(): Observable<Array<Slide>> {
|
||||||
|
return this.httpClient.get<Array<Slide>>(this.apiEndPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public persist(slide: Slide): Observable<Slide> {
|
||||||
|
return slide.id === null
|
||||||
|
? this.create(slide)
|
||||||
|
: this.update(slide);
|
||||||
|
}
|
||||||
|
|
||||||
|
public create(slide: Slide): Observable<Slide> {
|
||||||
|
return this.httpClient.post<Slide>(this.apiEndPoint, SlideService.prepareSlideData(slide));
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(slide: Slide): Observable<Slide> {
|
||||||
|
return this.httpClient.put<Slide>(`${this.apiEndPoint}/${slide.id.toString()}`, SlideService.prepareSlideData(slide));
|
||||||
|
}
|
||||||
|
|
||||||
|
public delete(slide: Slide): Observable<boolean> {
|
||||||
|
return this.httpClient.delete<boolean>(`${this.apiEndPoint}/${slide.id.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
get slides(): Array<Slide> {
|
||||||
|
return this.cachedSlides;
|
||||||
|
}
|
||||||
|
|
||||||
|
set slides(slides: Array<Slide>) {
|
||||||
|
this.cachedSlides = slides;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,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 persist(team: Team): Observable<Team> {
|
||||||
|
return team.id === null
|
||||||
|
? this.create(team)
|
||||||
|
: this.update(team);
|
||||||
|
}
|
||||||
|
|
||||||
public create(team: Team) {
|
public create(team: Team) {
|
||||||
return this.httpClient.post<Team>(this.apiEndPoint, team);
|
return this.httpClient.post<Team>(this.apiEndPoint, team);
|
||||||
}
|
}
|
||||||
@ -41,8 +47,4 @@ export class TeamService implements Resolve<Array<Team>> {
|
|||||||
set teams(teams: Array<Team>) {
|
set teams(teams: Array<Team>) {
|
||||||
this.cachedTeams = teams;
|
this.cachedTeams = teams;
|
||||||
}
|
}
|
||||||
|
|
||||||
get teamMembers(): Array<string> {
|
|
||||||
return ['eztoli', 'etamecs', 'esteist', 'emrtsis', 'erudvel'];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
43
src/app/shared/slide-in-out-animation.ts
Normal file
43
src/app/shared/slide-in-out-animation.ts
Normal file
@ -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%'
|
||||||
|
}))
|
||||||
|
])
|
||||||
|
]);
|
||||||
@ -1,11 +1,11 @@
|
|||||||
import { Team } from './team';
|
import { Team } from './team';
|
||||||
|
|
||||||
export class Slide {
|
export class Slide {
|
||||||
id: Number;
|
id: Number = null;
|
||||||
title: String;
|
title: String = '';
|
||||||
team: Team;
|
team: Team = null;
|
||||||
slideData: String;
|
slideData: String = '';
|
||||||
isVisible: boolean;
|
isVisible = true;
|
||||||
createdAt: String;
|
createdAt: String = null;
|
||||||
updatedAt: String;
|
updatedAt: String = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,9 @@
|
|||||||
/* You can add global styles to this file, and also import other style files */
|
/* You can add global styles to this file, and also import other style files */
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: calc(100% - 2em);
|
||||||
|
}
|
||||||
|
|
||||||
.main.container {
|
.main.container {
|
||||||
margin-top: 2em;
|
margin-top: 2em;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user