diff --git a/angular.json b/angular.json index 103db74..fde59d5 100644 --- a/angular.json +++ b/angular.json @@ -8,7 +8,11 @@ "sourceRoot": "src", "projectType": "application", "prefix": "app", - "schematics": {}, + "schematics": { + "@schematics/angular:component": { + "styleext": "scss" + } + }, "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", @@ -119,4 +123,4 @@ } }, "defaultProject": "openlp-remote" -} \ No newline at end of file +} diff --git a/package.json b/package.json index b730a36..e9cfb6d 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,13 @@ "private": true, "dependencies": { "@angular/animations": "^6.0.0", + "@angular/cdk": "^6.4.2", "@angular/common": "^6.0.0", "@angular/compiler": "^6.0.0", "@angular/core": "^6.0.0", "@angular/forms": "^6.0.0", "@angular/http": "^6.0.0", + "@angular/material": "^6.4.2", "@angular/platform-browser": "^6.0.0", "@angular/platform-browser-dynamic": "^6.0.0", "@angular/router": "^6.0.0", @@ -25,10 +27,9 @@ "zone.js": "^0.8.26" }, "devDependencies": { - "@angular/compiler-cli": "^6.0.0", "@angular-devkit/build-angular": "~0.6.0", - "typescript": "~2.7.2", "@angular/cli": "~6.0.0", + "@angular/compiler-cli": "^6.0.0", "@angular/language-service": "^6.0.0", "@types/jasmine": "~2.8.6", "@types/jasminewd2": "~2.0.3", @@ -43,6 +44,7 @@ "karma-jasmine-html-reporter": "^0.2.2", "protractor": "~5.3.0", "ts-node": "~5.0.1", - "tslint": "~5.9.1" + "tslint": "~5.9.1", + "typescript": "~2.7.2" } -} \ No newline at end of file +} diff --git a/src/app/alert.component.ts b/src/app/alert.component.ts new file mode 100644 index 0000000..2e0e684 --- /dev/null +++ b/src/app/alert.component.ts @@ -0,0 +1,30 @@ +import { Component } from '@angular/core'; + + +import { OpenLPService } from './openlp.service'; + +@Component({ +selector: 'openlp-remote-alert', +template: ` +

Send an Alert

+ + +
+ +
+`, +providers: [OpenLPService] +}) + +export class OpenLPAlertComponent { + + public alert: string; + + constructor(private openlpService: OpenLPService) { } + + onSubmit() { + console.log('submitted: ', this.alert); + this.openlpService.showAlert(this.alert); + this.alert = ''; + } +} diff --git a/src/app/app.component.css b/src/app/app.component.css deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/app.component.html b/src/app/app.component.html index fa2706a..b9cddfc 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,20 +1,49 @@ - -
-

- Welcome to {{ title }}! -

- Angular Logo + + + + Service + Slides + Alerts + Search + + +
+
+ + + OpenLP Remote + +
+
+ +
+
+ + + + + + + + + + +
-

Here are some links to help you start:

