Merge branch 'update-dependencies' into 'master'

Update dependencies

See merge request openlp/web-remote!47
This commit is contained in:
Raoul Snyman 2022-12-21 16:27:47 +00:00
commit a54ff79ca1
33 changed files with 8266 additions and 8930 deletions

92
.eslintrc.json Normal file
View File

@ -0,0 +1,92 @@
{
"root": true,
"ignorePatterns": [
"projects/**/*"
],
"overrides": [
{
"files": [
"*.ts"
],
"parserOptions": {
"project": [
"tsconfig.json",
"e2e/tsconfig.json"
],
"createDefaultProgram": true
},
"extends": [
"plugin:@angular-eslint/ng-cli-compat",
"plugin:@angular-eslint/ng-cli-compat--formatting-add-on",
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"@typescript-eslint/consistent-type-definitions": "error",
"@typescript-eslint/dot-notation": "off",
"@typescript-eslint/explicit-member-accessibility": [
"off",
{
"accessibility": "explicit"
}
],
"@typescript-eslint/member-ordering": [
"error",
{
"default": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": ["app", "openlp"],
"style": "kebab-case"
}
],
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": ["app", "openlp"],
"style": "camelCase"
}
],
"@typescript-eslint/naming-convention": [
"error",
{
"selector": ["variable"],
"modifiers": ["readonly"],
"format": ["UPPER_CASE"]
}
],
"jsdoc/no-types": [
"off"
],
"prefer-arrow/prefer-arrow-functions": [
"off"
],
"brace-style": "off",
"@typescript-eslint/brace-style": [
"off"
],
"id-blacklist": "off",
"id-match": "off",
"no-underscore-dangle": "off"
}
},
{
"files": [
"*.html"
],
"extends": [
"plugin:@angular-eslint/template/recommended"
],
"rules": {}
}
]
}

1
.gitignore vendored
View File

@ -33,6 +33,7 @@ npm-debug.log
yarn-error.log yarn-error.log
testem.log testem.log
/typings /typings
/.angular/cache
# System Files # System Files
.DS_Store .DS_Store

View File

@ -9,9 +9,9 @@
"sourceRoot": "src", "sourceRoot": "src",
"projectType": "application", "projectType": "application",
"schematics": { "schematics": {
"@schematics/angular:component": { "@schematics/angular:component": {
"styleext": "scss" "style": "scss"
} }
}, },
"architect": { "architect": {
"build": { "build": {
@ -86,14 +86,11 @@
} }
}, },
"lint": { "lint": {
"builder": "@angular-devkit/build-angular:tslint", "builder": "@angular-eslint/builder:lint",
"options": { "options": {
"tsConfig": [ "lintFilePatterns": [
"src/tsconfig.app.json", "src/**/*.ts",
"src/tsconfig.spec.json" "src/**/*.html"
],
"exclude": [
"**/node_modules/**"
] ]
} }
} }
@ -111,16 +108,15 @@
} }
}, },
"lint": { "lint": {
"builder": "@angular-devkit/build-angular:tslint", "builder": "@angular-eslint/builder:lint",
"options": { "options": {
"tsConfig": "e2e/tsconfig.e2e.json", "lintFilePatterns": [
"exclude": [ "src/**/*.ts",
"**/node_modules/**" "src/**/*.html"
] ]
} }
} }
} }
} }
}, }
"defaultProject": "@openlp/web-remote"
} }

View File

