mirror of https://github.com/Chocobozzz/PeerTube
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" dependencypull/6000/head
parent
e6b455b4ea
commit
50e415e12e
|
@ -195,7 +195,6 @@
|
||||||
"path-browserify",
|
"path-browserify",
|
||||||
"deep-merge",
|
"deep-merge",
|
||||||
"escape-string-regexp",
|
"escape-string-regexp",
|
||||||
"mousetrap",
|
|
||||||
"is-plain-object",
|
"is-plain-object",
|
||||||
"parse-srcset",
|
"parse-srcset",
|
||||||
"deepmerge",
|
"deepmerge",
|
||||||
|
|
|
@ -84,7 +84,6 @@
|
||||||
"@wdio/mocha-framework": "^8.10.4",
|
"@wdio/mocha-framework": "^8.10.4",
|
||||||
"@wdio/shared-store-service": "^8.10.5",
|
"@wdio/shared-store-service": "^8.10.5",
|
||||||
"@wdio/spec-reporter": "^8.10.5",
|
"@wdio/spec-reporter": "^8.10.5",
|
||||||
"angular2-hotkeys": "^13.1.0",
|
|
||||||
"angularx-qrcode": "16.0.0",
|
"angularx-qrcode": "16.0.0",
|
||||||
"babel-loader": "^9.1.0",
|
"babel-loader": "^9.1.0",
|
||||||
"bootstrap": "^5.1.3",
|
"bootstrap": "^5.1.3",
|
||||||
|
@ -126,6 +125,7 @@
|
||||||
"socket.io-client": "^4.5.4",
|
"socket.io-client": "^4.5.4",
|
||||||
"stylelint": "^15.1.0",
|
"stylelint": "^15.1.0",
|
||||||
"stylelint-config-sass-guidelines": "^10.0.0",
|
"stylelint-config-sass-guidelines": "^10.0.0",
|
||||||
|
"tinykeys": "^2.1.0",
|
||||||
"ts-loader": "^9.3.0",
|
"ts-loader": "^9.3.0",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"tslib": "^2.4.0",
|
"tslib": "^2.4.0",
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { Hotkey, HotkeysService } from 'angular2-hotkeys'
|
|
||||||
import { Subscription } from 'rxjs'
|
import { Subscription } from 'rxjs'
|
||||||
import { catchError, distinctUntilChanged, map, switchMap } from 'rxjs/operators'
|
import { catchError, distinctUntilChanged, map, switchMap } from 'rxjs/operators'
|
||||||
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'
|
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'
|
||||||
import { ActivatedRoute } from '@angular/router'
|
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 { Account, ListOverflowItem, VideoChannel, VideoChannelService, VideoService } from '@app/shared/shared-main'
|
||||||
import { BlocklistService } from '@app/shared/shared-moderation'
|
import { BlocklistService } from '@app/shared/shared-moderation'
|
||||||
import { SupportModalComponent } from '@app/shared/shared-support-modal'
|
import { SupportModalComponent } from '@app/shared/shared-support-modal'
|
||||||
|
@ -77,12 +76,12 @@ export class VideoChannelsComponent implements OnInit, OnDestroy {
|
||||||
})
|
})
|
||||||
|
|
||||||
this.hotkeys = [
|
this.hotkeys = [
|
||||||
new Hotkey('S', (event: KeyboardEvent): boolean => {
|
new Hotkey('Shift+s', () => {
|
||||||
if (this.subscribeButton.subscribed) this.subscribeButton.unsubscribe()
|
if (this.subscribeButton.isSubscribedToAll()) this.subscribeButton.unsubscribe()
|
||||||
else this.subscribeButton.subscribe()
|
else this.subscribeButton.subscribe()
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}, undefined, $localize`Subscribe to the account`)
|
}, $localize`Subscribe to the account`)
|
||||||
]
|
]
|
||||||
if (this.isUserLoggedIn()) this.hotkeysService.add(this.hotkeys)
|
if (this.isUserLoggedIn()) this.hotkeysService.add(this.hotkeys)
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { Hotkey, HotkeysService } from 'angular2-hotkeys'
|
|
||||||
import { Observable } from 'rxjs'
|
import { Observable } from 'rxjs'
|
||||||
import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output } from '@angular/core'
|
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 { VideoDetails, VideoService } from '@app/shared/shared-main'
|
||||||
import { UserVideoRateType } from '@peertube/peertube-models'
|
import { UserVideoRateType } from '@peertube/peertube-models'
|
||||||
|
|
||||||
|
@ -41,15 +40,15 @@ export class VideoRateComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
|
|
||||||
if (this.isUserLoggedIn) {
|
if (this.isUserLoggedIn) {
|
||||||
this.hotkeys = [
|
this.hotkeys = [
|
||||||
new Hotkey('shift+l', () => {
|
new Hotkey('Shift+l', () => {
|
||||||
this.setLike()
|
this.setLike()
|
||||||
return false
|
return false
|
||||||
}, undefined, $localize`Like the video`),
|
}, $localize`Like the video`),
|
||||||
|
|
||||||
new Hotkey('shift+d', () => {
|
new Hotkey('Shift+d', () => {
|
||||||
this.setDislike()
|
this.setDislike()
|
||||||
return false
|
return false
|
||||||
}, undefined, $localize`Dislike the video`)
|
}, $localize`Dislike the video`)
|
||||||
]
|
]
|
||||||
|
|
||||||
this.hotkeysService.add(this.hotkeys)
|
this.hotkeysService.add(this.hotkeys)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { Hotkey, HotkeysService } from 'angular2-hotkeys'
|
|
||||||
import { forkJoin, map, Observable, of, Subscription, switchMap } from 'rxjs'
|
import { forkJoin, map, Observable, of, Subscription, switchMap } from 'rxjs'
|
||||||
import { PlatformLocation } from '@angular/common'
|
import { PlatformLocation } from '@angular/common'
|
||||||
import { Component, ElementRef, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'
|
import { Component, ElementRef, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'
|
||||||
|
@ -13,6 +12,8 @@ import {
|
||||||
RestExtractor,
|
RestExtractor,
|
||||||
ScreenService,
|
ScreenService,
|
||||||
ServerService,
|
ServerService,
|
||||||
|
Hotkey,
|
||||||
|
HotkeysService,
|
||||||
User,
|
User,
|
||||||
UserService
|
UserService
|
||||||
} from '@app/core'
|
} from '@app/core'
|
||||||
|
@ -866,33 +867,33 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
this.hotkeys = [
|
this.hotkeys = [
|
||||||
// These hotkeys are managed by the player
|
// These hotkeys are managed by the player
|
||||||
new Hotkey('f', e => e, undefined, $localize`Enter/exit fullscreen`),
|
new Hotkey('f', e => e, $localize`Enter/exit fullscreen`),
|
||||||
new Hotkey('space', e => e, undefined, $localize`Play/Pause the video`),
|
new Hotkey('space', e => e, $localize`Play/Pause the video`),
|
||||||
new Hotkey('m', e => e, undefined, $localize`Mute/unmute the video`),
|
new Hotkey('m', e => e, $localize`Mute/unmute the video`),
|
||||||
|
|
||||||
new Hotkey('up', e => e, undefined, $localize`Increase the volume`),
|
new Hotkey('up', e => e, $localize`Increase the volume`),
|
||||||
new Hotkey('down', e => e, undefined, $localize`Decrease the volume`),
|
new Hotkey('down', e => e, $localize`Decrease the volume`),
|
||||||
|
|
||||||
new Hotkey('t', e => {
|
new Hotkey('t', e => {
|
||||||
this.theaterEnabled = !this.theaterEnabled
|
this.theaterEnabled = !this.theaterEnabled
|
||||||
return false
|
return false
|
||||||
}, undefined, $localize`Toggle theater mode`)
|
}, $localize`Toggle theater mode`)
|
||||||
]
|
]
|
||||||
|
|
||||||
if (!video.isLive) {
|
if (!video.isLive) {
|
||||||
this.hotkeys = this.hotkeys.concat([
|
this.hotkeys = this.hotkeys.concat([
|
||||||
// These hotkeys are also managed by the player but only for VOD
|
// 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('right', e => e, $localize`Seek the video forward`),
|
||||||
new Hotkey('left', e => e, undefined, $localize`Seek the video backward`),
|
new Hotkey('left', e => e, $localize`Seek the video backward`),
|
||||||
|
|
||||||
new Hotkey('>', e => e, undefined, $localize`Increase playback rate`),
|
new Hotkey('>', e => e, $localize`Increase playback rate`),
|
||||||
new Hotkey('<', e => e, undefined, $localize`Decrease 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, $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 next frame`)
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -903,7 +904,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
||||||
else this.subscribeButton.subscribe()
|
else this.subscribeButton.subscribe()
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}, undefined, $localize`Subscribe to the account`)
|
}, $localize`Subscribe to the account`)
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<a i18n class="visually-hidden-focusable skip-to-content" href="#content" (click)="$event.preventDefault(); mainContent.focus()">Skip to main content</a>
|
<a i18n class="visually-hidden-focusable skip-to-content" href="#content" (click)="$event.preventDefault(); mainContent.focus()">Skip to main content</a>
|
||||||
|
|
||||||
<my-hotkeys-cheatsheet (hotkeysModalStateChange)="onHotkeysModalStateChange($event)"></my-hotkeys-cheatsheet>
|
<my-hotkeys-cheat-sheet (hotkeysModalStateChange)="onHotkeysModalStateChange($event)"></my-hotkeys-cheat-sheet>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="peertube-container"
|
class="peertube-container"
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { Hotkey, HotkeysService } from 'angular2-hotkeys'
|
|
||||||
import { delay, forkJoin } from 'rxjs'
|
import { delay, forkJoin } from 'rxjs'
|
||||||
import { filter, first, map } from 'rxjs/operators'
|
import { filter, first, map } from 'rxjs/operators'
|
||||||
import { DOCUMENT, getLocaleDirection, PlatformLocation } from '@angular/common'
|
import { DOCUMENT, getLocaleDirection, PlatformLocation } from '@angular/common'
|
||||||
|
@ -15,7 +14,9 @@ import {
|
||||||
ServerService,
|
ServerService,
|
||||||
ThemeService,
|
ThemeService,
|
||||||
User,
|
User,
|
||||||
UserLocalStorageService
|
UserLocalStorageService,
|
||||||
|
Hotkey,
|
||||||
|
HotkeysService
|
||||||
} from '@app/core'
|
} from '@app/core'
|
||||||
import { HooksService } from '@app/core/plugins/hooks.service'
|
import { HooksService } from '@app/core/plugins/hooks.service'
|
||||||
import { PluginService } from '@app/core/plugins/plugin.service'
|
import { PluginService } from '@app/core/plugins/plugin.service'
|
||||||
|
@ -313,40 +314,40 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||||
|
|
||||||
private initHotkeys () {
|
private initHotkeys () {
|
||||||
this.hotkeysService.add([
|
this.hotkeysService.add([
|
||||||
new Hotkey([ '/', 's' ], (event: KeyboardEvent): boolean => {
|
new Hotkey([ '/', 's' ], () => {
|
||||||
document.getElementById('search-video').focus()
|
document.getElementById('search-video').focus()
|
||||||
return false
|
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()
|
this.menu.toggleMenu()
|
||||||
return false
|
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' ])
|
this.router.navigate([ '/videos/overview' ])
|
||||||
return false
|
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' ])
|
this.router.navigate([ '/videos/trending' ])
|
||||||
return false
|
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' ])
|
this.router.navigate([ '/videos/recently-added' ])
|
||||||
return false
|
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' ])
|
this.router.navigate([ '/videos/local' ])
|
||||||
return false
|
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' ])
|
this.router.navigate([ '/videos/upload' ])
|
||||||
return false
|
return false
|
||||||
}, undefined, $localize`Go to the videos upload page`)
|
}, $localize`Go to the videos upload page`)
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ import { SharedGlobalIconModule } from './shared/shared-icons'
|
||||||
import { SharedInstanceModule } from './shared/shared-instance'
|
import { SharedInstanceModule } from './shared/shared-instance'
|
||||||
import { SharedMainModule } from './shared/shared-main'
|
import { SharedMainModule } from './shared/shared-main'
|
||||||
import { SharedUserInterfaceSettingsModule } from './shared/shared-user-settings'
|
import { SharedUserInterfaceSettingsModule } from './shared/shared-user-settings'
|
||||||
|
import { HotkeysCheatSheetComponent } from './hotkeys'
|
||||||
|
|
||||||
registerLocaleData(localeOc, 'oc')
|
registerLocaleData(localeOc, 'oc')
|
||||||
|
|
||||||
|
@ -63,7 +64,9 @@ export function loadConfigFactory (server: ServerService, pluginService: PluginS
|
||||||
CustomModalComponent,
|
CustomModalComponent,
|
||||||
AdminWelcomeModalComponent,
|
AdminWelcomeModalComponent,
|
||||||
InstanceConfigWarningModalComponent,
|
InstanceConfigWarningModalComponent,
|
||||||
ConfirmComponent
|
ConfirmComponent,
|
||||||
|
|
||||||
|
HotkeysCheatSheetComponent
|
||||||
],
|
],
|
||||||
|
|
||||||
imports: [
|
imports: [
|
||||||
|
|
|
@ -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 { Observable, ReplaySubject, Subject, throwError as observableThrowError } from 'rxjs'
|
||||||
import { catchError, map, mergeMap, share, tap } from 'rxjs/operators'
|
import { catchError, map, mergeMap, share, tap } from 'rxjs/operators'
|
||||||
import { HttpClient, HttpErrorResponse, HttpHeaders, HttpParams } from '@angular/common/http'
|
import { HttpClient, HttpErrorResponse, HttpHeaders, HttpParams } from '@angular/common/http'
|
||||||
|
@ -57,22 +57,22 @@ export class AuthService {
|
||||||
|
|
||||||
// Set HotKeys
|
// Set HotKeys
|
||||||
this.hotkeys = [
|
this.hotkeys = [
|
||||||
new Hotkey('m s', (event: KeyboardEvent): boolean => {
|
new Hotkey('m s', e => {
|
||||||
this.router.navigate([ '/videos/subscriptions' ])
|
this.router.navigate([ '/videos/subscriptions' ])
|
||||||
return false
|
return false
|
||||||
}, undefined, $localize`Go to my subscriptions`),
|
}, $localize`Go to my subscriptions`),
|
||||||
new Hotkey('m v', (event: KeyboardEvent): boolean => {
|
new Hotkey('m v', e => {
|
||||||
this.router.navigate([ '/my-library/videos' ])
|
this.router.navigate([ '/my-library/videos' ])
|
||||||
return false
|
return false
|
||||||
}, undefined, $localize`Go to my videos`),
|
}, $localize`Go to my videos`),
|
||||||
new Hotkey('m i', (event: KeyboardEvent): boolean => {
|
new Hotkey('m i', e => {
|
||||||
this.router.navigate([ '/my-library/video-imports' ])
|
this.router.navigate([ '/my-library/video-imports' ])
|
||||||
return false
|
return false
|
||||||
}, undefined, $localize`Go to my imports`),
|
}, $localize`Go to my imports`),
|
||||||
new Hotkey('m c', (event: KeyboardEvent): boolean => {
|
new Hotkey('m c', e => {
|
||||||
this.router.navigate([ '/my-library/video-channels' ])
|
this.router.navigate([ '/my-library/video-channels' ])
|
||||||
return false
|
return false
|
||||||
}, undefined, $localize`Go to my channels`)
|
}, $localize`Go to my channels`)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { HotkeyModule } from 'angular2-hotkeys'
|
|
||||||
import { MessageService } from 'primeng/api'
|
import { MessageService } from 'primeng/api'
|
||||||
import { ToastModule } from 'primeng/toast'
|
import { ToastModule } from 'primeng/toast'
|
||||||
import { CommonModule } from '@angular/common'
|
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 { HooksService, PluginService } from '@app/core/plugins'
|
||||||
import { AuthService } from './auth'
|
import { AuthService } from './auth'
|
||||||
import { ConfirmService } from './confirm'
|
import { ConfirmService } from './confirm'
|
||||||
import { CheatSheetComponent } from './hotkeys'
|
|
||||||
import { MenuService } from './menu'
|
import { MenuService } from './menu'
|
||||||
import { throwIfAlreadyLoaded } from './module-import-guard'
|
import { throwIfAlreadyLoaded } from './module-import-guard'
|
||||||
import { Notifier } from './notification'
|
import { Notifier } from './notification'
|
||||||
|
@ -32,30 +30,23 @@ import { ServerService } from './server'
|
||||||
import { ThemeService } from './theme'
|
import { ThemeService } from './theme'
|
||||||
import { UserLocalStorageService, UserService } from './users'
|
import { UserLocalStorageService, UserService } from './users'
|
||||||
import { LocalStorageService, ScreenService, SessionStorageService } from './wrappers'
|
import { LocalStorageService, ScreenService, SessionStorageService } from './wrappers'
|
||||||
|
import { HotkeysService } from './hotkeys'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
|
|
||||||
ToastModule,
|
ToastModule
|
||||||
|
|
||||||
HotkeyModule.forRoot({
|
|
||||||
cheatSheetCloseEsc: true,
|
|
||||||
cheatSheetDescription: $localize`Show/hide this help menu`,
|
|
||||||
cheatSheetCloseEscDescription: $localize`Hide this help menu`
|
|
||||||
})
|
|
||||||
],
|
],
|
||||||
|
|
||||||
declarations: [
|
declarations: [
|
||||||
CheatSheetComponent,
|
|
||||||
HomepageRedirectComponent
|
HomepageRedirectComponent
|
||||||
],
|
],
|
||||||
|
|
||||||
exports: [
|
exports: [
|
||||||
ToastModule,
|
ToastModule,
|
||||||
|
|
||||||
CheatSheetComponent,
|
|
||||||
HomepageRedirectComponent
|
HomepageRedirectComponent
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -97,7 +88,9 @@ import { LocalStorageService, ScreenService, SessionStorageService } from './wra
|
||||||
ScrollService,
|
ScrollService,
|
||||||
|
|
||||||
MetaService,
|
MetaService,
|
||||||
MetaGuard
|
MetaGuard,
|
||||||
|
|
||||||
|
HotkeysService
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class CoreModule {
|
export class CoreModule {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<boolean>()
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<boolean>()
|
||||||
|
|
||||||
|
private hotkeys: Hotkey[] = []
|
||||||
|
private preventIn = [ 'INPUT', 'SELECT', 'TEXTAREA' ]
|
||||||
|
|
||||||
|
private disabled = false
|
||||||
|
|
||||||
|
private removeTinyKeysStore = new Map<Hotkey, (() => 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +1,2 @@
|
||||||
export * from './hotkeys.component'
|
export * from './hotkey.model'
|
||||||
|
export * from './hotkeys.service'
|
||||||
|
|
|
@ -2,6 +2,13 @@
|
||||||
<div class="cfp-hotkeys">
|
<div class="cfp-hotkeys">
|
||||||
<h1 class="cfp-hotkeys-title">{{ title }}</h1>
|
<h1 class="cfp-hotkeys-title">{{ title }}</h1>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-center m-3">
|
||||||
|
<my-peertube-checkbox
|
||||||
|
inputName="enable-hotkeys" [(ngModel)]="hotkeysEnabled" (ngModelChange)="onHotkeysEnabledChange()"
|
||||||
|
i18n-labelText labelText="Enable hotkeys in this web browser"
|
||||||
|
></my-peertube-checkbox>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ul role="presentation">
|
<ul role="presentation">
|
||||||
<li *ngFor="let hotkey of hotkeys">
|
<li *ngFor="let hotkey of hotkeys">
|
||||||
<div class="cfp-hotkeys-keys">
|
<div class="cfp-hotkeys-keys">
|
|
@ -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<boolean>()
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './hotkeys-cheat-sheet.component'
|
|
@ -1,4 +1,3 @@
|
||||||
import { HotkeysService } from 'angular2-hotkeys'
|
|
||||||
import * as debug from 'debug'
|
import * as debug from 'debug'
|
||||||
import { forkJoin, Subscription } from 'rxjs'
|
import { forkJoin, Subscription } from 'rxjs'
|
||||||
import { first, switchMap } from 'rxjs/operators'
|
import { first, switchMap } from 'rxjs/operators'
|
||||||
|
@ -10,6 +9,7 @@ import {
|
||||||
AuthStatus,
|
AuthStatus,
|
||||||
AuthUser,
|
AuthUser,
|
||||||
HooksService,
|
HooksService,
|
||||||
|
HotkeysService,
|
||||||
MenuSection,
|
MenuSection,
|
||||||
MenuService,
|
MenuService,
|
||||||
RedirectService,
|
RedirectService,
|
||||||
|
|
|
@ -2460,11 +2460,6 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.1.tgz#2f4f65bb08bc368ac39c96da7b2f09140b26851b"
|
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.1.tgz#2f4f65bb08bc368ac39c96da7b2f09140b26851b"
|
||||||
integrity sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==
|
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@*":
|
"@types/ms@*":
|
||||||
version "0.7.31"
|
version "0.7.31"
|
||||||
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
|
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"
|
json-schema-traverse "^0.4.1"
|
||||||
uri-js "^4.2.2"
|
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:
|
angularx-qrcode@16.0.0:
|
||||||
version "16.0.0"
|
version "16.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/angularx-qrcode/-/angularx-qrcode-16.0.0.tgz#c637924b8d8f9cc344216caa80adf3810ccd5e5b"
|
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"
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108"
|
||||||
integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==
|
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:
|
mpd-parser@0.22.1, mpd-parser@^0.22.1:
|
||||||
version "0.22.1"
|
version "0.22.1"
|
||||||
resolved "https://registry.yarnpkg.com/mpd-parser/-/mpd-parser-0.22.1.tgz#bc2bf7d3e56368e4b0121035b055675401871521"
|
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"
|
resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d"
|
||||||
integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==
|
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:
|
tmp@0.2.1, tmp@~0.2.1:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14"
|
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14"
|
||||||
|
|
Loading…
Reference in New Issue