Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add keyboard shortcuts to titles #5857

Open
wants to merge 18 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
fe5de7f
Update title for custom FreeTube button labels with keyboard shortcuts
kommunarr Oct 13, 2024
3134053
Add keyboard shortcut to unmodified Shaka control labels
kommunarr Oct 13, 2024
0d3882e
Replace in-code use of available shortcuts with the corresponding con…
kommunarr Oct 13, 2024
443c4af
Add explanatory comments
kommunarr Oct 14, 2024
42c3128
Fix captions constant name
kommunarr Oct 14, 2024
7c379e2
Prevent creating new shortcut localization if Shaka localization key …
kommunarr Oct 14, 2024
764cace
Add util functions to localize special keys
kommunarr Oct 14, 2024
6798c3a
Replace more video player shortcut usage in code with corresponding c…
kommunarr Oct 14, 2024
6f8d212
Add labels for History and Settings app shortcuts, and update variabl…
kommunarr Oct 14, 2024
16693ea
Display 'Option' instead of 'Alt' for Mac users
kommunarr Oct 20, 2024
cf263fb
Use Mac icons in keyboard shortcuts for Macs
kommunarr Oct 20, 2024
d642682
Update Mac arrow icon choice
kommunarr Oct 23, 2024
348100d
Update KeyboardShortcuts constant organization in preparation for key…
kommunarr Oct 23, 2024
dd1fffa
Merge branch 'development' of github.com:FreeTubeApp/FreeTube into fe…
kommunarr Nov 23, 2024
89b6861
Adjust nameSpan._textContent to be set to same value as aria-label
kommunarr Nov 23, 2024
fdae9b4
Merge branch 'feat/add-shortcut-labels' of github.com:kommunarr/FreeT…
kommunarr Nov 23, 2024
304fa82
Add code comment explaining shakaControlKeysToShortcuts
kommunarr Nov 24, 2024
532fcc7
Move changes from deleted Popular.js to Popular.vue
kommunarr Nov 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,47 @@ const SyncEvents = {
},
}

// note: the multi-key shortcut values are currently just for display use in action titles
const KeyboardShortcuts = {
APP: {
HISTORY_BACKWARD: 'alt+arrowleft',
HISTORY_FORWARD: 'alt+arrowright',
NEW_WINDOW: 'ctrl+N',
NAVIGATE_TO_SETTINGS: 'ctrl+,',
NAVIGATE_TO_HISTORY: 'ctrl+H',
NAVIGATE_TO_HISTORY_MAC: 'cmd+Y',
},
FEED: {
REFRESH: 'r'
},
VIDEO_PLAYER: {
CAPTIONS: 'c',
STATS: 'd',
FULLSCREEN: 'f',
PICTURE_IN_PICTURE: 'i',
LARGE_REWIND: 'j',
PLAY: 'k',
LARGE_FAST_FORWARD: 'l',
MUTE: 'm',
DECREASE_VIDEO_SPEED: 'o',
INCREASE_VIDEO_SPEED: 'p',
FULLWINDOW: 's',
THEATRE_MODE: 't',
TAKE_SCREENSHOT: 'u',
LAST_FRAME: ',',
NEXT_FRAME: '.',
VOLUME_UP: 'arrowup',
VOLUME_DOWN: 'arrowdown',
SMALL_REWIND: 'arrowleft',
SMALL_FAST_FORWARD: 'arrowright',

/* For future use
LAST_CHAPTER: 'ctrl+arrowleft',
NEXT_CHAPTER: 'ctrl+arrowright',
*/
},
}

// Utils
const MAIN_PROFILE_ID = 'allChannels'