- - +
diff --git a/src/app/app.component.scss b/src/app/app.component.scss new file mode 100644 index 0000000..5df9b8e --- /dev/null +++ b/src/app/app.component.scss @@ -0,0 +1,34 @@ +mat-sidenav { + background: white; +} +.all-wrap { + min-height: 100vh; + } + +.page-wrap { +display: flex; +flex-direction: column; +min-height: 100vh; +} + +.content { +flex: 1; +} + +.footer { + display: flex; + flex-direction: row; + justify-content: space-evenly; +} + +/* +* Make the Component injected by Router Outlet full height: +*/ +main { + display: flex; + flex-direction: column; + > *:not(router-outlet) { + flex: 1; + display: block; + } +} \ No newline at end of file diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 7b0f672..8acfef8 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,10 +1,55 @@ import { Component } from '@angular/core'; +import { State } from './state'; +import { OpenLPService } from './openlp.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html', - styleUrls: ['./app.component.css'] + styleUrls: ['./app.component.scss'] }) export class AppComponent { title = 'app'; + + state: State = new State(); + + constructor(private openlpService: OpenLPService) { + openlpService.stateChanged$.subscribe(item => this.state = item); + openlpService.getServiceItems().subscribe(result => { + console.log(result); + }); + } + /* + nextItem() { + this.openlpService.nextItem(); + } + + previousItem() { + this.openlpService.previousItem(); + } + + nextSlide() { + this.openlpService.nextSlide(); + } + + previousSlide() { + this.openlpService.previousSlide(); + } + + blankDisplay() { + this.openlpService.blankDisplay(); + } + + themeDisplay() { + this.openlpService.themeDisplay(); + } + + desktopDisplay() { + this.openlpService.desktopDisplay(); + } + + showDisplay() { + this.openlpService.showDisplay(); + } + */ + } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index f657163..524e318 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,16 +1,53 @@ import { BrowserModule } from '@angular/platform-browser'; +import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; + import { NgModule } from '@angular/core'; +import {MatListModule} from '@angular/material/list'; +import {MatSidenavModule} from '@angular/material/sidenav'; +import {MatIconModule} from '@angular/material/icon'; +import {MatToolbarModule} from '@angular/material/toolbar'; +import {MatGridListModule} from '@angular/material/grid-list'; +import {MatFormFieldModule} from '@angular/material/form-field'; +import {MatSelectModule} from '@angular/material/select'; +import {MatButtonModule} from '@angular/material/button'; + import { AppComponent } from './app.component'; +import { OpenLPService } from './openlp.service'; +import { HttpClientModule } from '@angular/common/http'; +import { AppRoutingModule } from './app.routing'; +import { OpenLPServiceComponent } from './service.component'; +import { OpenLPAlertComponent } from './alert.component'; +import { OpenLPSearchComponent } from './search.component'; +import { OpenLPSlidesComponent } from './slides.component'; +import { FormsModule } from '@angular/forms'; @NgModule({ declarations: [ - AppComponent + AppComponent, + OpenLPServiceComponent, + OpenLPAlertComponent, + OpenLPSearchComponent, + OpenLPSlidesComponent ], imports: [ - BrowserModule + BrowserModule, + BrowserAnimationsModule, + HttpClientModule, + AppRoutingModule, + MatListModule, + MatSidenavModule, + MatIconModule, + MatToolbarModule, + MatGridListModule, + FormsModule, + MatFormFieldModule, + MatSelectModule, + MatButtonModule + ], + providers: [ + OpenLPService ], - providers: [], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/src/app/app.routing.ts b/src/app/app.routing.ts new file mode 100644 index 0000000..4a714d6 --- /dev/null +++ b/src/app/app.routing.ts @@ -0,0 +1,38 @@ +import { ModuleWithProviders, NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { AppComponent } from './app.component'; +import { OpenLPSearchComponent } from './search.component'; +import { OpenLPAlertComponent } from './alert.component'; +import { OpenLPSlidesComponent } from './slides.component'; +import { OpenLPServiceComponent } from './service.component'; + +const routes: Routes = [ +{ +path: '', +redirectTo: '/service', +pathMatch: 'full' +}, +{ +path: 'service', +component: OpenLPServiceComponent +}, +{ +path: 'slides', +component: OpenLPSlidesComponent +}, +// { +// path: 'alerts', +// component: OpenLPAlertComponent +// }, +// { +// path: 'search', +// component: OpenLPSearchComponent +// } +]; +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule] +}) + +export class AppRoutingModule {} \ No newline at end of file diff --git a/src/app/openlp.service.ts b/src/app/openlp.service.ts new file mode 100644 index 0000000..c0986af --- /dev/null +++ b/src/app/openlp.service.ts @@ -0,0 +1,221 @@ +import { Injectable, EventEmitter } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { URLSearchParams, Http } from '@angular/http'; + +import { State } from './state'; +import { Slide } from './slide'; +import { ServiceItem } from './service_item'; +import { Observable } from 'rxjs'; +import { map, take } from 'rxjs/operators'; +import { SlideOuterResponse } from './responses'; + +let deserialize = (json, cls) => { + var inst = new cls(); + for(var p in json) { + if(!json.hasOwnProperty(p)) { + continue; + } + inst[p] = json[p]; + } + return inst; + } + +let buildTextParams = id => { + let params: URLSearchParams = new URLSearchParams(); + params.set('data', '{"request": {"text": "' + id + '"}}'); + return {search: params} +} + +let buildNumberParams = id => { + let params: URLSearchParams = new URLSearchParams(); + params.set('data', '{"request": {"id": ' + id + '}}'); + return {search: params} +} + +@Injectable() +export class OpenLPService { + private apiURL: string = 'http://localhost:4316'; + + public stateChanged$: EventEmitter; + + constructor(private http: HttpClient) { + this.stateChanged$ = new EventEmitter(); + let state:State = null; + let ws:WebSocket = new WebSocket('ws://localhost:4317/poll') + ws.onmessage = (event) => { + let reader = new FileReader() + reader.onload = () => { + state = deserialize(JSON.parse(reader.result).results, State); + this.stateChanged$.emit(state); + } + reader.readAsText(event.data); + } + } + + getItemSlides(): Observable { + return this.http.get('http://localhost:4316/controller/live/text') + .pipe( + take(1), + map(result => result.results.slides)); + } + + // getItemSlides() { + // return this.http.get('http://localhost:4316/api/controller/live/text') + // .toPromise() + // .then(response => { + // let slides:Slide[] = []; + // response.json().results.slides.forEach(item => { + // let slide = deserialize(item, Slide); + // slide.lines = slide.text.split('\n'); + // slides.push(slide); + // }); + // return slides; + // }) + // .catch(this.handleError); + // } + + getServiceItems(): Observable { + return this.http.get('http://localhost:4316/service/list'); + } + + + // getServiceItems() { + // return this.http.get('http://localhost:4316/api/service/list') + // .toPromise() + // .then(response => { + // let serviceItems:ServiceItem[] = []; + // response.json().results.items.forEach(item => serviceItems.push(deserialize(item, ServiceItem))); + // return serviceItems; + // }) + // .catch(this.handleError); + // } + + sendItemLive(plugin, id) {} + showAlert(text) {} + search(plugin, text) {} + addItemToService(plugin, id) {} + + getSearchablePlugins() { + return this.http.get(`${this.apiURL}/plugin/search`); + } + + // getSearchablePlugins() { + // return this.http.get('http://localhost:4316/plugin/search') + // .toPromise() + // .then(response => response.json().results.items) + // .catch(this.handleError); + // } + + setServiceItem(id:number) { + } + + + // setServiceItem(id:number) { + // this.http.get('http://localhost:4316/service/set', buildNumberParams(id)) + // .toPromise() + // .then(response => console.log(response.json().results)) + // .catch(this.dropError); + // } + + /* + setSlide(id) { + this.http.get('http://localhost:4316/controller/live/set', buildNumberParams(id)) + .toPromise() + .then(response => console.log(response.json().results)) + .catch(this.dropError); + } + + nextItem() { + this.http.get('http://localhost:4316/service/next') + .toPromise() + .then(response => console.log(response.json().results)) + .catch(this.dropError); + } + + previousItem() { + this.http.get('http://localhost:4316/service/previous') + .toPromise() + .then(response => console.log(response.json().results)) + .catch(this.dropError); + } + + nextSlide() { + this.http.get('http://localhost:4316/controller/live/next') + .toPromise() + .then(response => console.log(response.json().results)) + .catch(this.dropError); + } + + previousSlide() { + this.http.get('http://localhost:4316/controller/live/previous') + .toPromise() + .then(response => console.log(response.json().results)) + .catch(this.dropError); + } + + blankDisplay() { + this.http.get('http://localhost:4316/display/blank') + .toPromise() + .then(response => console.log(response.json().results)) + .catch(this.dropError); + } + + themeDisplay() { + this.http.get('http://localhost:4316/display/theme') + .toPromise() + .then(response => console.log(response.json().results)) + .catch(this.dropError); + } + + desktopDisplay() { + this.http.get('http://localhost:4316/display/desktop') + .toPromise() + .then(response => console.log(response.json().results)) + .catch(this.dropError); + } + + showDisplay() { + this.http.get('http://localhost:4316/display/show') + .toPromise() + .then(response => console.log(response.json().results)) + .catch(this.dropError); + } + + showAlert(text) { + this.http.get('http://localhost:4316/alert', buildTextParams(text)) + .toPromise() + .then(response => console.log(response.json().results)) + .catch(this.dropError); + } + + search(plugin, text) { + return this.http.get('http://localhost:4316/' + plugin + '/search', buildTextParams(text)) + .toPromise() + .then(response => response.json().results.items) + .catch(this.handleError); + } + + sendItemLive(plugin, id) { + this.http.get('http://localhost:4316/' + plugin + '/live', buildNumberParams(id)) + .toPromise() + .then(response => console.log(response)) + .catch(this.dropError); + } + + addItemToService(plugin, id) { + this.http.get('http://localhost:4316/' + plugin + '/add', buildNumberParams(id)) + .toPromise() + .then(response => console.log(response)) + .catch(this.dropError); + } + + private dropError(error: any) { + console.error('An error occurred', error); + } + + private handleError(error: any) { + console.error('An error occurred', error); + return Promise.reject(error.message || error); + } + */ +} diff --git a/src/app/responses.ts b/src/app/responses.ts new file mode 100644 index 0000000..582010e --- /dev/null +++ b/src/app/responses.ts @@ -0,0 +1,10 @@ +import { Slide } from "./slide"; + +interface SlideResponse { + item: string; + slides: Slide[]; +} + +export interface SlideOuterResponse { + results: SlideResponse; +} \ No newline at end of file diff --git a/src/app/search.component.ts b/src/app/search.component.ts new file mode 100644 index 0000000..c7049c0 --- /dev/null +++ b/src/app/search.component.ts @@ -0,0 +1,76 @@ +import { Component, OnInit } from '@angular/core'; +import { OpenLPService } from './openlp.service'; + +@Component({ +selector: 'openlp-remote-search', +template: ` +

Search

+
+ + + + {{plugin.name}} + + +
+ + +
+ +
+
+

Search Results:

+ + + + + + +
{{item[1]}}
+
+`, +providers: [OpenLPService] +}) + + +export class OpenLPSearchComponent implements OnInit { + + public searchPlugins: OpenLPPlugin[] = null; + public searchText = null; + public searchResults = null; + public selectedPlugin: string; + public currentPlugin: string; + + constructor(private openlpService: OpenLPService) {} + + onSubmit() { + this.currentPlugin = this.selectedPlugin; + // this.openlpService.search(this.currentPlugin, this.searchText).then(items => this.searchResults = items); + } + + sendLive(id) { + this.openlpService.sendItemLive(this.currentPlugin, id); + } + + addToService(id) { + this.openlpService.addItemToService(this.currentPlugin, id); + } + + ngOnInit() { + this.getSearchablePlugins(); + } + + getSearchablePlugins() { + // this.openlpService.getSearchablePlugins().then(items => { + // this.searchPlugins = []; + // for (var i = items.length - 1; i >= 0; i--) { + // this.searchPlugins.push({id: items[i][0], name: items[i][1]}) + // } + // }); + } +} + +interface OpenLPPlugin { + id: string; + name: string; +} diff --git a/src/app/service.component.ts b/src/app/service.component.ts new file mode 100644 index 0000000..dce2f68 --- /dev/null +++ b/src/app/service.component.ts @@ -0,0 +1,40 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router' + +import { OpenLPService } from './openlp.service'; + +@Component({ +selector: 'openlp-remote-service', +template: ` +

Service items:

+ +`, +providers: [OpenLPService] +}) + +export class OpenLPServiceComponent implements OnInit { + items = null; + ngOnInit() { + this.getServiceItems(); + } + + onItemSelected(item) { + this.openlpService.setServiceItem(item); + this.router.navigate(['slides']); + } + + getServiceItems() { + // this.openlpService.getServiceItems().then(items => this.items = items); + } + + constructor(private openlpService: OpenLPService, private router: Router) { + openlpService.stateChanged$.subscribe(item => this.getServiceItems()); + } + +} diff --git a/src/app/service_item.ts b/src/app/service_item.ts new file mode 100644 index 0000000..b3ea666 --- /dev/null +++ b/src/app/service_item.ts @@ -0,0 +1,7 @@ +export class ServiceItem { + id: string; + notes: string; + plugin: string; + selected: boolean; + title: string; +} diff --git a/src/app/slide.ts b/src/app/slide.ts new file mode 100644 index 0000000..25abe9f --- /dev/null +++ b/src/app/slide.ts @@ -0,0 +1,7 @@ +export class Slide { + selected: boolean; + html: string; + tag: string; + text: string; + lines: string[]; +} \ No newline at end of file diff --git a/src/app/slides.component.ts b/src/app/slides.component.ts new file mode 100644 index 0000000..02e311c --- /dev/null +++ b/src/app/slides.component.ts @@ -0,0 +1,41 @@ +import { Component, OnInit } from '@angular/core'; + + +import { OpenLPService } from './openlp.service'; + +@Component({ +selector: 'openlp-remote-slides', +template: ` +

Slides:

+
+ + + {{ slide.tag }} +

+ {{line}} +

+
+
+
+`, +providers: [OpenLPService] +}) + +export class OpenLPSlidesComponent implements OnInit { + slides = null; + ngOnInit() { + this.getSlides(); + } + + onSlideSelected(item) { + // this.openlpService.setSlide(item); + } + + getSlides() { + // this.openlpService.getItemSlides().then(slides=> {this.slides = slides;console.log(slides);}); + } + + constructor(private openlpService: OpenLPService) { + openlpService.stateChanged$.subscribe(item => this.getSlides()); + } +} diff --git a/src/app/state.ts b/src/app/state.ts new file mode 100644 index 0000000..2251b30 --- /dev/null +++ b/src/app/state.ts @@ -0,0 +1,12 @@ +export class State { + isAuthorized: boolean; + version: number; + slide: number; + display: boolean; + isSecure: boolean; + blank: boolean; + twelve: boolean; + theme: boolean; + + live = () => {return !(this.blank || this.display || this.theme);} +} \ No newline at end of file diff --git a/src/assets/images/loading.png b/src/assets/images/loading.png new file mode 100644 index 0000000..0adfeea Binary files /dev/null and b/src/assets/images/loading.png differ diff --git a/src/index.html b/src/index.html index 722017b..4a2d2fa 100644 --- a/src/index.html +++ b/src/index.html @@ -7,8 +7,16 @@ + - + +
+
+ +

Loading...

+
+
+
diff --git a/src/styles.css b/src/styles.css index 90d4ee0..3244c13 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1 +1,24 @@ /* You can add global styles to this file, and also import other style files */ +@import "~@angular/material/prebuilt-themes/indigo-pink.css"; + + +* { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +mat-sidenav-layout { + background: rgba(0,0,0,0.03); +} +mat-sidenav { + width: 200px; +} + +.displayButton .active { + background: 'teal'; +} + +.content { + margin: 20px; +} + diff --git a/yarn.lock b/yarn.lock index 580c127..a4a675b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -97,6 +97,12 @@ dependencies: tslib "^1.9.0" +"@angular/cdk@^6.4.2": + version "6.4.2" + resolved "https://registry.yarnpkg.com/@angular/cdk/-/cdk-6.4.2.tgz#e623cdbbe6258980b0fa93beafce0ea7c4e2c17b" + dependencies: + tslib "^1.7.1" + "@angular/cli@~6.0.0": version "6.0.8" resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-6.0.8.tgz#65070958b944be30053232c51f8449b7ddd4d92a" @@ -157,6 +163,14 @@ version "6.1.1" resolved "https://registry.yarnpkg.com/@angular/language-service/-/language-service-6.1.1.tgz#131a320be957a02dcf710d9242bd8e43b7c24c3c" +"@angular/material@^6.4.2": + version "6.4.2" + resolved "https://registry.yarnpkg.com/@angular/material/-/material-6.4.2.tgz#f39529d56bad97f0470b886c3f30591b9d3a7d05" + dependencies: + tslib "^1.7.1" + optionalDependencies: + parse5 "^5.0.0" + "@angular/platform-browser-dynamic@^6.0.0": version "6.1.1" resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-6.1.1.tgz#2505f463d29827256944c1d134cd8519e7f954cd" @@ -4436,6 +4450,10 @@ parse5@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" +parse5@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.0.0.tgz#4d02710d44f3c3846197a11e205d4ef17842b81a" + parsejson@0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/parsejson/-/parsejson-0.0.3.tgz#ab7e3759f209ece99437973f7d0f1f64ae0e64ab" @@ -5916,7 +5934,7 @@ tsickle@^0.32.1: source-map "^0.6.0" source-map-support "^0.5.0" -tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0: +tslib@^1.7.1, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"