Update the UI

merge-requests/5/head
Raoul Snyman 2019-11-07 18:02:26 +00:00
parent f3ffb599a9
commit 642e0ebf04
16 changed files with 204 additions and 138 deletions

View File

@ -1,4 +1,10 @@
<mat-sidenav-container class="all-wrap" fullscreen>
<mat-toolbar color="primary">
<mat-toolbar-row>
<button mat-icon-button (click)="menu.toggle()"><mat-icon>menu</mat-icon></button>
<span class="page-title">{{pageTitle}}</span>
</mat-toolbar-row>
</mat-toolbar>
<mat-sidenav-container>
<mat-sidenav #menu mode="over">
<mat-nav-list>
<a mat-list-item (click)="menu.close()" routerLink="/service">Service</a>
@ -13,51 +19,43 @@
<mat-slide-toggle color="primary" [checked]="fastSwitching" (change)="sliderChanged($event)">Fast switching</mat-slide-toggle>
</mat-nav-list>
</mat-sidenav>
<div class="page-wrap">
<header>
<mat-toolbar style="background-color: #64aef3;">
<button mat-icon-button (click)="menu.toggle()"><mat-icon>menu</mat-icon></button>
<span>OpenLP Remote</span>
<span class="filler"></span>
<button *ngIf="showLogin" mat-button (click)="login()">Login</button>
</mat-toolbar>
</header>
<main class="content">
<router-outlet></router-outlet>
</main>
<footer>
<mat-toolbar class="footer">
<button mat-icon-button (click)="previousItem()" matTooltip="Previous item">
<mat-icon>first_page</mat-icon>
</button>
<button mat-icon-button (click)="nextItem()" matTooltip="Next item">
<mat-icon>last_page</mat-icon>
</button>
<button mat-icon-button (click)="previousSlide()" matTooltip="Previous slide">
<mat-icon>navigate_before</mat-icon>
</button>
<button mat-icon-button (click)="nextSlide()" matTooltip="Next slide">
<mat-icon>navigate_next</mat-icon>
</button>
<button mat-icon-button (click)="blankDisplay()" class="displayButton" [class.active]="state.blank" [disabled]="state.blank" matTooltip="Show black">
<mat-icon>videocam_off</mat-icon>
</button>
<button mat-icon-button (click)="themeDisplay()" class="displayButton" [class.active]="state.theme" [disabled]="state.theme" matTooltip="Show background">
<mat-icon>wallpaper</mat-icon>
</button>
<button mat-icon-button (click)="desktopDisplay()" class="displayButton" [class.active]="state.display" [disabled]="state.display" matTooltip="Show Desktop">
<mat-icon>desktop_windows</mat-icon>
</button>
<button mat-icon-button (click)="showDisplay()" class="displayButton" [class.active]="state.display" [disabled]="state.live()" matTooltip="Show Presentation">
<mat-icon>videocam</mat-icon>
</button>
</mat-toolbar>
<mat-toolbar *ngIf="fastSwitching" class="fast-access">
<button mat-icon-button routerLink="/service"><mat-icon>list</mat-icon></button>
<button mat-icon-button routerLink="/slides"><mat-icon>collections</mat-icon></button>
<button mat-icon-button routerLink="/alerts"><mat-icon>error</mat-icon></button>
<button mat-icon-button routerLink="/search"><mat-icon>search</mat-icon></button>
<mat-sidenav-content>
<main class="content">
<router-outlet></router-outlet>
</main>
<footer>
<mat-toolbar class="footer">
<button mat-icon-button (click)="previousItem()" matTooltip="Previous item">
<mat-icon>first_page</mat-icon>
</button>
<button mat-icon-button (click)="nextItem()" matTooltip="Next item">
<mat-icon>last_page</mat-icon>
</button>
<button mat-icon-button (click)="previousSlide()" matTooltip="Previous slide">
<mat-icon>navigate_before</mat-icon>
</button>
<button mat-icon-button (click)="nextSlide()" matTooltip="Next slide">
<mat-icon>navigate_next</mat-icon>
</button>
<button mat-icon-button (click)="blankDisplay()" class="displayButton" [class.active]="state.blank" [disabled]="state.blank" matTooltip="Show black">
<mat-icon>videocam_off</mat-icon>
</button>
<button mat-icon-button (click)="themeDisplay()" class="displayButton" [class.active]="state.theme" [disabled]="state.theme" matTooltip="Show background">
<mat-icon>wallpaper</mat-icon>
</button>
<button mat-icon-button (click)="desktopDisplay()" class="displayButton" [class.active]="state.display" [disabled]="state.display" matTooltip="Show Desktop">
<mat-icon>desktop_windows</mat-icon>
</button>
<button mat-icon-button (click)="showDisplay()" class="displayButton" [class.active]="state.display" [disabled]="state.live()" matTooltip="Show Presentation">
<mat-icon>videocam</mat-icon>
</button>
</mat-toolbar>
</footer>
</div>
<mat-toolbar *ngIf="fastSwitching" class="fast-access">
<button mat-icon-button routerLink="/service"><mat-icon>list</mat-icon></button>
<button mat-icon-button routerLink="/slides"><mat-icon>collections</mat-icon></button>
<button mat-icon-button routerLink="/alerts"><mat-icon>error</mat-icon></button>
<button mat-icon-button routerLink="/search"><mat-icon>search</mat-icon></button>
</mat-toolbar>
</footer>
</mat-sidenav-content>
</mat-sidenav-container>

