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,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true
|
||||
},
|
||||
"development": {
|
||||
"buildOptimizer": false,
|
||||
"optimization": false,
|
||||
"vendorChunk": true,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"namedChunks": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -59,8 +67,12 @@
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "@openlp/web-remote:build:production"
|
||||
},
|
||||
"development": {
|
||||
"browserTarget": "@openlp/web-remote:build:development"
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"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="/chords">Chord View</a>
|
||||
<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-sidenav>
|
||||
<mat-sidenav-content>
|
||||
|
@ -12,6 +12,7 @@ import { fromEvent } from 'rxjs';
|
||||
import { debounceTime } from 'rxjs/operators';
|
||||
import { DisplayModeSelectorComponent } from './components/display-mode-selector/display-mode-selector.component';
|
||||
import { HotKeysService } from './hotkeys.service';
|
||||
import { SettingsService } from './settings.service';
|
||||
// import { version } from '../../package.json';
|
||||
|
||||
@Component({
|
||||
@ -23,16 +24,16 @@ export class AppComponent implements OnInit {
|
||||
// Make DisplayMode enum visible to html code
|
||||
DisplayMode = DisplayMode;
|
||||
|
||||
private _fastSwitching = false;
|
||||
state = new State();
|
||||
showLogin = false;
|
||||
pageTitle = 'OpenLP Remote';
|
||||
appVersion = '0.0';
|
||||
webSocketOpen = false;
|
||||
fastSwitching = false;
|
||||
|
||||
constructor(private pageTitleService: PageTitleService, private openlpService: OpenLPService,
|
||||
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);
|
||||
openlpService.stateChanged$.subscribe(item => this.state = item);
|
||||
openlpService.webSocketStateChanged$.subscribe(status => this.webSocketOpen = status === WebSocketStatus.Open);
|
||||
@ -48,6 +49,8 @@ export class AppComponent implements OnInit {
|
||||
ngOnInit(): void {
|
||||
this.openlpService.retrieveSystemInformation().subscribe(res => this.showLogin = res.login_required);
|
||||
this.addHotKeys();
|
||||
this.fastSwitching = this.settingsService.get('fastSwitching');
|
||||
this.settingsService.onPropertyChanged('fastSwitching').subscribe(value => this.fastSwitching = value);
|
||||
}
|
||||
|
||||
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 {
|
||||
const selectorRef = this.bottomSheet.open(DisplayModeSelectorComponent, {data: this.state.displayMode});
|
||||
selectorRef.afterDismissed().subscribe(result => {
|
||||
@ -154,10 +145,6 @@ export class AppComponent implements OnInit {
|
||||
this.openlpService.showDisplay().subscribe();
|
||||
}
|
||||
|
||||
sliderChanged(event: MatSlideToggleChange) {
|
||||
this.fastSwitching = event.checked;
|
||||
}
|
||||
|
||||
forceWebSocketReconnection() {
|
||||
this.openlpService.reconnectWebSocketIfNeeded();
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { MatBottomSheetModule } from '@angular/material/bottom-sheet';
|
||||
import { MatSliderModule } from '@angular/material/slider';
|
||||
|
||||
import { AppComponent } from './app.component';
|
||||
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 { StageViewItemComponent } from './components/stage-view/stage-view-item/stage-view-item.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({
|
||||
@ -69,7 +72,9 @@ import { DisplayModeSelectorComponent } from './components/display-mode-selector
|
||||
SlideListComponent,
|
||||
SlideItemComponent,
|
||||
ThemesComponent,
|
||||
DisplayModeSelectorComponent
|
||||
DisplayModeSelectorComponent,
|
||||
SettingsComponent,
|
||||
StageChordPreviewComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
@ -93,7 +98,8 @@ import { DisplayModeSelectorComponent } from './components/display-mode-selector
|
||||
MatTabsModule,
|
||||
MatToolbarModule,
|
||||
MatTooltipModule,
|
||||
MatBottomSheetModule
|
||||
MatBottomSheetModule,
|
||||
MatSliderModule
|
||||
],
|
||||
providers: [
|
||||
PageTitleService,
|
||||
|
@ -9,6 +9,7 @@ import { ChordViewComponent } from './components/chord-view/chord-view.component
|
||||
import { MainViewComponent } from './components/main-view/main-view.component';
|
||||
import { StageViewComponent } from './components/stage-view/stage-view.component';
|
||||
import { ThemesComponent } from './components/themes/themes.component';
|
||||
import { SettingsComponent } from './components/settings/settings.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', redirectTo: '/service', pathMatch: 'full' },
|
||||
@ -19,7 +20,8 @@ const routes: Routes = [
|
||||
{ path: 'chords', component: ChordViewComponent },
|
||||
{ path: 'main', component: MainViewComponent },
|
||||
{ path: 'stage', component: StageViewComponent },
|
||||
{ path: 'themes', component: ThemesComponent}
|
||||
{ path: 'themes', component: ThemesComponent},
|
||||
{ path: 'settings', component: SettingsComponent}
|
||||
];
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes)],
|
||||
|
@ -1,18 +1,19 @@
|
||||
$mobile-breakpoint: 1024px;
|
||||
|
||||
@mixin slide-font-size($scale: 1, $desktop-scale: $scale) {
|
||||
font-size: calc(#{4vw * $scale} + #{1.5vh * $scale});
|
||||
@mixin slide-font-size($scale: 1, $desktop-scale: $scale, $stage-var-name: stage) {
|
||||
$var-sentence: var(--openlp-#{$stage-var-name}-font-scale);
|
||||
font-size: calc((#{4vw * $scale} * #{$var-sentence}) + (#{1.5vh * $scale} * #{$var-sentence}));
|
||||
|
||||
@media (orientation: landscape) {
|
||||
font-size: #{6vmin * $scale};
|
||||
font-size: calc(#{6vmin * $scale} * #{$var-sentence});
|
||||
}
|
||||
|
||||
@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) {
|
||||
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: #{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="tags">
|
||||
<span *ngFor="let tag of tags" [class.active]="tag.active">{{ tag.text }}</span>
|
||||
@ -23,7 +27,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
</a>
|
||||
<div class="transpose">
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { OpenLPService } from '../../openlp.service';
|
||||
import { Slide } from '../../responses';
|
||||
import { Observable } from 'rxjs';
|
||||
import { StageViewComponent } from '../stage-view/stage-view.component';
|
||||
|
||||
@Component({
|
||||
@ -16,6 +14,7 @@ export class ChordViewComponent extends StageViewComponent {
|
||||
songTransposeMap = new Map();
|
||||
// current songs transpose level
|
||||
transposeLevel = 0;
|
||||
stageProperty = 'chords';
|
||||
currentSlide = 0;
|
||||
useNewTransposeEndpoint = this.openlpService.assertApiVersionMinimum(2, 2);
|
||||
|
||||
|
@ -1,28 +1,35 @@
|
||||
@import "./overlay-common";
|
||||
|
||||
:root {
|
||||
--openlp-stage-font-scale: 1;
|
||||
--openlp-stage-image-scale: 1;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
background: black;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 1200;
|
||||
overflow: hidden;
|
||||
color: white;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
flex-direction: row;
|
||||
&:not(.embedded) {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 1200;
|
||||
}
|
||||
|
||||
.active-slide-img {
|
||||
/* properly size current slide thumbnail */
|
||||
/* relative sizes might not work in real world */
|
||||
/* If we get larger thumbnail sizes, we want to limit their size */
|
||||
max-height: 75%;
|
||||
min-height: 250px;
|
||||
max-height: calc(75% * var(--openlp-stage-image-scale));
|
||||
min-height: calc(250px * var(--openlp-stage-image-scale));
|
||||
}
|
||||
.active-slide-img-text {
|
||||
font-size: 1.8rem;
|
||||
@include slide-font-size(0.75);
|
||||
}
|
||||
.next-slides-img {
|
||||
/* properly size thumbnail displayed in 2nd and subsequent slides */
|
||||
@ -32,7 +39,7 @@
|
||||
}
|
||||
|
||||
.next-slides-text {
|
||||
font-size: 1.4rem;
|
||||
@include slide-font-size(0.5);
|
||||
}
|
||||
|
||||
&-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="tags">
|
||||
<span *ngFor="let tag of tags" [class.active]="tag.active">{{ tag.text }}</span>
|
||||
@ -11,7 +15,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
</a>
|
||||
<button
|
||||
@ -26,7 +30,7 @@
|
||||
</button>
|
||||
<div class="time">{{ (openlpService.getIsTwelveHourTime()) ? (time|date:'h:mm a') : (time|date:'HH:mm') }}</div>
|
||||
</div>
|
||||
<div class="sidebar" *ngIf="showNotes && notes">
|
||||
<div class="sidebar" *ngIf="(showNotes || embedded) && notes">
|
||||
<div class="notes" [innerHTML]="notes|nl2br"></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 { SettingsProperties, SettingsService } from 'src/app/settings.service';
|
||||
import { OpenLPService } from '../../openlp.service';
|
||||
import { ServiceItem, Slide } from '../../responses';
|
||||
|
||||
@ -14,7 +16,8 @@ interface Tag {
|
||||
styleUrls: ['./stage-view.component.scss', '../overlay.scss'],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class StageViewComponent implements OnInit {
|
||||
export class StageViewComponent implements OnInit, OnDestroy {
|
||||
@Input() embedded = false;
|
||||
serviceItem: ServiceItem = null;
|
||||
notes = '';
|
||||
currentSlides: Slide[] = [];
|
||||
@ -22,15 +25,39 @@ export class StageViewComponent implements OnInit {
|
||||
tags: Tag[] = [];
|
||||
time = new Date();
|
||||
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);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.updateCurrentSlides(null, null);
|
||||
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 {
|
||||
|
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 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
z-index: 1;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user