From 50e415e12e10adc7360955f5463e01eb3b4e20bc Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 9 Oct 2023 15:33:19 +0200 Subject: [PATCH] Allow to disable all hotkeys Added angular2-hotkeys dependency inside PeerTube, to tweak some settings It will also allow us to support non latin keyboard in the future as we can choose the "mouse trap" dependency --- client/angular.json | 1 - client/package.json | 2 +- .../video-channels.component.ts | 9 +- .../action-buttons/video-rate.component.ts | 11 +- .../+video-watch/video-watch.component.ts | 31 ++--- client/src/app/app.component.html | 2 +- client/src/app/app.component.ts | 33 ++--- client/src/app/app.module.ts | 5 +- client/src/app/core/auth/auth.service.ts | 18 +-- client/src/app/core/core.module.ts | 17 +-- client/src/app/core/hotkeys/hotkey.model.ts | 59 +++++++++ .../src/app/core/hotkeys/hotkeys.component.ts | 53 -------- .../src/app/core/hotkeys/hotkeys.service.ts | 121 ++++++++++++++++++ client/src/app/core/hotkeys/index.ts | 3 +- .../hotkeys-cheat-sheet.component.html} | 7 + .../hotkeys-cheat-sheet.component.scss} | 0 .../hotkeys/hotkeys-cheat-sheet.component.ts | 74 +++++++++++ client/src/app/hotkeys/index.ts | 1 + client/src/app/menu/menu.component.ts | 2 +- client/yarn.lock | 24 +--- 20 files changed, 332 insertions(+), 141 deletions(-) create mode 100644 client/src/app/core/hotkeys/hotkey.model.ts delete mode 100644 client/src/app/core/hotkeys/hotkeys.component.ts create mode 100644 client/src/app/core/hotkeys/hotkeys.service.ts rename client/src/app/{core/hotkeys/hotkeys.component.html => hotkeys/hotkeys-cheat-sheet.component.html} (64%) rename client/src/app/{core/hotkeys/hotkeys.component.scss => hotkeys/hotkeys-cheat-sheet.component.scss} (100%) create mode 100644 client/src/app/hotkeys/hotkeys-cheat-sheet.component.ts create mode 100644 client/src/app/hotkeys/index.ts diff --git a/client/angular.json b/client/angular.json index 9b069422f..37af94e99 100644 --- a/client/angular.json +++ b/client/angular.json @@ -195,7 +195,6 @@ "path-browserify", "deep-merge", "escape-string-regexp", - "mousetrap", "is-plain-object", "parse-srcset", "deepmerge", diff --git a/client/package.json b/client/package.json index 5a0481be6..f10f774a8 100644 --- a/client/package.json +++ b/client/package.json @@ -84,7 +84,6 @@ "@wdio/mocha-framework": "^8.10.4", "@wdio/shared-store-service": "^8.10.5", "@wdio/spec-reporter": "^8.10.5", - "angular2-hotkeys": "^13.1.0", "angularx-qrcode": "16.0.0", "babel-loader": "^9.1.0", "bootstrap": "^5.1.3", @@ -126,6 +125,7 @@ "socket.io-client": "^4.5.4", "stylelint": "^15.1.0", "stylelint-config-sass-guidelines": "^10.0.0", + "tinykeys": "^2.1.0", "ts-loader": "^9.3.0", "ts-node": "^10.9.1", "tslib": "^2.4.0", diff --git a/client/src/app/+video-channels/video-channels.component.ts b/client/src/app/+video-channels/video-channels.component.ts index 40b3b19b7..3125cffc9 100644 --- a/client/src/app/+video-channels/video-channels.component.ts +++ b/client/src/app/+video-channels/video-channels.component.ts @@ -1,9 +1,8 @@ -import { Hotkey, HotkeysService } from 'angular2-hotkeys' import { Subscription } from 'rxjs' import { catchError, distinctUntilChanged, map, switchMap } from 'rxjs/operators' import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core' import { ActivatedRoute } from '@angular/router' -import { AuthService, MarkdownService, Notifier, RestExtractor, ScreenService } from '@app/core' +import { AuthService, MarkdownService, Notifier, RestExtractor, ScreenService, Hotkey, HotkeysService } from '@app/core' import { Account, ListOverflowItem, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main' import { BlocklistService } from '@app/shared/shared-moderation' import { SupportModalComponent } from '@app/shared/shared-support-modal' @@ -77,12 +76,12 @@ export class VideoChannelsComponent implements OnInit, OnDestroy { }) this.hotkeys = [ - new Hotkey('S', (event: KeyboardEvent): boolean => { - if (this.subscribeButton.subscribed) this.subscribeButton.unsubscribe() + new Hotkey('Shift+s', () => { + if (this.subscribeButton.isSubscribedToAll()) this.subscribeButton.unsubscribe() else this.subscribeButton.subscribe() return false - }, undefined, $localize`Subscribe to the account`) + }, $localize`Subscribe to the account`) ] if (this.isUserLoggedIn()) this.hotkeysService.add(this.hotkeys) diff --git a/client/src/app/+videos/+video-watch/shared/action-buttons/video-rate.component.ts b/client/src/app/+videos/+video-watch/shared/action-buttons/video-rate.component.ts index 13a709cb0..b8165e760 100644 --- a/client/src/app/+videos/+video-watch/shared/action-buttons/video-rate.component.ts +++ b/client/src/app/+videos/+video-watch/shared/action-buttons/video-rate.component.ts @@ -1,7 +1,6 @@ -import { Hotkey, HotkeysService } from 'angular2-hotkeys' import { Observable } from 'rxjs' import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output } from '@angular/core' -import { Notifier, ScreenService } from '@app/core' +import { Notifier, ScreenService, Hotkey, HotkeysService } from '@app/core' import { VideoDetails, VideoService } from '@app/shared/shared-main' import { UserVideoRateType } from '@peertube/peertube-models' @@ -41,15 +40,15 @@ export class VideoRateComponent implements OnInit, OnChanges, OnDestroy { if (this.isUserLoggedIn) { this.hotkeys = [ - new Hotkey('shift+l', () => { + new Hotkey('Shift+l', () => { this.setLike() return false - }, undefined, $localize`Like the video`), + }, $localize`Like the video`), - new Hotkey('shift+d', () => { + new Hotkey('Shift+d', () => { this.setDislike() return false - }, undefined, $localize`Dislike the video`) + }, $localize`Dislike the video`) ] this.hotkeysService.add(this.hotkeys) diff --git a/client/src/app/+videos/+video-watch/video-watch.component.ts b/client/src/app/+videos/+video-watch/video-watch.component.ts index 39c9c7986..2524d0216 100644 --- a/client/src/app/+videos/+video-watch/video-watch.component.ts +++ b/client/src/app/+videos/+video-watch/video-watch.component.ts @@ -1,4 +1,3 @@ -import { Hotkey, HotkeysService } from 'angular2-hotkeys' import { forkJoin, map, Observable, of, Subscription, switchMap } from 'rxjs' import { PlatformLocation } from '@angular/common' import { Component, ElementRef, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core' @@ -13,6 +12,8 @@ import { RestExtractor, ScreenService, ServerService, + Hotkey, + HotkeysService, User, UserService } from '@app/core' @@ -866,33 +867,33 @@ export class VideoWatchComponent implements OnInit, OnDestroy { this.hotkeys = [ // These hotkeys are managed by the player - new Hotkey('f', e => e, undefined, $localize`Enter/exit fullscreen`), - new Hotkey('space', e => e, undefined, $localize`Play/Pause the video`), - new Hotkey('m', e => e, undefined, $localize`Mute/unmute the video`), + new Hotkey('f', e => e, $localize`Enter/exit fullscreen`), + new Hotkey('space', e => e, $localize`Play/Pause the video`), + new Hotkey('m', e => e, $localize`Mute/unmute the video`), - new Hotkey('up', e => e, undefined, $localize`Increase the volume`), - new Hotkey('down', e => e, undefined, $localize`Decrease the volume`), + new Hotkey('up', e => e, $localize`Increase the volume`), + new Hotkey('down', e => e, $localize`Decrease the volume`), new Hotkey('t', e => { this.theaterEnabled = !this.theaterEnabled return false - }, undefined, $localize`Toggle theater mode`) + }, $localize`Toggle theater mode`) ] if (!video.isLive) { this.hotkeys = this.hotkeys.concat([ // These hotkeys are also managed by the player but only for VOD - new Hotkey('0-9', e => e, undefined, $localize`Skip to a percentage of the video: 0 is 0% and 9 is 90%`), + new Hotkey('0-9', e => e, $localize`Skip to a percentage of the video: 0 is 0% and 9 is 90%`), - new Hotkey('right', e => e, undefined, $localize`Seek the video forward`), - new Hotkey('left', e => e, undefined, $localize`Seek the video backward`), + new Hotkey('right', e => e, $localize`Seek the video forward`), + new Hotkey('left', e => e, $localize`Seek the video backward`), - new Hotkey('>', e => e, undefined, $localize`Increase playback rate`), - new Hotkey('<', e => e, undefined, $localize`Decrease playback rate`), + new Hotkey('>', e => e, $localize`Increase playback rate`), + new Hotkey('<', e => e, $localize`Decrease playback rate`), - new Hotkey(',', e => e, undefined, $localize`Navigate in the video to the previous frame`), - new Hotkey('.', e => e, undefined, $localize`Navigate in the video to the next frame`) + new Hotkey(',', e => e, $localize`Navigate in the video to the previous frame`), + new Hotkey('.', e => e, $localize`Navigate in the video to the next frame`) ]) } @@ -903,7 +904,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { else this.subscribeButton.subscribe() return false - }, undefined, $localize`Subscribe to the account`) + }, $localize`Subscribe to the account`) ]) } diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html index da3c0e3dd..aa742bf03 100644 --- a/client/src/app/app.component.html +++ b/client/src/app/app.component.html @@ -2,7 +2,7 @@ Skip to main content - +
{ + new Hotkey([ '/', 's' ], () => { document.getElementById('search-video').focus() return false - }, undefined, $localize`Focus the search bar`), + }, $localize`Focus the search bar`), - new Hotkey('b', (event: KeyboardEvent): boolean => { + new Hotkey('b', () => { this.menu.toggleMenu() return false - }, undefined, $localize`Toggle the left menu`), + }, $localize`Toggle the left menu`), - new Hotkey('g o', (event: KeyboardEvent): boolean => { + new Hotkey('g o', () => { this.router.navigate([ '/videos/overview' ]) return false - }, undefined, $localize`Go to the discover videos page`), + }, $localize`Go to the discover videos page`), - new Hotkey('g t', (event: KeyboardEvent): boolean => { + new Hotkey('g t', () => { this.router.navigate([ '/videos/trending' ]) return false - }, undefined, $localize`Go to the trending videos page`), + }, $localize`Go to the trending videos page`), - new Hotkey('g r', (event: KeyboardEvent): boolean => { + new Hotkey('g r', () => { this.router.navigate([ '/videos/recently-added' ]) return false - }, undefined, $localize`Go to the recently added videos page`), + }, $localize`Go to the recently added videos page`), - new Hotkey('g l', (event: KeyboardEvent): boolean => { + new Hotkey('g l', () => { this.router.navigate([ '/videos/local' ]) return false - }, undefined, $localize`Go to the local videos page`), + }, $localize`Go to the local videos page`), - new Hotkey('g u', (event: KeyboardEvent): boolean => { + new Hotkey('g u', () => { this.router.navigate([ '/videos/upload' ]) return false - }, undefined, $localize`Go to the videos upload page`) + }, $localize`Go to the videos upload page`) ]) } diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index 9339865f1..6485c98c9 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts @@ -26,6 +26,7 @@ import { SharedGlobalIconModule } from './shared/shared-icons' import { SharedInstanceModule } from './shared/shared-instance' import { SharedMainModule } from './shared/shared-main' import { SharedUserInterfaceSettingsModule } from './shared/shared-user-settings' +import { HotkeysCheatSheetComponent } from './hotkeys' registerLocaleData(localeOc, 'oc') @@ -63,7 +64,9 @@ export function loadConfigFactory (server: ServerService, pluginService: PluginS CustomModalComponent, AdminWelcomeModalComponent, InstanceConfigWarningModalComponent, - ConfirmComponent + ConfirmComponent, + + HotkeysCheatSheetComponent ], imports: [ diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts index bc67ab7a0..2ec4a4b7e 100644 --- a/client/src/app/core/auth/auth.service.ts +++ b/client/src/app/core/auth/auth.service.ts @@ -1,4 +1,4 @@ -import { Hotkey, HotkeysService } from 'angular2-hotkeys' +import { Hotkey, HotkeysService } from '@app/core' import { Observable, ReplaySubject, Subject, throwError as observableThrowError } from 'rxjs' import { catchError, map, mergeMap, share, tap } from 'rxjs/operators' import { HttpClient, HttpErrorResponse, HttpHeaders, HttpParams } from '@angular/common/http' @@ -57,22 +57,22 @@ export class AuthService { // Set HotKeys this.hotkeys = [ - new Hotkey('m s', (event: KeyboardEvent): boolean => { + new Hotkey('m s', e => { this.router.navigate([ '/videos/subscriptions' ]) return false - }, undefined, $localize`Go to my subscriptions`), - new Hotkey('m v', (event: KeyboardEvent): boolean => { + }, $localize`Go to my subscriptions`), + new Hotkey('m v', e => { this.router.navigate([ '/my-library/videos' ]) return false - }, undefined, $localize`Go to my videos`), - new Hotkey('m i', (event: KeyboardEvent): boolean => { + }, $localize`Go to my videos`), + new Hotkey('m i', e => { this.router.navigate([ '/my-library/video-imports' ]) return false - }, undefined, $localize`Go to my imports`), - new Hotkey('m c', (event: KeyboardEvent): boolean => { + }, $localize`Go to my imports`), + new Hotkey('m c', e => { this.router.navigate([ '/my-library/video-channels' ]) return false - }, undefined, $localize`Go to my channels`) + }, $localize`Go to my channels`) ] } diff --git a/client/src/app/core/core.module.ts b/client/src/app/core/core.module.ts index 4a4c2321b..c66dd5b57 100644 --- a/client/src/app/core/core.module.ts +++ b/client/src/app/core/core.module.ts @@ -1,4 +1,3 @@ -import { HotkeyModule } from 'angular2-hotkeys' import { MessageService } from 'primeng/api' import { ToastModule } from 'primeng/toast' import { CommonModule } from '@angular/common' @@ -8,7 +7,6 @@ import { PeerTubeSocket } from '@app/core/notification/peertube-socket.service' import { HooksService, PluginService } from '@app/core/plugins' import { AuthService } from './auth' import { ConfirmService } from './confirm' -import { CheatSheetComponent } from './hotkeys' import { MenuService } from './menu' import { throwIfAlreadyLoaded } from './module-import-guard' import { Notifier } from './notification' @@ -32,30 +30,23 @@ import { ServerService } from './server' import { ThemeService } from './theme' import { UserLocalStorageService, UserService } from './users' import { LocalStorageService, ScreenService, SessionStorageService } from './wrappers' +import { HotkeysService } from './hotkeys' @NgModule({ imports: [ CommonModule, BrowserAnimationsModule, - ToastModule, - - HotkeyModule.forRoot({ - cheatSheetCloseEsc: true, - cheatSheetDescription: $localize`Show/hide this help menu`, - cheatSheetCloseEscDescription: $localize`Hide this help menu` - }) + ToastModule ], declarations: [ - CheatSheetComponent, HomepageRedirectComponent ], exports: [ ToastModule, - CheatSheetComponent, HomepageRedirectComponent ], @@ -97,7 +88,9 @@ import { LocalStorageService, ScreenService, SessionStorageService } from './wra ScrollService, MetaService, - MetaGuard + MetaGuard, + + HotkeysService ] }) export class CoreModule { diff --git a/client/src/app/core/hotkeys/hotkey.model.ts b/client/src/app/core/hotkeys/hotkey.model.ts new file mode 100644 index 000000000..e4e974352 --- /dev/null +++ b/client/src/app/core/hotkeys/hotkey.model.ts @@ -0,0 +1,59 @@ +// Thanks to https://github.com/brtnshrdr/angular2-hotkeys + +import { arrayify } from '@peertube/peertube-core-utils' + +export class Hotkey { + private formattedHotkey: string[] + + static symbolize (combo: string): string { + const map: any = { + command: '\u2318', // ⌘ + shift: '\u21E7', // ⇧ + left: '\u2190', // ← + right: '\u2192', // → + up: '\u2191', // ↑ + down: '\u2193', // ↓ + return: '\u23CE', // ⏎ + backspace: '\u232B' // ⌫ + } + const comboSplit: string[] = combo.split('+') + + for (let i = 0; i < comboSplit.length; i++) { + // try to resolve command / ctrl based on OS: + if (comboSplit[i] === 'mod') { + if (window.navigator?.platform.includes('Mac')) { + comboSplit[i] = 'command' + } else { + comboSplit[i] = 'ctrl' + } + } + + comboSplit[i] = map[comboSplit[i]] || comboSplit[i] + } + + return comboSplit.join(' + ') + } + + constructor ( + public combo: string | string[], + public callback: (event: KeyboardEvent, combo: string) => any | boolean, + public description?: string | Function + ) { + this.combo = arrayify(combo) + this.description = description || '' + } + + get formatted (): string[] { + if (!this.formattedHotkey) { + const sequence: string[] = [ ...this.combo ] + + for (let i = 0; i < sequence.length; i++) { + sequence[i] = Hotkey.symbolize(sequence[i]) + } + + this.formattedHotkey = sequence + } + + return this.formattedHotkey + } +} diff --git a/client/src/app/core/hotkeys/hotkeys.component.ts b/client/src/app/core/hotkeys/hotkeys.component.ts deleted file mode 100644 index 2c610a442..000000000 --- a/client/src/app/core/hotkeys/hotkeys.component.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Hotkey, HotkeysService } from 'angular2-hotkeys' -import { Subscription } from 'rxjs' -import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core' - -@Component({ - selector: 'my-hotkeys-cheatsheet', - templateUrl: './hotkeys.component.html', - styleUrls: [ './hotkeys.component.scss' ] -}) -export class CheatSheetComponent implements OnInit, OnDestroy { - @Input() title = $localize`Keyboard Shortcuts:` - - @Output() hotkeysModalStateChange = new EventEmitter() - - helpVisible = false - subscription: Subscription - - hotkeys: Hotkey[] - - constructor ( - private hotkeysService: HotkeysService - ) {} - - public ngOnInit (): void { - this.subscription = this.hotkeysService.cheatSheetToggle.subscribe((isOpen) => { - if (isOpen !== false) { - this.hotkeys = this.hotkeysService.hotkeys.filter(hotkey => hotkey.description) - } - - if (isOpen === false) { - this.helpVisible = false - } else { - this.toggleHelpVisible() - } - - this.hotkeysModalStateChange.emit(this.helpVisible) - }) - } - - public ngOnDestroy (): void { - if (this.subscription) { - this.subscription.unsubscribe() - } - } - - public toggleCheatSheet (): void { - this.hotkeysService.cheatSheetToggle.next(!this.helpVisible) - } - - public toggleHelpVisible (): void { - this.helpVisible = !this.helpVisible - } -} diff --git a/client/src/app/core/hotkeys/hotkeys.service.ts b/client/src/app/core/hotkeys/hotkeys.service.ts new file mode 100644 index 000000000..9e0f5a70b --- /dev/null +++ b/client/src/app/core/hotkeys/hotkeys.service.ts @@ -0,0 +1,121 @@ +// Thanks to https://github.com/brtnshrdr/angular2-hotkeys + +import { Injectable } from '@angular/core' +import { Hotkey } from './hotkey.model' +import { Subject } from 'rxjs' +import { tinykeys } from 'tinykeys' +import debug from 'debug' + +const debugLogger = debug('peertube:hotkeys') + +@Injectable() +export class HotkeysService { + cheatSheetToggle = new Subject() + + private hotkeys: Hotkey[] = [] + private preventIn = [ 'INPUT', 'SELECT', 'TEXTAREA' ] + + private disabled = false + + private removeTinyKeysStore = new Map void)[]>() + + constructor () { + this.initCheatSheet() + } + + private initCheatSheet () { + debugLogger('Init hotkeys') + + this.add([ + new Hotkey( + [ '?', 'Shift+?' ], + () => this.cheatSheetToggle.next(undefined), + $localize`Show / hide this help menu` + ), + + new Hotkey( + 'escape', + () => this.cheatSheetToggle.next(false), + $localize`Hide this help menu` + ) + ]) + } + + add (hotkey: Hotkey): Hotkey + add (hotkey: Hotkey[]): Hotkey[] + add (hotkey: Hotkey | Hotkey[]): Hotkey[] | Hotkey { + if (Array.isArray(hotkey)) { + return hotkey.map(h => this.add(h)) + } + + this.remove(hotkey) + this.hotkeys.push(hotkey) + + for (const combo of hotkey.combo) { + debugLogger('Adding hotkey ' + hotkey.formatted) + + const removeTinyKey = tinykeys(window, { + [combo]: event => { + if (this.disabled) return + + const target = event.target as Element + const nodeName: string = target.nodeName.toUpperCase() + + if (this.preventIn.includes(nodeName)) { + return + } + + const result = hotkey.callback.apply(this, [ event, combo ]) + + if (result === false) { + event.preventDefault() + event.stopPropagation() + } + } + }) + + if (!this.removeTinyKeysStore.has(hotkey)) { + this.removeTinyKeysStore.set(hotkey, []) + } + + this.removeTinyKeysStore.get(hotkey).push(removeTinyKey) + } + + return hotkey + } + + remove (hotkey: Hotkey | Hotkey[]) { + if (Array.isArray(hotkey)) { + for (const h of hotkey) { + this.remove(h) + } + + return + } + + this.hotkeys = this.hotkeys.filter(h => h !== hotkey) + const removeHandlers = this.removeTinyKeysStore.get(hotkey) + + if (removeHandlers) { + debugLogger('Removing hotkey ' + hotkey.formatted) + + for (const removeHandler of removeHandlers) { + removeHandler() + } + } + + this.removeTinyKeysStore.delete(hotkey) + } + + getHotkeys () { + return this.hotkeys + } + + disableHotkeys () { + this.disabled = true + } + + enableHotkeys () { + this.disabled = false + } +} diff --git a/client/src/app/core/hotkeys/index.ts b/client/src/app/core/hotkeys/index.ts index a8d807c71..192e27dbd 100644 --- a/client/src/app/core/hotkeys/index.ts +++ b/client/src/app/core/hotkeys/index.ts @@ -1 +1,2 @@ -export * from './hotkeys.component' +export * from './hotkey.model' +export * from './hotkeys.service' diff --git a/client/src/app/core/hotkeys/hotkeys.component.html b/client/src/app/hotkeys/hotkeys-cheat-sheet.component.html similarity index 64% rename from client/src/app/core/hotkeys/hotkeys.component.html rename to client/src/app/hotkeys/hotkeys-cheat-sheet.component.html index 103f28411..ea9a46724 100644 --- a/client/src/app/core/hotkeys/hotkeys.component.html +++ b/client/src/app/hotkeys/hotkeys-cheat-sheet.component.html @@ -2,6 +2,13 @@

{{ title }}

+
+ +
+
  • diff --git a/client/src/app/core/hotkeys/hotkeys.component.scss b/client/src/app/hotkeys/hotkeys-cheat-sheet.component.scss similarity index 100% rename from client/src/app/core/hotkeys/hotkeys.component.scss rename to client/src/app/hotkeys/hotkeys-cheat-sheet.component.scss diff --git a/client/src/app/hotkeys/hotkeys-cheat-sheet.component.ts b/client/src/app/hotkeys/hotkeys-cheat-sheet.component.ts new file mode 100644 index 000000000..68e6460bb --- /dev/null +++ b/client/src/app/hotkeys/hotkeys-cheat-sheet.component.ts @@ -0,0 +1,74 @@ +import { Subscription } from 'rxjs' +import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core' +import { LocalStorageService, HotkeysService, Hotkey } from '@app/core' + +@Component({ + selector: 'my-hotkeys-cheat-sheet', + templateUrl: './hotkeys-cheat-sheet.component.html', + styleUrls: [ './hotkeys-cheat-sheet.component.scss' ] +}) +export class HotkeysCheatSheetComponent implements OnInit, OnDestroy { + @Input() title = $localize`Keyboard Shortcuts` + + @Output() hotkeysModalStateChange = new EventEmitter() + + hotkeysEnabled = true + + helpVisible = false + subscription: Subscription + + hotkeys: Hotkey[] + + private readonly localStorageHotkeysDisabledKey = 'peertube-hotkeys-disabled' + + constructor ( + private hotkeysService: HotkeysService, + private localStorage: LocalStorageService + ) {} + + ngOnInit () { + if (this.localStorage.getItem(this.localStorageHotkeysDisabledKey) === 'true') { + this.hotkeysEnabled = false + this.hotkeysService.disableHotkeys() + } + + this.subscription = this.hotkeysService.cheatSheetToggle.subscribe(isOpen => { + if (isOpen !== false) { + this.hotkeys = this.hotkeysService.getHotkeys().filter(hotkey => hotkey.description) + } + + if (isOpen === false) { + this.helpVisible = false + } else { + this.toggleHelpVisible() + } + + this.hotkeysModalStateChange.emit(this.helpVisible) + }) + } + + ngOnDestroy () { + if (this.subscription) { + this.subscription.unsubscribe() + } + } + + toggleCheatSheet () { + this.hotkeysService.cheatSheetToggle.next(!this.helpVisible) + } + + toggleHelpVisible () { + this.helpVisible = !this.helpVisible + } + + onHotkeysEnabledChange () { + if (!this.hotkeysEnabled) { + this.localStorage.setItem(this.localStorageHotkeysDisabledKey, 'true') + this.hotkeysService.disableHotkeys() + return + } + + this.hotkeysService.enableHotkeys() + this.localStorage.removeItem(this.localStorageHotkeysDisabledKey) + } +} diff --git a/client/src/app/hotkeys/index.ts b/client/src/app/hotkeys/index.ts new file mode 100644 index 000000000..a00ecee62 --- /dev/null +++ b/client/src/app/hotkeys/index.ts @@ -0,0 +1 @@ +export * from './hotkeys-cheat-sheet.component' diff --git a/client/src/app/menu/menu.component.ts b/client/src/app/menu/menu.component.ts index 31118b476..c896da116 100644 --- a/client/src/app/menu/menu.component.ts +++ b/client/src/app/menu/menu.component.ts @@ -1,4 +1,3 @@ -import { HotkeysService } from 'angular2-hotkeys' import * as debug from 'debug' import { forkJoin, Subscription } from 'rxjs' import { first, switchMap } from 'rxjs/operators' @@ -10,6 +9,7 @@ import { AuthStatus, AuthUser, HooksService, + HotkeysService, MenuSection, MenuService, RedirectService, diff --git a/client/yarn.lock b/client/yarn.lock index cc0bd23a6..ddaaa7c9c 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -2460,11 +2460,6 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.1.tgz#2f4f65bb08bc368ac39c96da7b2f09140b26851b" integrity sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q== -"@types/mousetrap@^1.6.9": - version "1.6.11" - resolved "https://registry.yarnpkg.com/@types/mousetrap/-/mousetrap-1.6.11.tgz#ef9620160fdcefcb85bccda8aaa3e84d7429376d" - integrity sha512-F0oAily9Q9QQpv9JKxKn0zMKfOo36KHCW7myYsmUyf2t0g+sBTbG3UleTPoguHdE1z3GLFr3p7/wiOio52QFjQ== - "@types/ms@*": version "0.7.31" resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" @@ -3293,15 +3288,6 @@ ajv@^6.10.0, ajv@^6.12.4, ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -angular2-hotkeys@^13.1.0: - version "13.4.0" - resolved "https://registry.yarnpkg.com/angular2-hotkeys/-/angular2-hotkeys-13.4.0.tgz#a96676466936556655cd64f92e1f5cd3aeac8e10" - integrity sha512-WvkouvdXtTYw3tpuaoEVF+ue41pvI2XSa8m4tVRPLzAblT/f7PG0uQO4npyjVw3oDIc7qnFkQR+oqGl1KM1eow== - dependencies: - "@types/mousetrap" "^1.6.9" - mousetrap "^1.6.5" - tslib "^2.3.1" - angularx-qrcode@16.0.0: version "16.0.0" resolved "https://registry.yarnpkg.com/angularx-qrcode/-/angularx-qrcode-16.0.0.tgz#c637924b8d8f9cc344216caa80adf3810ccd5e5b" @@ -8186,11 +8172,6 @@ moment@^2.10.2: resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== -mousetrap@^1.6.5: - version "1.6.5" - resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.5.tgz#8a766d8c272b08393d5f56074e0b5ec183485bf9" - integrity sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA== - mpd-parser@0.22.1, mpd-parser@^0.22.1: version "0.22.1" resolved "https://registry.yarnpkg.com/mpd-parser/-/mpd-parser-0.22.1.tgz#bc2bf7d3e56368e4b0121035b055675401871521" @@ -10789,6 +10770,11 @@ thunky@^1.0.2: resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== +tinykeys@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tinykeys/-/tinykeys-2.1.0.tgz#1341563e92a7fac9ca90053fddaf2b7553500298" + integrity sha512-/MESnqBD1xItZJn5oGQ4OsNORQgJfPP96XSGoyu4eLpwpL0ifO0SYR5OD76u0YMhMXsqkb0UqvI9+yXTh4xv8Q== + tmp@0.2.1, tmp@~0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14"