@ -24,44 +24,52 @@
"e2e": "ng e2e" "e2e": "ng e2e"
}, },
"dependencies": { "dependencies": {
"@angular/animations": "^11.0.5", "@angular/animations": "^15.0.2",
"@angular/cdk": "^11.0.3", "@angular/cdk": "^15.0.2",
"@angular/common": "^11.0.5", "@angular/common": "^15.0.2",
"@angular/compiler": "^11.0.5", "@angular/compiler": "^15.0.2",
"@angular/core": "^11.0.5", "@angular/core": "^15.0.2",
"@angular/flex-layout": "^11.0.0-beta.33", "@angular/forms": "^15.0.2",
"@angular/forms": "^11.0.5", "@angular/material": "^15.0.2",
"@angular/material": "^11.0.3", "@angular/platform-browser": "^15.0.2",
"@angular/platform-browser": "^11.0.5", "@angular/platform-browser-dynamic": "^15.0.2",
"@angular/platform-browser-dynamic": "^11.0.5", "@angular/router": "^15.0.2",
"@angular/router": "^11.0.5", "@fontsource/roboto": "^4.5.8",
"@fontsource/roboto": "^4.4.5", "core-js": "^3.26.1",
"core-js": "^3.8.1",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
"material-icons": "^1.12.1", "material-icons": "^1.13.1",
"rxjs": "^6.6.3", "rxjs": "^7.6.0",
"zone.js": "^0.10.3" "zone.js": "^0.12.0"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "~0.1100.5", "@angular-devkit/build-angular": "^15.0.3",
"@angular/cli": "~11.0.5", "@angular-eslint/builder": "^15.0.3",
"@angular/compiler-cli": "^11.0.5", "@angular-eslint/eslint-plugin": "^15.1.0",
"@angular/language-service": "^11.0.5", "@angular-eslint/eslint-plugin-template": "^15.1.0",
"@types/jasmine": "~3.6.2", "@angular-eslint/schematics": "^15.1.0",
"@types/jasminewd2": "~2.0.8", "@angular-eslint/template-parser": "^15.1.0",
"@types/node": "~14.14.16", "@angular/cli": "~15.0.2",
"codelyzer": "~6.0.1", "@angular/compiler-cli": "^15.0.2",
"jasmine-core": "~3.6.0", "@angular/language-service": "^15.0.2",
"jasmine-spec-reporter": "~6.0.0", "@types/jasmine": "~4.3.1",
"karma": "~5.2.3", "@types/jasminewd2": "~2.0.10",
"karma-chrome-launcher": "~3.1.0", "@types/node": "~18.11.13",
"@typescript-eslint/eslint-plugin": "5.44.0",
"@typescript-eslint/parser": "5.44.0",
"eslint": "^8.28.0",
"eslint-plugin-import": "~2.26.0",
"eslint-plugin-jsdoc": "~39.6.4",
"eslint-plugin-prefer-arrow": "~1.2.3",
"jasmine-core": "~4.5.0",
"jasmine-spec-reporter": "~7.0.0",
"karma": "~6.4.1",
"karma-chrome-launcher": "~3.1.1",
"karma-coverage-istanbul-reporter": "~3.0.3", "karma-coverage-istanbul-reporter": "~3.0.3",
"karma-jasmine": "~4.0.1", "karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "^1.5.4", "karma-jasmine-html-reporter": "^2.0.0",
"protractor": "~7.0.0", "protractor": "~7.0.0",
"ts-node": "~9.1.1", "ts-node": "~10.9.1",
"tslint": "~6.1.3", "typescript": "~4.8.2"
"typescript": "~4.0.5"
}, },
"private": true "private": true
} }

View File

@ -40,28 +40,34 @@
<mat-toolbar *ngIf="fastSwitching" class="toolbar-padding"></mat-toolbar> <mat-toolbar *ngIf="fastSwitching" class="toolbar-padding"></mat-toolbar>
<footer> <footer>
<mat-toolbar class="footer"> <mat-toolbar class="footer">
<button mat-icon-button (click)="previousItem()" matTooltip="Previous item"> <button mat-icon-button (click)="previousItem()" matTooltip="Previous item" matTooltipPosition="above">
<mat-icon>first_page</mat-icon> <mat-icon>first_page</mat-icon>
</button> </button>
<button mat-icon-button (click)="nextItem()" matTooltip="Next item"> <button mat-icon-button (click)="nextItem()" matTooltip="Next item" matTooltipPosition="above">
<mat-icon>last_page</mat-icon> <mat-icon>last_page</mat-icon>
</button> </button>
<button mat-icon-button (click)="previousSlide()" matTooltip="Previous slide"> <button mat-icon-button (click)="previousSlide()" matTooltip="Previous slide" matTooltipPosition="above">
<mat-icon>navigate_before</mat-icon> <mat-icon>navigate_before</mat-icon>
</button> </button>
<button mat-icon-button (click)="nextSlide()" matTooltip="Next slide"> <button mat-icon-button (click)="nextSlide()" matTooltip="Next slide" matTooltipPosition="above">
<mat-icon>navigate_next</mat-icon> <mat-icon>navigate_next</mat-icon>
</button> </button>
<button mat-icon-button (click)="blankDisplay()" class="displayButton" [class.active]="state.blank" [disabled]="state.blank" matTooltip="Show black"> <button mat-icon-button #squashedDisplayButton (click)="openDisplayModeSelector()" class="squashed-display-button" matTooltip="Change Display Mode" matTooltipPosition="above">
<mat-icon *ngIf="state.blank">videocam_off</mat-icon>
<mat-icon *ngIf="state.theme">wallpaper</mat-icon>
<mat-icon *ngIf="state.display">desktop_windows</mat-icon>
<mat-icon *ngIf="state.live()">videocam</mat-icon>
</button>
<button mat-icon-button (click)="blankDisplay()" class="displayButton" [class.active]="state.blank" [disabled]="state.blank" matTooltip="Show black" matTooltipPosition="above">
<mat-icon>videocam_off</mat-icon> <mat-icon>videocam_off</mat-icon>
</button> </button>
<button mat-icon-button (click)="themeDisplay()" class="displayButton" [class.active]="state.theme" [disabled]="state.theme" matTooltip="Show background"> <button mat-icon-button (click)="themeDisplay()" class="displayButton" [class.active]="state.theme" [disabled]="state.theme" matTooltip="Show background" matTooltipPosition="above">
<mat-icon>wallpaper</mat-icon> <mat-icon>wallpaper</mat-icon>
</button> </button>
<button mat-icon-button (click)="desktopDisplay()" class="displayButton" [class.active]="state.display" [disabled]="state.display" matTooltip="Show Desktop"> <button mat-icon-button (click)="desktopDisplay()" class="displayButton" [class.active]="state.display" [disabled]="state.display" matTooltip="Show Desktop" matTooltipPosition="above">
<mat-icon>desktop_windows</mat-icon> <mat-icon>desktop_windows</mat-icon>
</button> </button>
<button mat-icon-button (click)="showDisplay()" class="displayButton" [class.active]="state.display" [disabled]="state.live()" matTooltip="Show Presentation"> <button mat-icon-button (click)="showDisplay()" class="displayButton" [class.active]="state.display" [disabled]="state.live()" matTooltip="Show Presentation" matTooltipPosition="above">
<mat-icon>videocam</mat-icon> <mat-icon>videocam</mat-icon>
</button> </button>
</mat-toolbar> </mat-toolbar>