Expand All @@ -132,6 +173,7 @@ export {
IpcChannels,
DBActions,
SyncEvents,
KeyboardShortcuts,
MAIN_PROFILE_ID,
MOBILE_WIDTH_THRESHOLD,
PLAYLIST_HEIGHT_FORCE_LIST_THRESHOLD,
Expand Down
10 changes: 10 additions & 0 deletions src/renderer/components/ft-refresh-widget/ft-refresh-widget.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { defineComponent } from 'vue'

import FtIconButton from '../ft-icon-button/ft-icon-button.vue'
import { KeyboardShortcuts } from '../../../constants'
import { addKeyboardShortcutToActionTitle } from '../../helpers/utils'

export default defineComponent({
name: 'FtRefreshWidget',
Expand All @@ -22,6 +24,14 @@ export default defineComponent({
}
},
emits: ['click'],
computed: {
refreshFeedButtonTitle: function() {
return addKeyboardShortcutToActionTitle(
this.$t('Feed.Refresh Feed', { subscriptionName: this.title }),
KeyboardShortcuts.FEED.REFRESH
)
}
},
methods: {
click: function() {
this.$emit('click')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
:disabled="disableRefresh"
:icon="['fas', 'sync']"
class="refreshButton"
:title="$t('Feed.Refresh Feed', { subscriptionName: title })"
:title="refreshFeedButtonTitle"
:size="12"
theme="primary"
@click="click"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import shaka from 'shaka-player'
import store from '../../store/index'
import i18n from '../../i18n/index'

import { IpcChannels } from '../../../constants'
import { IpcChannels, KeyboardShortcuts } from '../../../constants'
import { AudioTrackSelection } from './player-components/AudioTrackSelection'
import { FullWindowButton } from './player-components/FullWindowButton'
import { LegacyQualitySelection } from './player-components/LegacyQualitySelection'
Expand All @@ -24,6 +24,7 @@ import {
translateSponsorBlockCategory
} from '../../helpers/player/utils'
import {
addKeyboardShortcutToActionTitle,
getPicturesPath,
showSaveDialog,
showToast
Expand All @@ -41,6 +42,19 @@ const RequestType = shaka.net.NetworkingEngine.RequestType
const AdvancedRequestType = shaka.net.NetworkingEngine.AdvancedRequestType
const TrackLabelFormat = shaka.ui.Overlay.TrackLabelFormat

const shakaControlKeysToShortcuts = {
MUTE: KeyboardShortcuts.VIDEO_PLAYER.MUTE,
UNMUTE: KeyboardShortcuts.VIDEO_PLAYER.MUTE,
PLAY: KeyboardShortcuts.VIDEO_PLAYER.PLAY,
PAUSE: KeyboardShortcuts.VIDEO_PLAYER.PLAY,
PICTURE_IN_PICTURE: KeyboardShortcuts.VIDEO_PLAYER.PICTURE_IN_PICTURE,
ENTER_PICTURE_IN_PICTURE: KeyboardShortcuts.VIDEO_PLAYER.PICTURE_IN_PICTURE,
EXIT_PICTURE_IN_PICTURE: KeyboardShortcuts.VIDEO_PLAYER.PICTURE_IN_PICTURE,
CAPTIONS: KeyboardShortcuts.VIDEO_PLAYER.CAPTIONS,
FULL_SCREEN: KeyboardShortcuts.VIDEO_PLAYER.FULLSCREEN,
EXIT_FULL_SCREEN: KeyboardShortcuts.VIDEO_PLAYER.FULLSCREEN
}

/** @type {Map<string, string>} */
const LOCALE_MAPPINGS = new Map(process.env.SHAKA_LOCALE_MAPPINGS)

Expand Down Expand Up @@ -968,7 +982,7 @@ export default defineComponent({
* @param {string} locale
*/
async function setLocale(locale) {
// For most of FreeTube's locales their is an equivalent one in shaka-player,
// For most of FreeTube's locales, there is an equivalent one in shaka-player,
// however if there isn't one we should fall back to US English.
// At the time of writing "et", "eu", "gl", "is" don't have any translations
const shakaLocale = LOCALE_MAPPINGS.get(locale) ?? 'en'
Expand All @@ -989,6 +1003,27 @@ export default defineComponent({

localization.changeLocale([shakaLocale])

// Add the keyboard shortcut to the label for the default Shaka controls

const shakaControlKeysToShortcutLocalizations = new Map()
Object.entries(shakaControlKeysToShortcuts).forEach(([shakaControlKey, shortcut]) => {
const originalLocalization = localization.resolve(shakaControlKey)
if (originalLocalization === '') {
// e.g., A Shaka localization key in shakaControlKeysToShortcuts has fallen out of date and need to be updated
console.error('Mising Shaka localization key "%s"', shakaControlKey)
return
}

const localizationWithShortcut = addKeyboardShortcutToActionTitle(
originalLocalization,
shortcut
)

shakaControlKeysToShortcutLocalizations.set(shakaControlKey, localizationWithShortcut)
})

localization.insert(shakaLocale, shakaControlKeysToShortcutLocalizations)
Comment on lines +1011 to +1030
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: For locales that shaka-player doesn't pre-bundle (so the ones loaded by the code above), we call .insert() twice, once to add the strings and the second time to overwrite them with the shortcuts text. So in the future it might be worth considering having two separate code paths, the current one would be used for locales that are pre-bundled and a second one that combines the fetching and modifications in a single step so we only need to insert the strings once.


events.dispatchEvent(new CustomEvent('localeChanged'))
}

Expand Down Expand Up @@ -1979,55 +2014,47 @@ export default defineComponent({

const video_ = video.value

switch (event.key) {
switch (event.key.toLowerCase()) {
case ' ':
case 'Spacebar': // older browsers might return spacebar instead of a space character
case 'K':
case 'k':
case 'spacebar': // older browsers might return spacebar instead of a space character
case KeyboardShortcuts.VIDEO_PLAYER.PLAY:
// Toggle Play/Pause
event.preventDefault()
video_.paused ? video_.play() : video_.pause()
break
case 'J':
case 'j':
case KeyboardShortcuts.VIDEO_PLAYER.LARGE_REWIND:
// Rewind by 2x the time-skip interval (in seconds)
event.preventDefault()
seekBySeconds(-defaultSkipInterval.value * video_.playbackRate * 2)
break
case 'L':
case 'l':
case KeyboardShortcuts.VIDEO_PLAYER.LARGE_FAST_FORWARD:
// Fast-Forward by 2x the time-skip interval (in seconds)
event.preventDefault()
seekBySeconds(defaultSkipInterval.value * video_.playbackRate * 2)
break
case 'O':
case 'o':
case KeyboardShortcuts.VIDEO_PLAYER.DECREASE_VIDEO_SPEED:
// Decrease playback rate by user configured interval
event.preventDefault()
changePlayBackRate(-videoPlaybackRateInterval.value)
break
case 'P':
case 'p':
case KeyboardShortcuts.VIDEO_PLAYER.INCREASE_VIDEO_SPEED:
// Increase playback rate by user configured interval
event.preventDefault()
changePlayBackRate(videoPlaybackRateInterval.value)
break
case 'F':
case 'f':
case KeyboardShortcuts.VIDEO_PLAYER.FULLSCREEN:
// Toggle full screen
event.preventDefault()
ui.getControls().toggleFullScreen()
break
case 'M':
case 'm':
case KeyboardShortcuts.VIDEO_PLAYER.MUTE:
// Toggle mute only if metakey is not pressed
if (!event.metaKey) {
event.preventDefault()
video_.muted = !video_.muted
}
break
case 'C':
case 'c':
case KeyboardShortcuts.VIDEO_PLAYER.CAPTIONS:
// Toggle caption/subtitles
if (player.getTextTracks().length > 0) {
event.preventDefault()
Expand All @@ -2036,17 +2063,17 @@ export default defineComponent({
player.setTextTrackVisibility(!currentlyVisible)
}
break
case 'ArrowUp':
case KeyboardShortcuts.VIDEO_PLAYER.VOLUME_UP:
// Increase volume
event.preventDefault()
changeVolume(0.05)
break
case 'ArrowDown':
case KeyboardShortcuts.VIDEO_PLAYER.VOLUME_DOWN:
// Decrease Volume
event.preventDefault()
changeVolume(-0.05)
break
case 'ArrowLeft':
case KeyboardShortcuts.VIDEO_PLAYER.SMALL_REWIND:
event.preventDefault()
if (canChapterJump(event, 'previous')) {
// Jump to the previous chapter
Expand All @@ -2056,7 +2083,7 @@ export default defineComponent({
seekBySeconds(-defaultSkipInterval.value * video_.playbackRate)
}
break
case 'ArrowRight':
case KeyboardShortcuts.VIDEO_PLAYER.SMALL_FAST_FORWARD:
event.preventDefault()
if (canChapterJump(event, 'next')) {
// Jump to the next chapter
Expand All @@ -2066,8 +2093,7 @@ export default defineComponent({
seekBySeconds(defaultSkipInterval.value * video_.playbackRate)
}
break
case 'I':
case 'i':
case KeyboardShortcuts.VIDEO_PLAYER.PICTURE_IN_PICTURE:
// Toggle picture in picture
if (props.format !== 'audio') {
const controls = ui.getControls()
Expand Down Expand Up @@ -2100,26 +2126,25 @@ export default defineComponent({
}
break
}
case ',':
case KeyboardShortcuts.VIDEO_PLAYER.LAST_FRAME:
event.preventDefault()
// Return to previous frame
frameByFrame(-1)
break
case '.':
case KeyboardShortcuts.VIDEO_PLAYER.NEXT_FRAME:
event.preventDefault()
// Advance to next frame
frameByFrame(1)
break
case 'D':
case 'd':
case KeyboardShortcuts.VIDEO_PLAYER.STATS:
// Toggle stats display
event.preventDefault()

events.dispatchEvent(new CustomEvent('setStatsVisibility', {
detail: !showStats.value
}))
break
case 'Escape':
case 'escape':
// Exit full window
if (fullWindowEnabled.value) {
event.preventDefault()
Expand All @@ -2129,16 +2154,14 @@ export default defineComponent({
}))
}
break
case 'S':
case 's':
case KeyboardShortcuts.VIDEO_PLAYER.FULLWINDOW:
// Toggle full window mode
event.preventDefault()
events.dispatchEvent(new CustomEvent('setFullWindow', {
detail: !fullWindowEnabled.value
}))
break
case 'T':
case 't':
case KeyboardShortcuts.VIDEO_PLAYER.THEATRE_MODE:
// Toggle theatre mode
if (props.theatrePossible) {
event.preventDefault()
Expand All @@ -2148,8 +2171,7 @@ export default defineComponent({
}))
}
break
case 'U':
case 'u':
case KeyboardShortcuts.VIDEO_PLAYER.TAKE_SCREENSHOT:
if (process.env.IS_ELECTRON && enableScreenshot.value && props.format !== 'audio') {
event.preventDefault()
// Take screenshot
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import shaka from 'shaka-player'

import i18n from '../../../i18n/index'
import { KeyboardShortcuts } from '../../../../constants'
import { addKeyboardShortcutToActionTitle } from '../../../helpers/utils'

export class FullWindowButton extends shaka.ui.Element {
/**
Expand Down Expand Up @@ -69,10 +71,16 @@ export class FullWindowButton extends shaka.ui.Element {
updateLocalisedStrings_() {
this.nameSpan_.textContent = i18n.t('Video.Player.Full Window')


this.icon_.textContent = this.fullWindowEnabled_ ? 'close_fullscreen' : 'open_in_full'

this.currentState_.textContent = this.localization.resolve(this.fullWindowEnabled_ ? 'ON' : 'OFF')

this.button_.ariaLabel = this.fullWindowEnabled_ ? i18n.t('Video.Player.Exit Full Window') : i18n.t('Video.Player.Full Window')
const baseAriaLabel = this.fullWindowEnabled_ ? i18n.t('Video.Player.Exit Full Window') : i18n.t('Video.Player.Full Window')

this.button_.ariaLabel = addKeyboardShortcutToActionTitle(
baseAriaLabel,
KeyboardShortcuts.VIDEO_PLAYER.FULLWINDOW
)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import shaka from 'shaka-player'

import i18n from '../../../i18n/index'
import { KeyboardShortcuts } from '../../../../constants'
import { addKeyboardShortcutToActionTitle } from '../../../helpers/utils'

export class ScreenshotButton extends shaka.ui.Element {
/**
Expand Down Expand Up @@ -49,8 +51,11 @@ export class ScreenshotButton extends shaka.ui.Element {

/** @private */
updateLocalisedStrings_() {
this.nameSpan_.textContent = i18n.t('Video.Player.Take Screenshot')

this.button_.ariaLabel = i18n.t('Video.Player.Take Screenshot')
const label = addKeyboardShortcutToActionTitle(
i18n.t('Video.Player.Take Screenshot'),
KeyboardShortcuts.VIDEO_PLAYER.TAKE_SCREENSHOT
)
this.nameSpan_.textContent = label
this.button_.ariaLabel = label
}
}
Loading