View File

@ -1,47 +1,36 @@
mat-sidenav {
background: white;
}
.all-wrap {
min-height: 100vh;
}
.page-wrap {
display: flex;
flex-direction: column;
min-height: 100vh;
background: white;
}
.filler {
flex-grow: 1;
}
.content {
flex: 1;
mat-sidenav-container {
min-height: 100vh;
}
mat-slide-toggle {
margin-left: 1rem;
margin-top: 0.8rem;
margin-left: 1rem;
font-size: 80%;
}
.fast-access {
display: flex;
justify-content: space-around;
display: flex;
justify-content: space-around;
}
.footer {
display: flex;
flex-direction: row;
justify-content: space-evenly;
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;
}
}
display: flex;
flex-direction: column;
> *:not(router-outlet) {
flex: 1;
display: block;
}
}

View File

@ -1,7 +1,9 @@
import { Component, OnInit } from '@angular/core';
import { MatSlideToggleChange, MatDialog } from '@angular/material';
import { State } from './responses';
import { OpenLPService } from './openlp.service';
import { MatSlideToggleChange, MatDialog } from '@angular/material';
import { PageTitleService } from './page-title.service';
import { LoginComponent } from './components/login/login.component';
@Component({
@ -13,8 +15,11 @@ export class AppComponent implements OnInit {
fastSwitching = false;
state = new State();
showLogin = false;
pageTitle = 'OpenLP Remote';
constructor(private openlpService: OpenLPService, private dialog: MatDialog) {
constructor(private pageTitleService: PageTitleService, private openlpService: OpenLPService,
private dialog: MatDialog) {
pageTitleService.pageTitleChanged$.subscribe(pageTitle => this.pageTitle = pageTitle);
openlpService.stateChanged$.subscribe(item => this.state = item);
}

View File

@ -1,5 +1,5 @@
import { BrowserModule } from '@angular/platform-browser';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import { BrowserModule, Title } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
@ -18,6 +18,7 @@ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { AppComponent } from './app.component';
import { PageTitleService } from './page-title.service';
import { OpenLPService } from './openlp.service';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app.routing';
@ -68,7 +69,9 @@ import { LoginComponent } from './components/login/login.component';
MatSnackBarModule
],
providers: [
OpenLPService
PageTitleService,
OpenLPService,
Title
],
entryComponents: [
LoginComponent

View File

@ -1,9 +1,9 @@
import { Component } from '@angular/core';
import { OpenLPService } from '../../openlp.service';
import { MatSnackBar } from '@angular/material';
import { PageTitleService } from '../../page-title.service';
import { OpenLPService } from '../../openlp.service';
@Component({
selector: 'openlp-alert',
templateUrl: './alert.component.html',
@ -15,7 +15,10 @@ export class AlertComponent {
public alert: string;
constructor(private openlpService: OpenLPService, private snackBar: MatSnackBar) { }
constructor(private pageTitleService: PageTitleService, private openlpService: OpenLPService,
private snackBar: MatSnackBar) {
pageTitleService.changePageTitle('Alerts');
}
onSubmit() {
this.openlpService.showAlert(this.alert).subscribe(res => this.snackBar.open('Alert submitted', '', {duration: 2000}));

View File

@ -1,5 +1,7 @@
import { Component, OnInit } from '@angular/core';
import { OpenLPService } from '../../openlp.service';
import { PageTitleService } from '../../page-title.service';
import { PluginDescription } from '../../responses';
@Component({
@ -8,17 +10,16 @@ import { PluginDescription } from '../../responses';
styleUrls: ['./search.component.scss'],
providers: [OpenLPService]
})
export class SearchComponent implements OnInit {
public searchPlugins: PluginDescription[] = [];
public searchText = null;
public searchResults = null;
public selectedPlugin = 'songs';
public currentPlugin: string;
constructor(private openlpService: OpenLPService) {}
constructor(private pageTitleService: PageTitleService, private openlpService: OpenLPService) {
pageTitleService.changePageTitle('Search');
}
onSubmit() {
this.currentPlugin = this.selectedPlugin;

View File

@ -1,9 +1,3 @@
<h3>Service items:</h3>
<div>
<mat-nav-list>
<a mat-list-item *ngFor="let item of items;let counter = index;" (click)="onItemSelected(counter)" [class.selected]="item.selected">
<mat-icon mat-list-icon>{{ getIcon(item) }}</mat-icon>
<p mat-line>{{item.title}}<p>
</a>
</mat-nav-list>
</div>
<mat-card *ngFor="let item of items; let counter = index;" (click)="onItemSelected(counter)" [tabindex]="counter" class="service-item">
<mat-icon>{{ getIcon(item) }}</mat-icon> {{ item.title }}
</mat-card>

View File

@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { OpenLPService } from '../../openlp.service';
import { PageTitleService } from '../../page-title.service';
import { ServiceItem } from '../../responses';
@Component({
@ -13,6 +14,7 @@ import { ServiceItem } from '../../responses';
export class ServiceComponent implements OnInit {
items: ServiceItem[] = [];
ngOnInit() {
this.getServiceItems();
}
@ -26,7 +28,9 @@ export class ServiceComponent implements OnInit {
this.openlpService.getServiceItems().subscribe(items => this.items = items);
}
constructor(private openlpService: OpenLPService, private router: Router) {
constructor(private pageTitleService: PageTitleService, private openlpService: OpenLPService,
private router: Router) {
pageTitleService.changePageTitle('Service');
openlpService.stateChanged$.subscribe(item => this.getServiceItems());
}

View File

@ -1,9 +1,4 @@
<h3>Slides:</h3>
<div>
<mat-nav-list>
<mat-list-item *ngFor="let slide of slides; let counter = index;" (click)="onSlideSelected(counter)" [class.selected]="slide.selected">
<span MatListAvatar>{{ slide.tag }}</span>
<span MatLine style="margin-left: 2rem;">{{ slide.text }}</span>
</mat-list-item>
</mat-nav-list>
</div>
<mat-card mat-list-item *ngFor="let slide of slides; let counter = index;" (click)="onSlideSelected(counter)" [class.selected]="slide.selected">
<div class="verse-tag">{{ slide.tag }}</div>
<div class="verse-text">{{ slide.text }}</div>
</mat-card>

View File

@ -1,3 +1,16 @@
mat-card {
cursor: pointer;
}
.selected {
font-weight: 700;
}
.verse-tag {
float: left;
}
.verse-text {
margin-left: 2.5rem;
white-space: pre-wrap;
}

View File

@ -1,7 +1,7 @@
import { Component, OnInit } from '@angular/core';
import { OpenLPService } from '../../openlp.service';
import { PageTitleService } from '../../page-title.service';
@Component({
selector: 'openlp-slides',
@ -12,6 +12,12 @@ import { OpenLPService } from '../../openlp.service';
export class SlidesComponent implements OnInit {
slides = null;
constructor(private pageTitleService: PageTitleService, private openlpService: OpenLPService) {
pageTitleService.changePageTitle('Slides');
openlpService.stateChanged$.subscribe(item => this.getSlides());
}
ngOnInit() {
this.getSlides();
}
@ -23,8 +29,4 @@ export class SlidesComponent implements OnInit {
getSlides() {
this.openlpService.getItemSlides().subscribe(slides => this.slides = slides);
}
constructor(private openlpService: OpenLPService) {
openlpService.stateChanged$.subscribe(item => this.getSlides());
}
}

View File

@ -1,11 +1,20 @@
import { Injectable, EventEmitter } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { PluginDescription, State, Slide, ServiceItem, MainView, SystemInformation, Credentials, AuthToken } from './responses';
import {
PluginDescription,
State,
Slide,
ServiceItem,
MainView,
SystemInformation,
Credentials,
AuthToken
} from './responses';
import { environment } from '../environments/environment';
const deserialize = (json, cls) => {
const inst = new cls();
for (const p in json) {
@ -21,12 +30,14 @@ const httpOptions = {
headers: new HttpHeaders({'Content-Type': 'application/json'})
};
@Injectable()
export class OpenLPService {
private apiURL: string;
public stateChanged$: EventEmitter<State>;
constructor(private http: HttpClient) {
const host = window.location.hostname;
let port: string;
if (environment.production) {
port = window.location.port;
@ -34,12 +45,11 @@ export class OpenLPService {
else {
port = '4316';
}
this.apiURL = `http://localhost:${port}/api/v1`;
this.apiURL = `http://${host}:${port}/api/v1`;
this.stateChanged$ = new EventEmitter<State>();
this.retrieveSystemInformation().subscribe(info => {
const ws = new WebSocket(`ws://localhost:${info.websocket_port}/state`);
const ws = new WebSocket(`ws://${host}:${info.websocket_port}/state`);
ws.onmessage = (event) => {
const reader = new FileReader();
reader.onload = () => {

View File

@ -0,0 +1,16 @@
import { Injectable } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { Subject } from 'rxjs';
@Injectable()
export class PageTitleService {
private pageTitleSource = new Subject<string>();
public pageTitleChanged$ = this.pageTitleSource.asObservable();
constructor(private titleService: Title) {}
changePageTitle(pageTitle: string) {
this.pageTitleSource.next(pageTitle);
this.titleService.setTitle(pageTitle + ' | OpenLP Remote');
}
}

View File

@ -2,12 +2,11 @@
<html lang="en">
<head>
<meta charset="utf-8">
<title>OpenLP &bull; Remote</title>
<title>OpenLP Remote</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="/assets/favicon.ico">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<app-root>

View File

@ -1,39 +1,74 @@
/* You can add global styles to this file, and also import other style files */
@import "~@angular/material/prebuilt-themes/indigo-pink.css";
@import '~@angular/material/theming';
@include mat-core();
$primary: mat-palette($mat-indigo);
$accent: mat-palette($mat-light-blue, A200, A100, A400);
$theme: mat-light-theme($primary, $accent);
@include angular-material-theme($theme);
* {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
font: 500 20px/32px Roboto, "Helvetica Neue", sans-serif;
}
html, body {
margin: 0;
padding: 0;
}
mat-sidenav-layout {
background: rgba(0,0,0,0.03);
background: rgba(0,0,0,0.03);
}
mat-sidenav {
width: 200px;
width: 12rem;
}
mat-card {
margin-bottom: 1rem;
}
mat-card.service-item,
mat-card.slide {
cursor: pointer;
}
.displayButton .active {
background: 'teal';
background: 'teal';
}
.page-title {
margin-left: 1rem;
}
.content {
margin: 20px;
margin: 1rem;
}
.chordline {
footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
}
.chordline {
line-height: 1.8;
}
.chordline1 {
line-height: 1.0
line-height: 1.0
}
.chordline span.chord span {
position: relative;
}
.chordline span.chord span strong {
position: absolute;
top: -2.1rem;

View File

@ -72,7 +72,6 @@
"no-trailing-whitespace": true,
"no-unnecessary-initializer": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"object-literal-sort-keys": false,
"one-line": [
@ -115,12 +114,12 @@
"check-type"
],
"no-output-on-prefix": true,
"use-input-property-decorator": true,
"use-output-property-decorator": true,
"use-host-property-decorator": true,
"no-inputs-metadata-property": true,
"no-outputs-metadata-property": true,
"no-host-metadata-property": true,
"no-input-rename": true,
"no-output-rename": true,
"use-life-cycle-interface": true,
"use-lifecycle-interface": true,
"use-pipe-transform-interface": true,
"component-class-suffix": true,
"directive-class-suffix": true