View File

@ -1,3 +1,5 @@
$small-toolbar-breakpoint: 500px;
// To allow the inner overlay (and other items) to use z-indexes greater than 1 // To allow the inner overlay (and other items) to use z-indexes greater than 1
.mat-sidenav { .mat-sidenav {
&-container, &-content { &-container, &-content {
@ -10,6 +12,11 @@ mat-toolbar {
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 1020; z-index: 1020;
/* Fix icon button alignment on some firefox configurations */
[mat-icon-button] {
line-height: 1;
}
} }
mat-divider { mat-divider {
@ -32,7 +39,12 @@ mat-sidenav-container {
flex: 1; flex: 1;
} }
mat-slide-toggle { /* Align icons with text */
mat-sidenav-container .mat-icon {
vertical-align: text-top;
}
.mat-mdc-slide-toggle {
margin-top: 0.8rem; margin-top: 0.8rem;
margin-left: 1rem; margin-left: 1rem;
font-size: 80%; font-size: 80%;
@ -50,7 +62,7 @@ mat-slide-toggle {
background-color: whitesmoke; background-color: whitesmoke;
} }
.fast-switcher a.mat-tab-link > span.text { .fast-switcher a.mat-mdc-tab-link > span.text {
margin-left: 0.3rem; margin-left: 0.3rem;
} }
@ -60,6 +72,19 @@ mat-slide-toggle {
justify-content: space-evenly; justify-content: space-evenly;
} }
.displayButton {
display: none;
}
@media screen and (min-width: $small-toolbar-breakpoint) {
.squashed-display-button {
display: none;
}
.displayButton {
display: block;
}
}
/* /*
* Make the Component injected by Router Outlet full height: * Make the Component injected by Router Outlet full height:
*/ */

View File

@ -1,14 +1,16 @@
import { Component, HostListener, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { MatSlideToggleChange } from '@angular/material/slide-toggle'; import { MatSlideToggleChange } from '@angular/material/slide-toggle';
import { MatBottomSheet } from '@angular/material/bottom-sheet';
import { State } from './responses'; import { State, DisplayMode } from './responses';
import { OpenLPService, WebSocketStatus } from './openlp.service'; import { OpenLPService, WebSocketStatus } from './openlp.service';
import { WindowRef } from './window-ref.service'; import { WindowRef } from './window-ref.service';
import { PageTitleService } from './page-title.service'; import { PageTitleService } from './page-title.service';
import { LoginComponent } from './components/login/login.component'; import { LoginComponent } from './components/login/login.component';
import { fromEvent } from 'rxjs'; import { fromEvent } from 'rxjs';
import { debounceTime } from 'rxjs/operators'; import { debounceTime } from 'rxjs/operators';
import { DisplayModeSelectorComponent } from './components/display-mode-selector/display-mode-selector.component';
// import { version } from '../../package.json'; // import { version } from '../../package.json';
@Component({ @Component({
@ -17,6 +19,9 @@ import { debounceTime } from 'rxjs/operators';
styleUrls: ['./app.component.scss'] styleUrls: ['./app.component.scss']
}) })
export class AppComponent implements OnInit { export class AppComponent implements OnInit {
// Make DisplayMode enum visible to html code
DisplayMode = DisplayMode;
private _fastSwitching = false; private _fastSwitching = false;
state = new State(); state = new State();
showLogin = false; showLogin = false;
@ -25,7 +30,7 @@ export class AppComponent implements OnInit {
webSocketOpen = false; webSocketOpen = false;
constructor(private pageTitleService: PageTitleService, private openlpService: OpenLPService, constructor(private pageTitleService: PageTitleService, private openlpService: OpenLPService,
private dialog: MatDialog, private windowRef: WindowRef) { private dialog: MatDialog, private bottomSheet: MatBottomSheet, private windowRef: WindowRef) {
pageTitleService.pageTitleChanged$.subscribe(pageTitle => this.pageTitle = pageTitle); pageTitleService.pageTitleChanged$.subscribe(pageTitle => this.pageTitle = pageTitle);
openlpService.stateChanged$.subscribe(item => this.state = item); openlpService.stateChanged$.subscribe(item => this.state = item);
openlpService.webSocketStateChanged$.subscribe(status => this.webSocketOpen = status === WebSocketStatus.Open); openlpService.webSocketStateChanged$.subscribe(status => this.webSocketOpen = status === WebSocketStatus.Open);
@ -54,6 +59,16 @@ export class AppComponent implements OnInit {
localStorage.setItem('OpenLP-fastSwitching', JSON.stringify(value)); localStorage.setItem('OpenLP-fastSwitching', JSON.stringify(value));
} }
openDisplayModeSelector(): void {
const selectorRef = this.bottomSheet.open(DisplayModeSelectorComponent, {data: this.state.displayMode});
selectorRef.afterDismissed().subscribe(result => {
if (result === DisplayMode.Blank) {this.blankDisplay();}
else if (result === DisplayMode.Desktop) {this.desktopDisplay();}
else if (result === DisplayMode.Theme) {this.themeDisplay();}
else if (result === DisplayMode.Presentation) {this.showDisplay();}
});
}
login() { login() {
const dialogRef = this.dialog.open(LoginComponent, { const dialogRef = this.dialog.open(LoginComponent, {
width: '250px' width: '250px'

View File

@ -4,7 +4,6 @@ import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FlexLayoutModule } from '@angular/flex-layout';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle'; import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatDialogModule } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog';
@ -21,6 +20,7 @@ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatTabsModule } from '@angular/material/tabs'; import { MatTabsModule } from '@angular/material/tabs';
import { MatToolbarModule } from '@angular/material/toolbar'; import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { MatBottomSheetModule } from '@angular/material/bottom-sheet';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { PageTitleService } from './page-title.service'; import { PageTitleService } from './page-title.service';
@ -43,6 +43,7 @@ import { SlideListComponent } from './components/slides/slide-list/slide-list.co
import { SlideItemComponent } from './components/slides/slide-item/slide-item.component'; import { SlideItemComponent } from './components/slides/slide-item/slide-item.component';
import { ServiceItemComponent } from './components/service/service-item/service-item.component'; import { ServiceItemComponent } from './components/service/service-item/service-item.component';
import { ServiceListComponent } from './components/service/service-list/service-list.component'; import { ServiceListComponent } from './components/service/service-list/service-list.component';
import { DisplayModeSelectorComponent } from './components/display-mode-selector/display-mode-selector.component';
@NgModule({ @NgModule({
@ -63,7 +64,8 @@ import { ServiceListComponent } from './components/service/service-list/service-
SlidesComponent, SlidesComponent,
SlideListComponent, SlideListComponent,
SlideItemComponent, SlideItemComponent,
ThemesComponent ThemesComponent,
DisplayModeSelectorComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
@ -87,7 +89,7 @@ import { ServiceListComponent } from './components/service/service-list/service-
MatTabsModule, MatTabsModule,
MatToolbarModule, MatToolbarModule,
MatTooltipModule, MatTooltipModule,
FlexLayoutModule MatBottomSheetModule
], ],
providers: [ providers: [
PageTitleService, PageTitleService,

View File

@ -4,7 +4,7 @@
<span *ngFor="let tag of tags" [class.active]="tag.active">{{ tag.text }}</span> <span *ngFor="let tag of tags" [class.active]="tag.active">{{ tag.text }}</span>
</div> </div>
<div class="container"> <div class="container">
<div class="slide currentSlide song mat-display-3" [innerHTML]="chordproFormatted(currentSlides[activeSlide])|chordpro"></div> <div class="slide currentSlide song mat-headline-2" [innerHTML]="chordproFormatted(currentSlides[activeSlide])|chordpro"></div>
<div class="nextSlides"> <div class="nextSlides">
<div <div
class="slide song" class="slide song"

View File

@ -62,6 +62,7 @@ export class ChordProPipe implements PipeTransform {
/** /**
* Pipe transformation for ChordPro-formatted song texts. * Pipe transformation for ChordPro-formatted song texts.
*
* @param {string} song * @param {string} song
* @param {number} nHalfSteps * @param {number} nHalfSteps
* @returns {string} * @returns {string}
@ -108,15 +109,16 @@ export class ChordProPipe implements PipeTransform {
/** /**
* Transpose the given chord the given (positive or negative) number of half steps. * Transpose the given chord the given (positive or negative) number of half steps.
*
* @param {string} chordRoot * @param {string} chordRoot
* @param {number} nHalfSteps * @param {number} nHalfSteps
* @returns {string} * @returns {string}
*/ */
transposeChord(chordRoot, nHalfSteps) { transposeChord(chordRoot, nHalfSteps) {
let pos = -1; let pos = -1;
for (let i = 0; i < this.keys.length; i++) { for (const key of this.keys) {
if (this.keys[i].name === chordRoot) { if (key.name === chordRoot) {
pos = this.keys[i].value; pos = key.value;
break; break;
} }
} }
@ -128,9 +130,9 @@ export class ChordProPipe implements PipeTransform {
else if (pos > this.MAX_HALF_STEPS) { else if (pos > this.MAX_HALF_STEPS) {
pos -= this.MAX_HALF_STEPS + 1; pos -= this.MAX_HALF_STEPS + 1;
} }
for (let i = 0; i < this.keys.length; i++) { for (const key of this.keys) {
if (this.keys[i].value === pos) { if (key.value === pos) {
return this.keys[i].name; return key.name;
} }
} }
} }
@ -225,7 +227,13 @@ export class ChordProPipe implements PipeTransform {
chord += ' '; chord += ' ';
} }
return `<span data-chord="${chord}" class="${chordClass}"><span class="chord">${chord}</span><span class="text ${fillHtml ? 'with-fill' : ''}"><span class="first-letter">${textFirstLetter}</span>${fillHtml}</span></span>${textRest}`; return `<span data-chord="${chord}" class="${chordClass}">` +
`<span class="chord">${chord}</span>` +
`<span class="text ${fillHtml ? 'with-fill' : ''}">` +
`<span class="first-letter">${textFirstLetter}</span>` +
`${fillHtml}` +
`</span>` +
`</span>${textRest}`;
} else { } else {
return `${lastPart}`; return `${lastPart}`;
} }

View File

@ -0,0 +1,19 @@
<mat-action-list>
<button mat-list-item (click)="setMode(DisplayMode.Blank)" class="display-button" [disabled]="displayMode === DisplayMode.Blank">
<mat-icon>videocam_off</mat-icon>
Show black
</button>
<button mat-list-item (click)="setMode(DisplayMode.Theme)" class="display-button" [disabled]="displayMode === DisplayMode.Theme">
<mat-icon>wallpaper</mat-icon>
Show background
</button>
<button mat-list-item (click)="setMode(DisplayMode.Desktop)" class="display-button" [disabled]="displayMode === DisplayMode.Desktop">
<mat-icon>desktop_windows</mat-icon>
Show Desktop
</button>
<button mat-list-item (click)="setMode(DisplayMode.Presentation)" class="display-button" [disabled]="displayMode === DisplayMode.Presentation">
<mat-icon>videocam</mat-icon>
Show Presentation
</button>
</mat-action-list>

View File

@ -0,0 +1,4 @@
.mat-icon {
vertical-align: text-top;
padding-right: 10px;
}

View File

@ -0,0 +1,22 @@
import { Component, Inject } from '@angular/core';
import { MatBottomSheetRef, MAT_BOTTOM_SHEET_DATA } from '@angular/material/bottom-sheet';
import { DisplayMode } from 'src/app/responses';
@Component({
selector: 'openlp-display-mode-sheet',
templateUrl: 'display-mode-selector.component.html',
styleUrls: ['./display-mode-selector.component.scss']
})
export class DisplayModeSelectorComponent {
// Make DisplayMode enum visible to html code
DisplayMode = DisplayMode;
constructor(private bottomSheetRef: MatBottomSheetRef<DisplayModeSelectorComponent>,
@Inject(MAT_BOTTOM_SHEET_DATA) public displayMode: DisplayMode) {}
setMode(mode: DisplayMode): void {
this.bottomSheetRef.dismiss(mode);
event.preventDefault();
}
}

View File

@ -12,7 +12,7 @@ export class SearchOptionsComponent {
public selectedPlugin: string; public selectedPlugin: string;
public searchOptions: Array<string>; public searchOptions: Array<string>;
public selectedSearchOption: string; public selectedSearchOption: string;
public searchOptionsTitle: String = ''; public searchOptionsTitle = '';
constructor(private openlpService: OpenLPService) {} constructor(private openlpService: OpenLPService) {}

View File

@ -17,7 +17,7 @@ export class SearchComponent implements OnInit, AfterViewInit {
public searchResults = null; public searchResults = null;
public selectedPlugin: string; public selectedPlugin: string;
public currentPlugin: string; public currentPlugin: string;
public displaySearchOptions: Boolean = false; public displaySearchOptions = false;
@ViewChild(SearchOptionsComponent, {static: false}) searchOptions: SearchOptionsComponent; @ViewChild(SearchOptionsComponent, {static: false}) searchOptions: SearchOptionsComponent;
constructor(private pageTitleService: PageTitleService, private openlpService: OpenLPService, constructor(private pageTitleService: PageTitleService, private openlpService: OpenLPService,

View File

@ -1,3 +1,5 @@
<mat-card (click)="onItemSelected(item)" class="service-item no-select" [class.selected]="selected"> <mat-card (click)="onItemSelected(item)" class="service-item no-select" [class.selected]="selected">
<mat-icon>{{ getIcon(item) }}</mat-icon> {{ item.title }} <mat-card-content>
<mat-icon>{{ getIcon(item) }}</mat-icon> {{ item.title }}
</mat-card-content>
</mat-card> </mat-card>

View File

@ -0,0 +1,9 @@
.selected {
background-color: rgb(235, 235, 235);
font-weight: 700;
}
/* Align icons with text */
.mat-icon {
line-height: inherit !important;
}

View File

@ -12,10 +12,10 @@ import { ServiceItem } from '../../../responses';
export class ServiceItemComponent { export class ServiceItemComponent {
@Input() item: ServiceItem; @Input() item: ServiceItem;
@Input() selected = false; @Input() selected = false;
@Output() select = new EventEmitter<ServiceItem>(); @Output() selectItem = new EventEmitter<ServiceItem>();
onItemSelected(item: ServiceItem) { onItemSelected(item: ServiceItem) {
this.select.emit(item); this.selectItem.emit(item);
} }
getIcon(item: ServiceItem): string { getIcon(item: ServiceItem): string {

View File

@ -2,6 +2,6 @@
*ngFor="let item of items" *ngFor="let item of items"
[item]="item" [item]="item"
[selected]="item.selected" [selected]="item.selected"
(select)="onItemSelected($event)" (selectItem)="onItemSelected($event)"
[tabindex]="item.id" [tabindex]="item.id"
></openlp-service-item> ></openlp-service-item>

View File

@ -1,13 +1,15 @@
<mat-card class="slide no-select" mat-list-item (click)="onSlideSelected(slide)" [class.selected]="selected"> <mat-card class="slide no-select" mat-list-item (click)="onSlideSelected(slide)" [class.selected]="selected">
<div class="verse-tag">{{ slide?.tag }}</div> <mat-card-content>
<div *ngIf="slide?.img; else onlySlideText" class="verse-img-container"> <div class="verse-tag">{{ slide?.tag }}</div>
<img src="{{ slide?.img }}" /> <div *ngIf="slide?.img; else onlySlideText" class="verse-img-container">
<div class="img-verse-text"> <img src="{{ slide?.img }}" />
{{ slide?.text }} <div class="img-verse-text">
{{ slide?.text }}
</div>
</div> </div>
</div> <ng-template #onlySlideText>
<ng-template #onlySlideText> <div class="verse-text">{{ slide?.text }}</div>
<div class="verse-text">{{ slide?.text }}</div> </ng-template>
</ng-template> </mat-card-content>
</mat-card> </mat-card>

View File

@ -1,4 +1,4 @@
mat-card { .slide {
cursor: pointer; cursor: pointer;
} }

View File

@ -11,10 +11,10 @@ import { Slide } from '../../../responses';
export class SlideItemComponent { export class SlideItemComponent {
@Input() slide: Slide; @Input() slide: Slide;
@Input() selected = false; @Input() selected = false;
@Output() select = new EventEmitter<Slide>(); @Output() selectSlide = new EventEmitter<Slide>();
onSlideSelected(slide: Slide) { onSlideSelected(slide: Slide) {
this.select.emit(slide); this.selectSlide.emit(slide);
} }
} }

View File

@ -1 +1 @@
<openlp-slide-item *ngFor="let slide of slides; let index = index" [slide]="slide" [tabindex]="counter" [selected]="slide.selected" (select)="onSlideSelected($event, index)"></openlp-slide-item> <openlp-slide-item *ngFor="let slide of slides; let index = index" [slide]="slide" [tabindex]="counter" [selected]="slide.selected" (selectSlide)="onSlideSelected($event, index)"></openlp-slide-item>

View File

@ -4,7 +4,7 @@
<span *ngFor="let tag of tags" [class.active]="tag.active">{{ tag.text }}</span> <span *ngFor="let tag of tags" [class.active]="tag.active">{{ tag.text }}</span>
</div> </div>
<div class="container"> <div class="container">
<div class="slide currentSlide mat-display-3"> <div class="slide currentSlide mat-headline-2">
<div *ngIf="currentSlides[activeSlide]?.img; else elseActiveSlideText"> <div *ngIf="currentSlides[activeSlide]?.img; else elseActiveSlideText">
<img src="{{currentSlides[activeSlide]?.img}}" class="active-slide-img" /> <img src="{{currentSlides[activeSlide]?.img}}" class="active-slide-img" />
<div class="active-slide-img-text">{{ currentSlides[activeSlide]?.text }}</div> <div class="active-slide-img-text">{{ currentSlides[activeSlide]?.text }}</div>
@ -14,7 +14,7 @@
</ng-template> </ng-template>
</div> </div>
<div class="nextSlides"> <div class="nextSlides">
<div class="slide mat-display-1" [class.first]="slide.first_slide_of_tag" *ngFor="let slide of nextSlides"> <div class="slide mat-headline-4" [class.first]="slide.first_slide_of_tag" *ngFor="let slide of nextSlides">
<div *ngIf="slide.img; else elseNextSlidesText"> <div *ngIf="slide.img; else elseNextSlidesText">
<img src="{{slide.img}}" class="next-slides-img" /> <img src="{{slide.img}}" class="next-slides-img" />
<div class="next-slides-text">{{ slide.text }}</div> <div class="next-slides-text">{{ slide.text }}</div>

View File

@ -15,10 +15,12 @@
<div class="theme-container content" *ngIf="isThemeLevelSupported()"> <div class="theme-container content" *ngIf="isThemeLevelSupported()">
<div *ngFor="let theme of themeList;"> <div *ngFor="let theme of themeList;">
<mat-card class="theme-card" (click)='setTheme(theme.name)' [class.selected]="theme.selected"> <mat-card class="theme-card" (click)='setTheme(theme.name)' [class.selected]="theme.selected">
<img [src]="theme.thumbnail"/> <mat-card-content>
<div class="theme-title">{{ theme.name }}</div> <img [src]="theme.thumbnail"/>
<div class="theme-title">{{ theme.name }}</div>
</mat-card-content>
</mat-card> </mat-card>
</div> </div>
</div> </div>
<mat-error *ngIf="!isThemeLevelSupported()">Song level theme changing not yet supported. Change your theme level to Global or Service</mat-error> <mat-error *ngIf="!isThemeLevelSupported()">Song level theme changing not supported. Change your theme level to Global or Service</mat-error>
</form> </form>

View File

@ -1,17 +1,4 @@
mat-card {
cursor: pointer;
justify-content: center;
}
mat-divider {
border-color: #afafaf;
}
mat-button-toggle-group {
margin-left: 10px;
}
mat-slide-toggle {
margin-left: auto;
padding: 0 40px;
}
img { img {
width: 100%; width: 100%;
} }
@ -29,7 +16,8 @@ img {
} }
.theme-card { .theme-card {
cursor: pointer;
justify-content: center;
} }
.theme-title { .theme-title {

View File

@ -98,7 +98,7 @@ export class OpenLPService {
} }
setSearchOption(plugin, option, value): Observable<any> { setSearchOption(plugin, option, value): Observable<any> {
return this.doPost(`${this.apiURL}/plugins/${plugin}/search-options`, {'option': option, 'value': value}); return this.doPost(`${this.apiURL}/plugins/${plugin}/search-options`, {option, value});
} }
getServiceItems(): Observable<ServiceItem[]> { getServiceItems(): Observable<ServiceItem[]> {
@ -106,15 +106,15 @@ export class OpenLPService {
} }
setServiceItem(id: any): Observable<any> { setServiceItem(id: any): Observable<any> {
return this.doPost(`${this.apiURL}/service/show`, {'id': id}); return this.doPost(`${this.apiURL}/service/show`, {id});
} }
nextItem(): Observable<any> { nextItem(): Observable<any> {
return this.doPost(`${this.apiURL}/service/progress`, {'action': 'next'}); return this.doPost(`${this.apiURL}/service/progress`, {action: 'next'});
} }
previousItem(): Observable<any> { previousItem(): Observable<any> {
return this.doPost(`${this.apiURL}/service/progress`, {'action': 'previous'}); return this.doPost(`${this.apiURL}/service/progress`, {action: 'previous'});
} }
getServiceItem(): Observable<any> { getServiceItem(): Observable<any> {
@ -126,15 +126,15 @@ export class OpenLPService {
} }
setSlide(id: any): Observable<any> { setSlide(id: any): Observable<any> {
return this.doPost(`${this.apiURL}/controller/show`, {'id': id}); return this.doPost(`${this.apiURL}/controller/show`, {id});
} }
nextSlide(): Observable<any> { nextSlide(): Observable<any> {
return this.doPost(`${this.apiURL}/controller/progress`, {'action': 'next'}); return this.doPost(`${this.apiURL}/controller/progress`, {action: 'next'});
} }
previousSlide(): Observable<any> { previousSlide(): Observable<any> {
return this.doPost(`${this.apiURL}/controller/progress`, {'action': 'previous'}); return this.doPost(`${this.apiURL}/controller/progress`, {action: 'previous'});
} }
getThemeLevel(): Observable<any> { getThemeLevel(): Observable<any> {
@ -146,7 +146,7 @@ export class OpenLPService {
} }
setThemeLevel(level): Observable<any> { setThemeLevel(level): Observable<any> {
return this.doPost(`${this.apiURL}/controller/theme-level`, {'level': level}); return this.doPost(`${this.apiURL}/controller/theme-level`, {level});
} }
getTheme(): Observable<any> { getTheme(): Observable<any> {
@ -154,35 +154,35 @@ export class OpenLPService {
} }
setTheme(theme: string): Observable<any> { setTheme(theme: string): Observable<any> {
return this.doPost(`${this.apiURL}/controller/theme`, {'theme': theme}); return this.doPost(`${this.apiURL}/controller/theme`, {theme});
} }
blankDisplay(): Observable<any> { blankDisplay(): Observable<any> {
return this.doPost(`${this.apiURL}/core/display`, {'display': 'blank'}); return this.doPost(`${this.apiURL}/core/display`, {display: 'blank'});
} }
themeDisplay(): Observable<any> { themeDisplay(): Observable<any> {
return this.doPost(`${this.apiURL}/core/display`, {'display': 'theme'}); return this.doPost(`${this.apiURL}/core/display`, {display: 'theme'});
} }
desktopDisplay(): Observable<any> { desktopDisplay(): Observable<any> {
return this.doPost(`${this.apiURL}/core/display`, {'display': 'desktop'}); return this.doPost(`${this.apiURL}/core/display`, {display: 'desktop'});
} }
showDisplay(): Observable<any> { showDisplay(): Observable<any> {
return this.doPost(`${this.apiURL}/core/display`, {'display': 'show'}); return this.doPost(`${this.apiURL}/core/display`, {display: 'show'});
} }
showAlert(text): Observable<any> { showAlert(text): Observable<any> {
return this.doPost(`${this.apiURL}/plugins/alerts`, {'text': text}); return this.doPost(`${this.apiURL}/plugins/alerts`, {text});
} }
sendItemLive(plugin, id): Observable<any> { sendItemLive(plugin, id): Observable<any> {
return this.doPost(`${this.apiURL}/plugins/${plugin}/live`, {'id': id}); return this.doPost(`${this.apiURL}/plugins/${plugin}/live`, {id});
} }
addItemToService(plugin, id): Observable<any> { addItemToService(plugin, id): Observable<any> {
return this.doPost(`${this.apiURL}/plugins/${plugin}/add`, {'id': id}); return this.doPost(`${this.apiURL}/plugins/${plugin}/add`, {id});
} }
transposeSong(transpose_value): Observable<any> { transposeSong(transpose_value): Observable<any> {
@ -257,7 +257,7 @@ export class OpenLPService {
this.webSocketTimeoutHandle = setTimeout(() => { this.webSocketTimeoutHandle = setTimeout(() => {
this.createWebSocket(); this.createWebSocket();
}, WEBSOCKET_RECONNECT_TIMEOUT); }, WEBSOCKET_RECONNECT_TIMEOUT);
} };
private clearWebSocketTimeoutHandle() { private clearWebSocketTimeoutHandle() {
if (this.webSocketTimeoutHandle) { if (this.webSocketTimeoutHandle) {
@ -272,7 +272,7 @@ export class OpenLPService {
this.handleStateChange(state); this.handleStateChange(state);
}; };
reader.readAsText(event.data); reader.readAsText(event.data);
} };
handleStateChange(state: State) { handleStateChange(state: State) {
this.isTwelveHourTime = state.twelve; this.isTwelveHourTime = state.twelve;

View File

@ -14,6 +14,25 @@ export class State {
theme: boolean; theme: boolean;
live = () => !(this.blank || this.display || this.theme); live = () => !(this.blank || this.display || this.theme);
get displayMode() {
if (this.blank) {
return DisplayMode.Blank;
} else if (this.display) {
return DisplayMode.Desktop;
} else if (this.theme) {
return DisplayMode.Theme;
} else {
return DisplayMode.Presentation;
}
}
}
export enum DisplayMode {
Blank,
Theme,
Desktop,
Presentation
} }
export interface Slide { export interface Slide {

View File

@ -1,5 +1,5 @@
/* 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 */
@import '~@angular/material/theming'; @import '@angular/material/theming';
@include mat-core(); @include mat-core();
$primary: mat-palette($mat-indigo); $primary: mat-palette($mat-indigo);
@ -36,12 +36,12 @@ mat-sidenav {
width: 12rem; width: 12rem;
} }
mat-card { .mat-mdc-card {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
mat-card.service-item, .mat-mdc-card.service-item,
mat-card.slide { .mat-mdc-card.slide {
cursor: pointer; cursor: pointer;
} }
@ -103,3 +103,7 @@ footer {
.no-select { .no-select {
user-select: none; user-select: none;
} }
.mat-mdc-tooltip {
font-size: 1rem;
}

View File

@ -7,14 +7,8 @@ import {
platformBrowserDynamicTesting platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing'; } from '@angular/platform-browser-dynamic/testing';
declare const require: any;
// First, initialize the Angular testing environment. // First, initialize the Angular testing environment.
getTestBed().initTestEnvironment( getTestBed().initTestEnvironment(
BrowserDynamicTestingModule, BrowserDynamicTestingModule,
platformBrowserDynamicTesting() platformBrowserDynamicTesting()
); );
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

View File

@ -1,17 +0,0 @@
{
"extends": "../tslint.json",
"rules": {
"directive-selector": [
true,
"attribute",
["app", "openlp"],
"camelCase"
],
"component-selector": [
true,
"element",
["app", "openlp"],
"kebab-case"
]
}
}

View File

@ -1,127 +0,0 @@
{
"rulesDirectory": [
"node_modules/codelyzer"
],
"rules": {
"arrow-return-shorthand": true,
"callable-types": true,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"deprecation": {
"severity": "warn"
},
"eofline": true,
"forin": true,
"import-blacklist": [
true,
"rxjs/Rx"
],
"import-spacing": true,
"indent": [
true,
"spaces"
],
"interface-over-type-literal": true,
"label-position": true,
"max-line-length": [
true,
140
],
"member-access": false,
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-arg": true,
"no-bitwise": true,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-debugger": true,
"no-duplicate-super": true,
"no-empty": false,
"no-empty-interface": true,
"no-eval": true,
"no-inferrable-types": [
true,
"ignore-params"
],
"no-misused-new": true,
"no-non-null-assertion": true,
"no-shadowed-variable": true,
"no-string-literal": false,
"no-string-throw": true,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": true,
"no-unnecessary-initializer": true,
"no-unused-expression": true,
"no-var-keyword": true,
"object-literal-sort-keys": false,
"one-line": [
true,
"check-open-brace",
"check-whitespace"
],
"prefer-const": true,
"quotemark": [
true,
"single"
],
"radix": true,
"semicolon": [
true,
"always"
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"unified-signatures": true,
"variable-name": false,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
],
"no-output-on-prefix": 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-lifecycle-interface": true,
"use-pipe-transform-interface": true,
"component-class-suffix": true,
"directive-class-suffix": true
}
}

16554
yarn.lock

File diff suppressed because it is too large Load Diff