mirror of
https://gitlab.com/openlp/web-remote.git
synced 2024-12-22 11:32:47 +00:00
Make use of the configured shortcuts in OpenLP.
This commit is contained in:
parent
ed9db1d295
commit
7ad8daff62
46
package.json
46
package.json
@ -24,44 +24,44 @@
|
||||
"supportedBrowsers": "(echo module.exports = && browserslist-useragent-regexp --allowHigherVersions) > src/assets/supportedBrowsers.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "^17.3.1",
|
||||
"@angular/cdk": "^17.3.1",
|
||||
"@angular/common": "^17.3.1",
|
||||
"@angular/compiler": "^17.3.1",
|
||||
"@angular/core": "^17.3.1",
|
||||
"@angular/forms": "^17.3.1",
|
||||
"@angular/material": "^17.3.1",
|
||||
"@angular/platform-browser": "^17.3.1",
|
||||
"@angular/platform-browser-dynamic": "^17.3.1",
|
||||
"@angular/router": "^17.3.1",
|
||||
"@fontsource/roboto": "^5.0.8",
|
||||
"core-js": "^3.35.1",
|
||||
"@angular/animations": "^17.3.2",
|
||||
"@angular/cdk": "^17.3.2",
|
||||
"@angular/common": "^17.3.2",
|
||||
"@angular/compiler": "^17.3.2",
|
||||
"@angular/core": "^17.3.2",
|
||||
"@angular/forms": "^17.3.2",
|
||||
"@angular/material": "^17.3.2",
|
||||
"@angular/platform-browser": "^17.3.2",
|
||||
"@angular/platform-browser-dynamic": "^17.3.2",
|
||||
"@angular/router": "^17.3.2",
|
||||
"@fontsource/roboto": "^5.0.12",
|
||||
"core-js": "^3.36.1",
|
||||
"hammerjs": "^2.0.8",
|
||||
"material-icons": "^1.13.12",
|
||||
"rxjs": "^7.8.1",
|
||||
"zone.js": "^0.14.3"
|
||||
"zone.js": "^0.14.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^17.3.1",
|
||||
"@angular-devkit/build-angular": "^17.3.2",
|
||||
"@angular-eslint/builder": "^17.3.0",
|
||||
"@angular-eslint/eslint-plugin": "^17.3.0",
|
||||
"@angular-eslint/eslint-plugin-template": "^17.3.0",
|
||||
"@angular-eslint/schematics": "^17.3.0",
|
||||
"@angular-eslint/template-parser": "^17.3.0",
|
||||
"@angular/cli": "~17.3.1",
|
||||
"@angular/compiler-cli": "^17.3.1",
|
||||
"@angular/language-service": "^17.3.1",
|
||||
"@angular/cli": "~17.3.2",
|
||||
"@angular/compiler-cli": "^17.3.2",
|
||||
"@angular/language-service": "^17.3.2",
|
||||
"@chiragrupani/karma-chromium-edge-launcher": "^2.3.1",
|
||||
"@types/jasmine": "~5.1.4",
|
||||
"@types/jasminewd2": "~2.0.13",
|
||||
"@types/node": "~20.11.17",
|
||||
"@typescript-eslint/eslint-plugin": "6.21.0",
|
||||
"@typescript-eslint/parser": "6.21.0",
|
||||
"@types/node": "~20.12.2",
|
||||
"@typescript-eslint/eslint-plugin": "7.4.0",
|
||||
"@typescript-eslint/parser": "7.4.0",
|
||||
"browserslist": "^4.23.0",
|
||||
"browserslist-useragent-regexp": "^4.1.1",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-import": "~2.29.1",
|
||||
"eslint-plugin-jsdoc": "~48.0.6",
|
||||
"eslint-plugin-jsdoc": "~48.2.2",
|
||||
"eslint-plugin-prefer-arrow": "~1.2.3",
|
||||
"jasmine-core": "~5.1.2",
|
||||
"jasmine-spec-reporter": "~7.0.0",
|
||||
@ -71,7 +71,7 @@
|
||||
"karma-jasmine": "~5.1.0",
|
||||
"karma-jasmine-html-reporter": "^2.1.0",
|
||||
"ts-node": "~10.9.2",
|
||||
"typescript": "~5.3.3"
|
||||
"typescript": "~5.4.3"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
@ -10,7 +10,8 @@ import { LoginComponent } from './components/login/login.component';
|
||||
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 { Shortcuts, ShortcutsService } from './shortcuts.service';
|
||||
import { ShortcutPipe } from './components/shortcuts/shortcut.pipe';
|
||||
import { SettingsService } from './settings.service';
|
||||
import * as supportedBrowsers from '../assets/supportedBrowsers';
|
||||
|
||||
@ -29,14 +30,16 @@ export class AppComponent implements OnInit {
|
||||
appVersion = '0.0';
|
||||
webSocketOpen = false;
|
||||
fastSwitching = false;
|
||||
useShortcutsFromOpenlp = false;
|
||||
|
||||
constructor(private pageTitleService: PageTitleService, private openlpService: OpenLPService,
|
||||
private dialog: MatDialog, private bottomSheet: MatBottomSheet, private windowRef: WindowRef,
|
||||
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);
|
||||
this.appVersion = windowRef.nativeWindow.appVersion || '0.0';
|
||||
private shortcutsService: ShortcutsService, private settingsService: SettingsService) {
|
||||
this.pageTitleService.pageTitleChanged$.subscribe(pageTitle => this.pageTitle = pageTitle);
|
||||
this.openlpService.stateChanged$.subscribe(item => this.state = item);
|
||||
this.openlpService.webSocketStateChanged$.subscribe(status => this.webSocketOpen = status === WebSocketStatus.Open);
|
||||
this.shortcutsService.shortcutsChanged$.subscribe(shortcuts => this.addShortcuts(shortcuts));
|
||||
this.appVersion = this.windowRef.nativeWindow.appVersion || '0.0';
|
||||
this.webSocketOpen = openlpService.webSocketStatus === WebSocketStatus.Open;
|
||||
// Try to force websocket reconnection as user is now focused on window and will try to interact soon
|
||||
// Adding a debounce to avoid event flooding
|
||||
@ -49,47 +52,60 @@ export class AppComponent implements OnInit {
|
||||
if (!(supportedBrowsers.test(navigator.userAgent))) {
|
||||
window.location.replace("/assets/notsupported.html");
|
||||
}
|
||||
this.openlpService.retrieveSystemInformation().subscribe(res => this.showLogin = res.login_required);
|
||||
this.addHotKeys();
|
||||
this.openlpService.retrieveSystemInformation().subscribe(res => {
|
||||
this.showLogin = res.login_required
|
||||
this.useShortcutsFromOpenlp = this.openlpService.assertApiVersionMinimum(2, 5)
|
||||
this.shortcutsService.getShortcuts(this.useShortcutsFromOpenlp);
|
||||
}
|
||||
);
|
||||
this.fastSwitching = this.settingsService.get('fastSwitching');
|
||||
this.settingsService.onPropertyChanged('fastSwitching').subscribe(value => this.fastSwitching = value);
|
||||
}
|
||||
|
||||
addHotKeys(): void {
|
||||
this.hotKeysService.addShortcut({ keys: 'ArrowUp' }).subscribe(() =>
|
||||
this.previousSlide()
|
||||
);
|
||||
this.hotKeysService.addShortcut({ keys: 'ArrowDown' }).subscribe(() =>
|
||||
this.nextSlide()
|
||||
);
|
||||
this.hotKeysService.addShortcut({ keys: 'PageUp' }).subscribe(() =>
|
||||
this.previousSlide()
|
||||
);
|
||||
this.hotKeysService.addShortcut({ keys: 'PageDown' }).subscribe(() =>
|
||||
this.nextSlide()
|
||||
);
|
||||
this.hotKeysService.addShortcut({ keys: 'ArrowLeft' }).subscribe(() =>
|
||||
this.previousItem()
|
||||
);
|
||||
this.hotKeysService.addShortcut({ keys: 'ArrowRight' }).subscribe(() =>
|
||||
this.nextItem()
|
||||
);
|
||||
this.hotKeysService.addShortcut({ keys: 'Space' }).subscribe(() =>
|
||||
{
|
||||
addShortcuts(shortcuts: Shortcuts): void {
|
||||
const shortcutPipe = new ShortcutPipe();
|
||||
shortcuts.previousSlide.forEach((key) => {
|
||||
this.shortcutsService.addShortcut({ keys: shortcutPipe.transform(key) }).subscribe(() =>
|
||||
this.previousSlide()
|
||||
)
|
||||
});
|
||||
shortcuts.nextSlide.forEach((key) => {
|
||||
this.shortcutsService.addShortcut({ keys: shortcutPipe.transform(key) }).subscribe(() =>
|
||||
this.nextSlide()
|
||||
)
|
||||
});
|
||||
shortcuts.previousItem.forEach((key) => {
|
||||
this.shortcutsService.addShortcut({ keys: shortcutPipe.transform(key) }).subscribe(() =>
|
||||
this.previousItem()
|
||||
)
|
||||
});
|
||||
shortcuts.nextItem.forEach((key) => {
|
||||
this.shortcutsService.addShortcut({ keys: shortcutPipe.transform(key) }).subscribe(() =>
|
||||
this.nextItem()
|
||||
)
|
||||
});
|
||||
shortcuts.showDisplay.forEach((key) => {
|
||||
this.shortcutsService.addShortcut({ keys: shortcutPipe.transform(key) }).subscribe(() => {
|
||||
if (this.state.displayMode !== DisplayMode.Presentation) {
|
||||
this.showDisplay();
|
||||
}
|
||||
}
|
||||
);
|
||||
this.hotKeysService.addShortcut({ keys: 't' }).subscribe(() =>
|
||||
this.state.displayMode === DisplayMode.Theme ? this.showDisplay() : this.themeDisplay()
|
||||
);
|
||||
this.hotKeysService.addShortcut({ keys: 'code.Period' }).subscribe(() =>
|
||||
this.state.displayMode === DisplayMode.Blank ? this.showDisplay() : this.blankDisplay()
|
||||
);
|
||||
this.hotKeysService.addShortcut({ keys: 'd' }).subscribe(() =>
|
||||
this.state.displayMode === DisplayMode.Desktop ? this.showDisplay() : this.desktopDisplay()
|
||||
);
|
||||
})
|
||||
});
|
||||
shortcuts.themeDisplay.forEach((key) => {
|
||||
this.shortcutsService.addShortcut({ keys: shortcutPipe.transform(key) }).subscribe(() =>
|
||||
this.state.displayMode === DisplayMode.Theme ? this.showDisplay() : this.themeDisplay()
|
||||
)
|
||||
});
|
||||
shortcuts.blankDisplay.forEach((key) => {
|
||||
this.shortcutsService.addShortcut({ keys: shortcutPipe.transform(key) }).subscribe(() =>
|
||||
this.state.displayMode === DisplayMode.Blank ? this.showDisplay() : this.blankDisplay()
|
||||
)
|
||||
});
|
||||
shortcuts.desktopDisplay.forEach((key) => {
|
||||
this.shortcutsService.addShortcut({ keys: shortcutPipe.transform(key) }).subscribe(() =>
|
||||
this.state.displayMode === DisplayMode.Desktop ? this.showDisplay() : this.desktopDisplay()
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
openDisplayModeSelector(): void {
|
||||
|
23
src/app/components/shortcuts/shortcut.pipe.ts
Normal file
23
src/app/components/shortcuts/shortcut.pipe.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({name: 'shortcut'})
|
||||
export class ShortcutPipe implements PipeTransform {
|
||||
transform(value: string): string {
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value !== 'string') {
|
||||
throw Error(`Invalid pipe argument: '${value}' for pipe 'ShortcutPipe'`);
|
||||
}
|
||||
value = value.replace('.', 'code.period');
|
||||
value = value.replace('PgUp', 'pageup');
|
||||
value = value.replace('PgDown', 'pagedown');
|
||||
value = value.replace('Up', 'arrowup');
|
||||
value = value.replace('Down', 'arrowdown');
|
||||
value = value.replace('Left', 'arrowleft');
|
||||
value = value.replace('Right', 'arrowright');
|
||||
value = value.replace(/(Alt|Shift)\+/, '$1.');
|
||||
value = value.replace(/Ctrl\+/, 'control.');
|
||||
return value.toLowerCase();
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { EventManager } from '@angular/platform-browser';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
interface Options {
|
||||
element: any;
|
||||
keys: string;
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class HotKeysService {
|
||||
defaults: Partial<Options> = {
|
||||
element: this.document
|
||||
};
|
||||
|
||||
constructor(private eventManager: EventManager, @Inject(DOCUMENT) private document: Document) {
|
||||
}
|
||||
|
||||
addShortcut(options: Partial<Options>) {
|
||||
const merged = { ...this.defaults, ...options };
|
||||
const event = `keydown.${merged.keys}`;
|
||||
|
||||
return new Observable(observer => {
|
||||
const handler = (e: KeyboardEvent) => {
|
||||
const activeElement = this.document.activeElement;
|
||||
const notOnInput = activeElement?.tagName !== 'INPUT' && activeElement?.tagName !== 'TEXTAREA';
|
||||
if (notOnInput) {
|
||||
if (document.URL.endsWith('/slides')) {
|
||||
e.preventDefault();
|
||||
observer.next(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const dispose = this.eventManager.addEventListener(
|
||||
merged.element, event, handler
|
||||
);
|
||||
|
||||
return () => {
|
||||
dispose();
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ import {
|
||||
ServiceItem,
|
||||
Theme,
|
||||
MainView,
|
||||
Shortcut,
|
||||
SystemInformation,
|
||||
Credentials,
|
||||
AuthToken,
|
||||
@ -96,6 +97,10 @@ export class OpenLPService {
|
||||
return this.doGet<MainView>(`${this.apiURL}/core/live-image`);
|
||||
}
|
||||
|
||||
getShortcuts(): Observable<Shortcut[]> {
|
||||
return this.doGet(`${this.apiURL}/core/shortcuts`);
|
||||
}
|
||||
|
||||
getSearchablePlugins(): Observable<PluginDescription[]> {
|
||||
return this.doGet<PluginDescription[]>(`${this.apiURL}/core/plugins`);
|
||||
}
|
||||
|
@ -66,6 +66,11 @@ export interface MainView {
|
||||
binary_image: string;
|
||||
}
|
||||
|
||||
export interface Shortcut {
|
||||
action: string;
|
||||
shortcut: string[];
|
||||
}
|
||||
|
||||
export interface SystemInformation {
|
||||
websocket_port: number;
|
||||
login_required: boolean;
|
||||
|
109
src/app/shortcuts.service.ts
Normal file
109
src/app/shortcuts.service.ts
Normal file
@ -0,0 +1,109 @@
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { EventEmitter, Inject, Injectable } from '@angular/core';
|
||||
import { EventManager } from '@angular/platform-browser';
|
||||
import { Observable } from 'rxjs';
|
||||
import { OpenLPService } from './openlp.service';
|
||||
|
||||
export class Shortcuts {
|
||||
previousSlide = ['Up', 'PgUp'];
|
||||
nextSlide = ['Down', 'PgDown'];
|
||||
previousItem = ['Left'];
|
||||
nextItem = ['Right'];
|
||||
showDisplay = ['Space'];
|
||||
themeDisplay = ['t'];
|
||||
blankDisplay = ['.'];
|
||||
desktopDisplay = ['d'];
|
||||
}
|
||||
|
||||
interface Options {
|
||||
element: any;
|
||||
keys: string;
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ShortcutsService {
|
||||
defaults: Partial<Options> = {
|
||||
element: this.document
|
||||
};
|
||||
|
||||
constructor(
|
||||
@Inject(DOCUMENT) private document: Document,
|
||||
private eventManager: EventManager,
|
||||
private openlpService: OpenLPService) {
|
||||
this.shortcutsChanged$ = new EventEmitter<Shortcuts>();
|
||||
}
|
||||
|
||||
private shortcuts: Shortcuts
|
||||
public shortcutsChanged$: EventEmitter<Shortcuts>;
|
||||
|
||||
getShortcuts(useShortcutsFromOpenlp: boolean) {
|
||||
const shortcuts: Shortcuts = new Shortcuts()
|
||||
if (useShortcutsFromOpenlp) {
|
||||
this.openlpService.getShortcuts().subscribe(res => {
|
||||
res.forEach((shortcut) => {
|
||||
switch (shortcut.action) {
|
||||
case 'blankScreen':
|
||||
shortcuts.blankDisplay = shortcut.shortcut;
|
||||
break;
|
||||
case 'desktopScreen':
|
||||
shortcuts.desktopDisplay = shortcut.shortcut;
|
||||
break;
|
||||
case 'nextItem_live':
|
||||
shortcuts.nextSlide = shortcut.shortcut;
|
||||
break;
|
||||
case 'nextService':
|
||||
shortcuts.nextItem = shortcut.shortcut;
|
||||
break;
|
||||
case 'previousItem_live':
|
||||
shortcuts.previousSlide = shortcut.shortcut;
|
||||
break;
|
||||
case 'previousService':
|
||||
shortcuts.previousItem = shortcut.shortcut;
|
||||
break;
|
||||
case 'showScreen':
|
||||
shortcuts.showDisplay = shortcut.shortcut;
|
||||
break;
|
||||
case 'themeScreen':
|
||||
shortcuts.themeDisplay = shortcut.shortcut;
|
||||
break;
|
||||
}
|
||||
})
|
||||
this._handleShortcutsChanged(shortcuts);
|
||||
})
|
||||
} else {
|
||||
this._handleShortcutsChanged(shortcuts);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
addShortcut(options: Partial<Options>) {
|
||||
const merged = { ...this.defaults, ...options };
|
||||
const event = `keydown.${merged.keys}`;
|
||||
|
||||
return new Observable(observer => {
|
||||
const handler = (e: KeyboardEvent) => {
|
||||
const activeElement = this.document.activeElement;
|
||||
const notOnInput = activeElement?.tagName !== 'INPUT' && activeElement?.tagName !== 'TEXTAREA';
|
||||
if (notOnInput) {
|
||||
if (document.URL.endsWith('/slides')) {
|
||||
e.preventDefault();
|
||||
observer.next(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const dispose = this.eventManager.addEventListener(
|
||||
merged.element, event, handler
|
||||
);
|
||||
|
||||
return () => {
|
||||
dispose();
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
protected _handleShortcutsChanged(shortcuts: Shortcuts) {
|
||||
this.shortcuts = shortcuts;
|
||||
this.shortcutsChanged$.emit(this.shortcuts);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user