mirror of
https://gitlab.com/openlp/web-remote.git
synced 2024-12-22 11:32:47 +00:00
Merge branch 'settings-section' into 'master'
Adding Font Scaling support to Stage and Chord View + Creating Settings Page See merge request openlp/web-remote!54
This commit is contained in:
commit
4eab9a080a
14
angular.json
14
angular.json
@ -48,6 +48,14 @@
|
|||||||
"extractLicenses": true,
|
"extractLicenses": true,
|
||||||
"vendorChunk": false,
|
"vendorChunk": false,
|
||||||
"buildOptimizer": true
|
"buildOptimizer": true
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"buildOptimizer": false,
|
||||||
|
"optimization": false,
|
||||||
|
"vendorChunk": true,
|
||||||
|
"extractLicenses": false,
|
||||||
|
"sourceMap": true,
|
||||||
|
"namedChunks": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -59,8 +67,12 @@
|
|||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
"browserTarget": "@openlp/web-remote:build:production"
|
"browserTarget": "@openlp/web-remote:build:production"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"browserTarget": "@openlp/web-remote:build:development"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"defaultConfiguration": "development"
|
||||||
},
|
},
|
||||||
"extract-i18n": {
|
"extract-i18n": {
|
||||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
<a mat-list-item (click)="menu.close()" routerLink="/stage">Stage View</a>
|
<a mat-list-item (click)="menu.close()" routerLink="/stage">Stage View</a>
|
||||||
<a mat-list-item (click)="menu.close()" routerLink="/chords">Chord View</a>
|
<a mat-list-item (click)="menu.close()" routerLink="/chords">Chord View</a>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
<mat-slide-toggle color="primary" [checked]="fastSwitching" (change)="sliderChanged($event)">Fast switching</mat-slide-toggle>
|
<a mat-list-item (click)="menu.close()" routerLink="/settings"><mat-icon>settings</mat-icon> Settings</a>
|
||||||
</mat-nav-list>
|
</mat-nav-list>
|
||||||
</mat-sidenav>
|
</mat-sidenav>
|
||||||
<mat-sidenav-content>
|
<mat-sidenav-content>
|
||||||
|
@ -12,6 +12,7 @@ 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 { DisplayModeSelectorComponent } from './components/display-mode-selector/display-mode-selector.component';
|
||||||
import { HotKeysService } from './hotkeys.service';
|
import { HotKeysService } from './hotkeys.service';
|
||||||
|
import { SettingsService } from './settings.service';
|
||||||
// import { version } from '../../package.json';
|
// import { version } from '../../package.json';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -23,16 +24,16 @@ export class AppComponent implements OnInit {
|
|||||||
// Make DisplayMode enum visible to html code
|
// Make DisplayMode enum visible to html code
|
||||||
DisplayMode = DisplayMode;
|
DisplayMode = DisplayMode;
|
||||||
|
|
||||||
private _fastSwitching = false;
|
|
||||||
state = new State();
|
state = new State();
|
||||||
showLogin = false;
|
showLogin = false;
|
||||||
pageTitle = 'OpenLP Remote';
|
pageTitle = 'OpenLP Remote';
|
||||||
appVersion = '0.0';
|
appVersion = '0.0';
|
||||||
webSocketOpen = false;
|
webSocketOpen = false;
|
||||||
|
fastSwitching = false;
|
||||||
|
|
||||||
constructor(private pageTitleService: PageTitleService, private openlpService: OpenLPService,
|
constructor(private pageTitleService: PageTitleService, private openlpService: OpenLPService,
|
||||||
private dialog: MatDialog, private bottomSheet: MatBottomSheet, private windowRef: WindowRef,
|
private dialog: MatDialog, private bottomSheet: MatBottomSheet, private windowRef: WindowRef,
|
||||||
private hotKeysService: HotKeysService) {
|
private hotKeysService: HotKeysService, private settingsService: SettingsService) {
|
||||||
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);
|
||||||
@ -48,6 +49,8 @@ export class AppComponent implements OnInit {
|
|||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.openlpService.retrieveSystemInformation().subscribe(res => this.showLogin = res.login_required);
|
this.openlpService.retrieveSystemInformation().subscribe(res => this.showLogin = res.login_required);
|
||||||
this.addHotKeys();
|
this.addHotKeys();
|
||||||
|
this.fastSwitching = this.settingsService.get('fastSwitching');
|
||||||
|
this.settingsService.onPropertyChanged('fastSwitching').subscribe(value => this.fastSwitching = value);
|
||||||
}
|
}
|
||||||
|
|
||||||
addHotKeys(): void {
|
addHotKeys(): void {
|
||||||
@ -87,18 +90,6 @@ export class AppComponent implements OnInit {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get fastSwitching(): boolean {
|
|
||||||
if (localStorage.getItem('OpenLP-fastSwitching')) {
|
|
||||||
this._fastSwitching = JSON.parse(localStorage.getItem('OpenLP-fastSwitching'));
|
|
||||||
}
|
|
||||||
return this._fastSwitching;
|
|
||||||
}
|
|
||||||
|
|
||||||
set fastSwitching(value: boolean) {
|
|
||||||
this._fastSwitching = value;
|
|
||||||
localStorage.setItem('OpenLP-fastSwitching', JSON.stringify(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
openDisplayModeSelector(): void {
|
openDisplayModeSelector(): void {
|
||||||
const selectorRef = this.bottomSheet.open(DisplayModeSelectorComponent, {data: this.state.displayMode});
|
const selectorRef = this.bottomSheet.open(DisplayModeSelectorComponent, {data: this.state.displayMode});
|
||||||
selectorRef.afterDismissed().subscribe(result => {
|
selectorRef.afterDismissed().subscribe(result => {
|
||||||
@ -154,10 +145,6 @@ export class AppComponent implements OnInit {
|
|||||||
this.openlpService.showDisplay().subscribe();
|
this.openlpService.showDisplay().subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
sliderChanged(event: MatSlideToggleChange) {
|
|
||||||
this.fastSwitching = event.checked;
|
|
||||||
}
|
|
||||||
|
|
||||||
forceWebSocketReconnection() {
|
forceWebSocketReconnection() {
|
||||||
this.openlpService.reconnectWebSocketIfNeeded();
|
this.openlpService.reconnectWebSocketIfNeeded();
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ 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 { MatBottomSheetModule } from '@angular/material/bottom-sheet';
|
||||||
|
import { MatSliderModule } from '@angular/material/slider';
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { PageTitleService } from './page-title.service';
|
import { PageTitleService } from './page-title.service';
|
||||||
@ -46,6 +47,8 @@ import { ServiceListComponent } from './components/service/service-list/service-
|
|||||||
import { ChordViewItemComponent } from './components/chord-view/chord-view-item/chord-view-item.component';
|
import { ChordViewItemComponent } from './components/chord-view/chord-view-item/chord-view-item.component';
|
||||||
import { StageViewItemComponent } from './components/stage-view/stage-view-item/stage-view-item.component';
|
import { StageViewItemComponent } from './components/stage-view/stage-view-item/stage-view-item.component';
|
||||||
import { DisplayModeSelectorComponent } from './components/display-mode-selector/display-mode-selector.component';
|
import { DisplayModeSelectorComponent } from './components/display-mode-selector/display-mode-selector.component';
|
||||||
|
import { SettingsComponent } from './components/settings/settings.component';
|
||||||
|
import { StageChordPreviewComponent } from './components/settings/stage-chord-preview/stage-chord-preview.component';
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@ -69,7 +72,9 @@ import { DisplayModeSelectorComponent } from './components/display-mode-selector
|
|||||||
SlideListComponent,
|
SlideListComponent,
|
||||||
SlideItemComponent,
|
SlideItemComponent,
|
||||||
ThemesComponent,
|
ThemesComponent,
|
||||||
DisplayModeSelectorComponent
|
DisplayModeSelectorComponent,
|
||||||
|
SettingsComponent,
|
||||||
|
StageChordPreviewComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
@ -93,7 +98,8 @@ import { DisplayModeSelectorComponent } from './components/display-mode-selector
|
|||||||
MatTabsModule,
|
MatTabsModule,
|
||||||
MatToolbarModule,
|
MatToolbarModule,
|
||||||
MatTooltipModule,
|
MatTooltipModule,
|
||||||
MatBottomSheetModule
|
MatBottomSheetModule,
|
||||||
|
MatSliderModule
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
PageTitleService,
|
PageTitleService,
|
||||||
|
@ -9,6 +9,7 @@ import { ChordViewComponent } from './components/chord-view/chord-view.component
|
|||||||
import { MainViewComponent } from './components/main-view/main-view.component';
|
import { MainViewComponent } from './components/main-view/main-view.component';
|
||||||
import { StageViewComponent } from './components/stage-view/stage-view.component';
|
import { StageViewComponent } from './components/stage-view/stage-view.component';
|
||||||
import { ThemesComponent } from './components/themes/themes.component';
|
import { ThemesComponent } from './components/themes/themes.component';
|
||||||
|
import { SettingsComponent } from './components/settings/settings.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: '', redirectTo: '/service', pathMatch: 'full' },
|
{ path: '', redirectTo: '/service', pathMatch: 'full' },
|
||||||
@ -19,7 +20,8 @@ const routes: Routes = [
|
|||||||
{ path: 'chords', component: ChordViewComponent },
|
{ path: 'chords', component: ChordViewComponent },
|
||||||
{ path: 'main', component: MainViewComponent },
|
{ path: 'main', component: MainViewComponent },
|
||||||
{ path: 'stage', component: StageViewComponent },
|
{ path: 'stage', component: StageViewComponent },
|
||||||
{ path: 'themes', component: ThemesComponent}
|
{ path: 'themes', component: ThemesComponent},
|
||||||
|
{ path: 'settings', component: SettingsComponent}
|
||||||
];
|
];
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forRoot(routes)],
|
imports: [RouterModule.forRoot(routes)],
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
$mobile-breakpoint: 1024px;
|
$mobile-breakpoint: 1024px;
|
||||||
|
|
||||||
@mixin slide-font-size($scale: 1, $desktop-scale: $scale) {
|
@mixin slide-font-size($scale: 1, $desktop-scale: $scale, $stage-var-name: stage) {
|
||||||
font-size: calc(#{4vw * $scale} + #{1.5vh * $scale});
|
$var-sentence: var(--openlp-#{$stage-var-name}-font-scale);
|
||||||
|
font-size: calc((#{4vw * $scale} * #{$var-sentence}) + (#{1.5vh * $scale} * #{$var-sentence}));
|
||||||
|
|
||||||
@media (orientation: landscape) {
|
@media (orientation: landscape) {
|
||||||
font-size: #{6vmin * $scale};
|
font-size: calc(#{6vmin * $scale} * #{$var-sentence});
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (orientation: landscape) and (max-aspect-ratio: 16/9) {
|
@media (orientation: landscape) and (max-aspect-ratio: 16/9) {
|
||||||
font-size: #{3vw * $scale};
|
font-size: calc(#{3vw * $scale} * #{$var-sentence});
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: $mobile-breakpoint) {
|
@media screen and (min-width: $mobile-breakpoint) {
|
||||||
font-size: calc(#{3.1vw * $desktop-scale} + #{1.5vh * $desktop-scale});
|
font-size: calc((#{3.1vw * $desktop-scale} * #{$var-sentence}) + (#{1.5vh * $desktop-scale} * #{$var-sentence}));
|
||||||
//font-size: #{4vw * $scale};
|
//font-size: #{4vw * $scale};
|
||||||
//font-size: #{5.6vmin * $scale};
|
//font-size: #{5.6vmin * $scale};
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
<div class="overlay">
|
<div
|
||||||
|
class="overlay"
|
||||||
|
[class.embedded]="embedded"
|
||||||
|
[style.--openlp-stage-font-scale]="fontScale"
|
||||||
|
>
|
||||||
<div class="overlay-content">
|
<div class="overlay-content">
|
||||||
<div class="tags">
|
<div class="tags">
|
||||||
<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>
|
||||||
@ -23,7 +27,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<a class="back-button" mat-mini-fab color="" routerLink="/" [matTooltip]="'Go back to controller'">
|
<a class="back-button" mat-mini-fab color="" routerLink="/" [matTooltip]="'Go back to controller'" *ngIf="!embedded">
|
||||||
<mat-icon>arrow_back</mat-icon>
|
<mat-icon>arrow_back</mat-icon>
|
||||||
</a>
|
</a>
|
||||||
<div class="transpose">
|
<div class="transpose">
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
||||||
import { OpenLPService } from '../../openlp.service';
|
|
||||||
import { Slide } from '../../responses';
|
import { Slide } from '../../responses';
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { StageViewComponent } from '../stage-view/stage-view.component';
|
import { StageViewComponent } from '../stage-view/stage-view.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -16,6 +14,7 @@ export class ChordViewComponent extends StageViewComponent {
|
|||||||
songTransposeMap = new Map();
|
songTransposeMap = new Map();
|
||||||
// current songs transpose level
|
// current songs transpose level
|
||||||
transposeLevel = 0;
|
transposeLevel = 0;
|
||||||
|
stageProperty = 'chords';
|
||||||
currentSlide = 0;
|
currentSlide = 0;
|
||||||
useNewTransposeEndpoint = this.openlpService.assertApiVersionMinimum(2, 2);
|
useNewTransposeEndpoint = this.openlpService.assertApiVersionMinimum(2, 2);
|
||||||
|
|
||||||
|
@ -1,28 +1,35 @@
|
|||||||
@import "./overlay-common";
|
@import "./overlay-common";
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--openlp-stage-font-scale: 1;
|
||||||
|
--openlp-stage-image-scale: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.overlay {
|
.overlay {
|
||||||
background: black;
|
background: black;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
z-index: 1200;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: white;
|
color: white;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
&:not(.embedded) {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1200;
|
||||||
|
}
|
||||||
|
|
||||||
.active-slide-img {
|
.active-slide-img {
|
||||||
/* properly size current slide thumbnail */
|
/* properly size current slide thumbnail */
|
||||||
/* relative sizes might not work in real world */
|
/* relative sizes might not work in real world */
|
||||||
/* If we get larger thumbnail sizes, we want to limit their size */
|
/* If we get larger thumbnail sizes, we want to limit their size */
|
||||||
max-height: 75%;
|
max-height: calc(75% * var(--openlp-stage-image-scale));
|
||||||
min-height: 250px;
|
min-height: calc(250px * var(--openlp-stage-image-scale));
|
||||||
}
|
}
|
||||||
.active-slide-img-text {
|
.active-slide-img-text {
|
||||||
font-size: 1.8rem;
|
@include slide-font-size(0.75);
|
||||||
}
|
}
|
||||||
.next-slides-img {
|
.next-slides-img {
|
||||||
/* properly size thumbnail displayed in 2nd and subsequent slides */
|
/* properly size thumbnail displayed in 2nd and subsequent slides */
|
||||||
@ -32,7 +39,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.next-slides-text {
|
.next-slides-text {
|
||||||
font-size: 1.4rem;
|
@include slide-font-size(0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
&-content {
|
&-content {
|
||||||
|
59
src/app/components/settings/settings.component.html
Normal file
59
src/app/components/settings/settings.component.html
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<div class="settings-panel">
|
||||||
|
<mat-card>
|
||||||
|
<mat-card-header>
|
||||||
|
User Interface
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="settings-item">
|
||||||
|
<mat-slide-toggle
|
||||||
|
color="primary"
|
||||||
|
[checked]="settings.fastSwitching"
|
||||||
|
(change)="setSetting('fastSwitching', $event.checked)"
|
||||||
|
>
|
||||||
|
Enable Fast Switching panel
|
||||||
|
</mat-slide-toggle>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
<mat-card>
|
||||||
|
<mat-card-header>
|
||||||
|
Stage and Chords Appearance
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<mat-tab-group>
|
||||||
|
<mat-tab label="Stage">
|
||||||
|
<ng-template matTabContent>
|
||||||
|
<ng-container>
|
||||||
|
<openlp-stage-chord-preview stageType="stage"></openlp-stage-chord-preview>
|
||||||
|
<ng-container *ngTemplateOutlet="stageSettings; context: {prefix: 'stage'}"></ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</ng-template>
|
||||||
|
</mat-tab>
|
||||||
|
<mat-tab label="Chords">
|
||||||
|
<ng-template matTabContent>
|
||||||
|
<openlp-stage-chord-preview stageType="chords"></openlp-stage-chord-preview>
|
||||||
|
<ng-container *ngTemplateOutlet="stageSettings; context: {prefix: 'chords'}"></ng-container>
|
||||||
|
</ng-template>
|
||||||
|
</mat-tab>
|
||||||
|
</mat-tab-group>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
|
<ng-template #stageSettings let-prefix="prefix">
|
||||||
|
<div class="stage-settings">
|
||||||
|
<div class="settings-item">
|
||||||
|
<label>Font Scale: {{settings[prefix + 'FontScale'] ?? 100}}%</label>
|
||||||
|
<mat-slider
|
||||||
|
min="25"
|
||||||
|
max="200"
|
||||||
|
step="6.25"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
matSliderThumb
|
||||||
|
[value]="settings[prefix + 'FontScale']"
|
||||||
|
(valueChange)="setSetting(prefix + 'FontScale', $event)"
|
||||||
|
>
|
||||||
|
</mat-slider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
26
src/app/components/settings/settings.component.scss
Normal file
26
src/app/components/settings/settings.component.scss
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
.settings-panel {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
@media screen and (min-height: 836px) {
|
||||||
|
max-width: 768px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-item {
|
||||||
|
font-size: 1rem;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: -1em;
|
||||||
|
}
|
||||||
|
mat-slider {
|
||||||
|
width: calc(100% - 3rem);
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
}
|
40
src/app/components/settings/settings.component.ts
Normal file
40
src/app/components/settings/settings.component.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { Component, OnDestroy, ViewChild } from '@angular/core';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
import { OpenLPService } from '../../openlp.service';
|
||||||
|
import { PageTitleService } from '../../page-title.service';
|
||||||
|
import { SettingsProperties, SettingsPropertiesItem, SettingsService } from '../../settings.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'openlp-settings',
|
||||||
|
templateUrl: `./settings.component.html`,
|
||||||
|
styleUrls: [`./settings.component.scss`]
|
||||||
|
})
|
||||||
|
export class SettingsComponent implements OnDestroy {
|
||||||
|
constructor(
|
||||||
|
protected pageTitleService: PageTitleService,
|
||||||
|
protected openlpService: OpenLPService,
|
||||||
|
protected settingsService: SettingsService,
|
||||||
|
) {
|
||||||
|
this.settingsSubscription$ = settingsService.settingChanged$.subscribe(this._settingChanged);
|
||||||
|
pageTitleService.changePageTitle('Settings');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected settingsSubscription$: Subscription;
|
||||||
|
|
||||||
|
settings: Partial<SettingsProperties> = this.settingsService.getAll();
|
||||||
|
|
||||||
|
setSetting<SP extends keyof SettingsProperties, SV = SettingsProperties[SP]>(property: SP, value: SV) {
|
||||||
|
this.settingsService.set(property, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
_settingChanged = <SP extends keyof SettingsProperties, SV = SettingsProperties[SP]>(
|
||||||
|
value: SettingsPropertiesItem<SP, SV>
|
||||||
|
) => {
|
||||||
|
this.settings = {...this.settings, [value.property]: value.value};
|
||||||
|
};
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.settingsSubscription$.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
<div class="stage-preview-container" #stageViewContainer>
|
||||||
|
<app-stage-view
|
||||||
|
#stageView
|
||||||
|
*ngIf="stageType === 'stage'"
|
||||||
|
[embedded]="true"
|
||||||
|
[style.--openlp-stage-font-scale]="fontScale"
|
||||||
|
></app-stage-view>
|
||||||
|
<app-chord-view
|
||||||
|
#chordsView
|
||||||
|
*ngIf="stageType === 'chords'"
|
||||||
|
[embedded]="true"
|
||||||
|
[style.--openlp-stage-font-scale]="fontScale"
|
||||||
|
></app-chord-view>
|
||||||
|
</div>
|
@ -0,0 +1,14 @@
|
|||||||
|
.stage-preview-container {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
pointer-events: none;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
transform-origin: 0 0;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,107 @@
|
|||||||
|
import {
|
||||||
|
AfterViewInit,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Component,
|
||||||
|
ElementRef,
|
||||||
|
Input,
|
||||||
|
OnChanges,
|
||||||
|
OnDestroy,
|
||||||
|
OnInit,
|
||||||
|
SimpleChanges,
|
||||||
|
ViewChild
|
||||||
|
} from '@angular/core';
|
||||||
|
import { fromEvent, Subscription } from 'rxjs';
|
||||||
|
import { debounceTime } from 'rxjs/operators';
|
||||||
|
import { SettingsProperties, SettingsService } from 'src/app/settings.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'openlp-stage-chord-preview',
|
||||||
|
templateUrl: './stage-chord-preview.component.html',
|
||||||
|
styleUrls: ['./stage-chord-preview.component.scss'],
|
||||||
|
})
|
||||||
|
export class StageChordPreviewComponent implements OnInit, AfterViewInit, OnDestroy, OnChanges {
|
||||||
|
constructor(
|
||||||
|
protected settingsService: SettingsService,
|
||||||
|
protected ref: ChangeDetectorRef
|
||||||
|
) {
|
||||||
|
this.windowResizeSubscription$ = fromEvent(window, 'resize')
|
||||||
|
.pipe(debounceTime(300))
|
||||||
|
.subscribe(() => this._resizeElement());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input() stageType: 'stage' | 'chords' = 'stage';
|
||||||
|
@ViewChild('stageView', {read: ElementRef}) stageView: ElementRef<HTMLElement>;
|
||||||
|
@ViewChild('chordsView', {read: ElementRef}) chordsView: ElementRef<HTMLElement>;
|
||||||
|
@ViewChild('stageViewContainer') stageViewContainer: ElementRef<HTMLElement>;
|
||||||
|
fontScale: number;
|
||||||
|
protected windowResizeSubscription$: Subscription;
|
||||||
|
protected settingChangedSubscription$: Subscription;
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.fontScale = this.settingsService.get(
|
||||||
|
this.stageType + 'FontScale' as keyof SettingsProperties
|
||||||
|
) as number / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
if (changes['stageType'].currentValue !== changes['stageType'].previousValue) {
|
||||||
|
this.settingChangedSubscription$?.unsubscribe();
|
||||||
|
this.settingChangedSubscription$ = this.settingsService
|
||||||
|
.onPropertyChanged(changes['stageType'].currentValue + 'FontScale' as keyof SettingsProperties)
|
||||||
|
.subscribe(value => {
|
||||||
|
this.fontScale = value as number / 100;
|
||||||
|
this.ref.detectChanges();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit(): void {
|
||||||
|
if (this._getStageViewElement()?.nativeElement) {
|
||||||
|
this._resizeElement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.windowResizeSubscription$?.unsubscribe();
|
||||||
|
this.settingChangedSubscription$?.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
_getStageViewElement() {
|
||||||
|
switch (this.stageType) {
|
||||||
|
case 'stage':
|
||||||
|
return this.stageView;
|
||||||
|
case 'chords':
|
||||||
|
return this.chordsView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_resizeElement() {
|
||||||
|
const viewElement = this._getStageViewElement();
|
||||||
|
if (this.stageViewContainer?.nativeElement && viewElement?.nativeElement) {
|
||||||
|
// Resetting container to 100% width before calculating
|
||||||
|
this.stageViewContainer.nativeElement.style.width = '100%';
|
||||||
|
this.stageViewContainer.nativeElement.style.height = 'auto';
|
||||||
|
const windowWidth = window.innerWidth;
|
||||||
|
const windowHeight = window.innerHeight;
|
||||||
|
|
||||||
|
const {width: containerWidth} = this.stageViewContainer?.nativeElement.getBoundingClientRect();
|
||||||
|
|
||||||
|
let zoomScale = containerWidth / windowWidth;
|
||||||
|
const targetContainerHeight = containerWidth * (windowHeight / windowWidth);
|
||||||
|
let scaleTranslate = '';
|
||||||
|
|
||||||
|
if (targetContainerHeight > (windowHeight * 0.6)) {
|
||||||
|
zoomScale *= 0.5;
|
||||||
|
scaleTranslate = ' translateX(50%)';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setting the container width + height to scale after
|
||||||
|
this.stageViewContainer.nativeElement.style.width = (windowWidth * zoomScale) + 'px';
|
||||||
|
this.stageViewContainer.nativeElement.style.height = (windowHeight * zoomScale) + 'px';
|
||||||
|
|
||||||
|
viewElement.nativeElement.style.width = windowWidth + 'px';
|
||||||
|
viewElement.nativeElement.style.height = windowHeight + 'px';
|
||||||
|
viewElement.nativeElement.style.transform = `scale(${zoomScale})${scaleTranslate}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,8 @@
|
|||||||
<div class="overlay">
|
<div
|
||||||
|
class="overlay"
|
||||||
|
[class.embedded]="embedded"
|
||||||
|
[style.--openlp-stage-font-scale]="fontScale"
|
||||||
|
>
|
||||||
<div class="overlay-content">
|
<div class="overlay-content">
|
||||||
<div class="tags">
|
<div class="tags">
|
||||||
<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>
|
||||||
@ -11,7 +15,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<a class="back-button" mat-mini-fab color="" routerLink="/" [matTooltip]="'Go back to controller'">
|
<a class="back-button" mat-mini-fab color="" routerLink="/" [matTooltip]="'Go back to controller'" *ngIf="!embedded">
|
||||||
<mat-icon>arrow_back</mat-icon>
|
<mat-icon>arrow_back</mat-icon>
|
||||||
</a>
|
</a>
|
||||||
<button
|
<button
|
||||||
@ -26,7 +30,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<div class="time">{{ (openlpService.getIsTwelveHourTime()) ? (time|date:'h:mm a') : (time|date:'HH:mm') }}</div>
|
<div class="time">{{ (openlpService.getIsTwelveHourTime()) ? (time|date:'h:mm a') : (time|date:'HH:mm') }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sidebar" *ngIf="showNotes && notes">
|
<div class="sidebar" *ngIf="(showNotes || embedded) && notes">
|
||||||
<div class="notes" [innerHTML]="notes|nl2br"></div>
|
<div class="notes" [innerHTML]="notes|nl2br"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
|
import { SettingsProperties, SettingsService } from 'src/app/settings.service';
|
||||||
import { OpenLPService } from '../../openlp.service';
|
import { OpenLPService } from '../../openlp.service';
|
||||||
import { ServiceItem, Slide } from '../../responses';
|
import { ServiceItem, Slide } from '../../responses';
|
||||||
|
|
||||||
@ -14,7 +16,8 @@ interface Tag {
|
|||||||
styleUrls: ['./stage-view.component.scss', '../overlay.scss'],
|
styleUrls: ['./stage-view.component.scss', '../overlay.scss'],
|
||||||
encapsulation: ViewEncapsulation.None
|
encapsulation: ViewEncapsulation.None
|
||||||
})
|
})
|
||||||
export class StageViewComponent implements OnInit {
|
export class StageViewComponent implements OnInit, OnDestroy {
|
||||||
|
@Input() embedded = false;
|
||||||
serviceItem: ServiceItem = null;
|
serviceItem: ServiceItem = null;
|
||||||
notes = '';
|
notes = '';
|
||||||
currentSlides: Slide[] = [];
|
currentSlides: Slide[] = [];
|
||||||
@ -22,15 +25,39 @@ export class StageViewComponent implements OnInit {
|
|||||||
tags: Tag[] = [];
|
tags: Tag[] = [];
|
||||||
time = new Date();
|
time = new Date();
|
||||||
showNotes = true;
|
showNotes = true;
|
||||||
serviceItemSubscription$: Subscription = null;
|
fontScale: number;
|
||||||
|
|
||||||
constructor(public openlpService: OpenLPService) {
|
serviceItemSubscription$: Subscription = null;
|
||||||
|
fontScaleSubscription$: Subscription;
|
||||||
|
|
||||||
|
stageProperty = 'stage';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public openlpService: OpenLPService,
|
||||||
|
protected route: ActivatedRoute,
|
||||||
|
protected settingsService: SettingsService,
|
||||||
|
protected ref: ChangeDetectorRef
|
||||||
|
) {
|
||||||
setInterval(() => this.time = new Date(), 1000);
|
setInterval(() => this.time = new Date(), 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.updateCurrentSlides(null, null);
|
this.updateCurrentSlides(null, null);
|
||||||
this.openlpService.stateChanged$.subscribe(item => this.updateCurrentSlides(item.item, item.slide));
|
this.openlpService.stateChanged$.subscribe(item => this.updateCurrentSlides(item.item, item.slide));
|
||||||
|
this.fontScale = this.settingsService.get(
|
||||||
|
this.stageProperty + 'FontScale' as keyof SettingsProperties
|
||||||
|
) as number / 100;
|
||||||
|
|
||||||
|
this.fontScaleSubscription$ = this.settingsService
|
||||||
|
.onPropertyChanged(this.stageProperty + 'FontScale' as keyof SettingsProperties)
|
||||||
|
.subscribe(value => {
|
||||||
|
this.fontScale = value as number / 100;
|
||||||
|
this.ref.detectChanges();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.fontScaleSubscription$?.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCurrentSlides(serviceItemId: string, currentSlide: number): void {
|
updateCurrentSlides(serviceItemId: string, currentSlide: number): void {
|
||||||
|
89
src/app/settings.service.ts
Normal file
89
src/app/settings.service.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import { EventEmitter, Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
// Set here the default value; if there's none, set as undefined and specify key type.
|
||||||
|
export class SettingsProperties {
|
||||||
|
fastSwitching = false;
|
||||||
|
stageFontScale = 100;
|
||||||
|
chordsFontScale = 100;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface SettingsPropertiesItem<SP extends keyof SettingsProperties, SV = SettingsProperties[SP]> {
|
||||||
|
property: SP;
|
||||||
|
value: SV;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LOCAL_STORAGE_PREFIX = 'OpenLP-';
|
||||||
|
|
||||||
|
@Injectable({providedIn: 'root'})
|
||||||
|
export class SettingsService {
|
||||||
|
constructor() {
|
||||||
|
window.addEventListener('storage', this._handleStorageEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultSettingsPropertiesInstance = new SettingsProperties();
|
||||||
|
|
||||||
|
settingChanged$: EventEmitter<SettingsPropertiesItem<any, any>> = new EventEmitter<any>();
|
||||||
|
listenersCache: {[key in keyof Partial<SettingsProperties>]: EventEmitter<any>} = {};
|
||||||
|
|
||||||
|
getAll(): Partial<SettingsProperties> {
|
||||||
|
const output: Partial<SettingsProperties> = {};
|
||||||
|
for (const key of Object.keys(this.defaultSettingsPropertiesInstance)) {
|
||||||
|
const value = this.get(key as keyof SettingsProperties);
|
||||||
|
if (value !== undefined) {
|
||||||
|
output[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
get<SP extends keyof SettingsProperties, SV = SettingsProperties[SP]>(property: SP): SV | undefined {
|
||||||
|
let propertyValue: any = localStorage.getItem(LOCAL_STORAGE_PREFIX + property);
|
||||||
|
if ((propertyValue === undefined || propertyValue === null)
|
||||||
|
&& this.defaultSettingsPropertiesInstance.hasOwnProperty(property)
|
||||||
|
) {
|
||||||
|
propertyValue = this.defaultSettingsPropertiesInstance[property];
|
||||||
|
this.set(property, propertyValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (propertyValue) {
|
||||||
|
return JSON.parse(propertyValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
set<SP extends keyof SettingsProperties, SV = SettingsProperties[SP]>(property: SP, value: SV) {
|
||||||
|
if (value === undefined) {
|
||||||
|
localStorage.removeItem(LOCAL_STORAGE_PREFIX + property);
|
||||||
|
this._emitEvent(property, undefined);
|
||||||
|
} else {
|
||||||
|
localStorage.setItem(LOCAL_STORAGE_PREFIX + property, JSON.stringify(value));
|
||||||
|
this._emitEvent(property, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remove<SP extends keyof SettingsProperties>(property: SP) {
|
||||||
|
this.set(property, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
onPropertyChanged<SP extends keyof SettingsProperties, SV = SettingsProperties[SP]>(
|
||||||
|
property: SP
|
||||||
|
): EventEmitter<SV> {
|
||||||
|
if (!this.listenersCache[property]) {
|
||||||
|
this.listenersCache[property] = new EventEmitter<SV>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.listenersCache[property];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _handleStorageEvent = (event: StorageEvent) => {
|
||||||
|
if (event.storageArea === localStorage) {
|
||||||
|
this._emitEvent(event.key.replace(LOCAL_STORAGE_PREFIX, '') as any, JSON.parse(event.newValue));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected _emitEvent<SP extends keyof SettingsProperties, SV = SettingsProperties[SP]>(property: SP, value: SV) {
|
||||||
|
this.settingChanged$.emit({property, value});
|
||||||
|
this.listenersCache?.[property]?.emit(value);
|
||||||
|
}
|
||||||
|
}
|
@ -107,3 +107,7 @@ footer {
|
|||||||
.mat-mdc-tooltip {
|
.mat-mdc-tooltip {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user