Skip to content

Commit

Permalink
Keyboard shortcut modal (FreeTubeApp#6306)
Browse files Browse the repository at this point in the history
* Add keyboard shortcut modal

* Temp

* Add keyboard shortcut modal shortcut entry, shift & enter labels + Mac icons, capitalize non-special shortcut letters

* Use usei18n instead of i18n.t

* Improve keyboard shortcut button formatting

* Update prompt logic to focus ft-icon-buttons as well

* Improve mobile styling

* Adjust v-for key to reflect new template structure

* Apply suggestions from code review

Co-authored-by: absidue <[email protected]>

* Implement review suggestions

Change shortcut to be more accurate, remove duplicate action for closing current FreeTube window, update label for force reload

* Change shortcut to YouTube's SHIFT+?

* Use keyboard icon for button, and check for usingElectron in showing it

* Change ctrl+R shortcuts to use new labelling

* Exit FS and/or FW when keyboard shortcut modal is opened

* Add code comment clarifying where parallel change to shortcut documentation should be

* Implement changes from review

---------

Co-authored-by: absidue <[email protected]>
  • Loading branch information
kommunarr and absidue authored Dec 27, 2024
1 parent 6d4c2c5 commit 6693654
Show file tree
Hide file tree
Showing 14 changed files with 446 additions and 12 deletions.
29 changes: 24 additions & 5 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,19 +116,37 @@ const SyncEvents = {
},
}

// note: the multi-key shortcut values are currently just for display use in action titles
/*
DEV NOTE: Duplicate any and all changes made here to our [official documentation site here](https://github.com/FreeTubeApp/FreeTube-Docs/blob/master/usage/keyboard-shortcuts.md)
to have them reflect on the [keyboard shortcut reference webpage](https://docs.freetubeapp.io/usage/keyboard-shortcuts).
Please also update the [keyboard shortcut modal](src/renderer/components/FtKeyboardShortcutPrompt/FtKeyboardShortcutPrompt.vue)
*/
const KeyboardShortcuts = {
APP: {
GENERAL: {
SHOW_SHORTCUTS: 'shift+?',
HISTORY_BACKWARD: 'alt+arrowleft',
HISTORY_FORWARD: 'alt+arrowright',
NEW_WINDOW: 'ctrl+N',
FULLSCREEN: 'f11',
NAVIGATE_TO_SETTINGS: 'ctrl+,',
NAVIGATE_TO_HISTORY: 'ctrl+H',
NAVIGATE_TO_HISTORY_MAC: 'cmd+Y',
NEW_WINDOW: 'ctrl+N',
MINIMIZE_WINDOW: 'ctrl+M',
CLOSE_WINDOW: 'ctrl+W',
RESTART_WINDOW: 'ctrl+R',
FORCE_RESTART_WINDOW: 'ctrl+shift+R',
TOGGLE_DEVTOOLS: 'ctrl+shift+I',
FOCUS_SEARCH: 'alt+D',
SEARCH_IN_NEW_WINDOW: 'shift+enter',
RESET_ZOOM: 'ctrl+0',
ZOOM_IN: 'ctrl+plus',
ZOOM_OUT: 'ctrl+-'

},
SITUATIONAL: {
REFRESH: 'r'
REFRESH: 'r',
FOCUS_SECONDARY_SEARCH: 'ctrl+F'
},
},
VIDEO_PLAYER: {
Expand All @@ -152,10 +170,11 @@ const KeyboardShortcuts = {
SMALL_FAST_FORWARD: 'arrowright',
DECREASE_VIDEO_SPEED: 'o',
INCREASE_VIDEO_SPEED: 'p',
LAST_FRAME: ',',
NEXT_FRAME: '.',
SKIP_N_TENTHS: '0..9',
LAST_CHAPTER: 'ctrl+arrowleft',
NEXT_CHAPTER: 'ctrl+arrowright',
LAST_FRAME: ',',
NEXT_FRAME: '.',
}
},
}
Expand Down
13 changes: 12 additions & 1 deletion src/renderer/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import FtToast from './components/ft-toast/ft-toast.vue'
import FtProgressBar from './components/FtProgressBar/FtProgressBar.vue'
import FtPlaylistAddVideoPrompt from './components/ft-playlist-add-video-prompt/ft-playlist-add-video-prompt.vue'
import FtCreatePlaylistPrompt from './components/ft-create-playlist-prompt/ft-create-playlist-prompt.vue'
import FtKeyboardShortcutPrompt from './components/FtKeyboardShortcutPrompt/FtKeyboardShortcutPrompt.vue'
import FtSearchFilters from './components/FtSearchFilters/FtSearchFilters.vue'
import { marked } from 'marked'
import { IpcChannels } from '../constants'
Expand All @@ -32,7 +33,8 @@ export default defineComponent({
FtProgressBar,
FtPlaylistAddVideoPrompt,
FtCreatePlaylistPrompt,
FtSearchFilters
FtSearchFilters,
FtKeyboardShortcutPrompt,
},
data: function () {
return {
Expand Down Expand Up @@ -71,6 +73,9 @@ export default defineComponent({
checkForBlogPosts: function () {
return this.$store.getters.getCheckForBlogPosts
},
isKeyboardShortcutPromptShown: function () {
return this.$store.getters.getIsKeyboardShortcutPromptShown
},
showAddToPlaylistPrompt: function () {
return this.$store.getters.getShowAddToPlaylistPrompt
},
Expand Down Expand Up @@ -347,6 +352,10 @@ export default defineComponent({
},

handleKeyboardShortcuts: function (event) {
if (event.shiftKey && event.key === '?') {
this.$store.commit('setIsKeyboardShortcutPromptShown', !this.isKeyboardShortcutPromptShown)
}

if (event.altKey) {
switch (event.key) {
case 'D':
Expand Down Expand Up @@ -576,6 +585,8 @@ export default defineComponent({
'fetchInvidiousInstancesFromFile',
'setRandomCurrentInvidiousInstance',
'setupListenersToSyncWindows',
'hideKeyboardShortcutPrompt',
'showKeyboardShortcutPrompt',
'updateBaseTheme',
'updateMainColor',
'updateSecColor',
Expand Down
3 changes: 3 additions & 0 deletions src/renderer/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@
<ft-search-filters
v-if="showSearchFilters"
/>
<ft-keyboard-shortcut-prompt
v-if="isKeyboardShortcutPromptShown"
/>
<ft-playlist-add-video-prompt
v-if="showAddToPlaylistPrompt"
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@


.keyboardShortcutPrompt {
max-inline-size: 80%;
}

.titleAndCloseButton {
display: flex;
justify-content: space-between;
margin-block: 20px;
margin-inline-start: 10px;
}

h2 {
font-size: 25px;
}

h3 {
font-size: 20px;
}

h2,
h3 {
margin-block: 0.25em;
inline-size: fit-content;
font-weight: bold;
}

.center {
text-align: center;
}

.primarySection {
inline-size: 100%;
}

.primarySections,
.primarySection {
display: flex;
flex-flow: row wrap;
gap: 30px;
justify-content: space-evenly;
}

.secondarySection {
flex: 0 0 500px;
}

.labelsAndShortcuts {
display: table;
table-layout: auto;
border-collapse: collapse;
}

.labelAndShortcut {
display: table-row;
font-size: 16px;
}

.labelAndShortcut + .labelAndShortcut {
border-block-start: 1px solid var(--primary-shadow-color);
}

.label,
.shortcut {
padding-block: 0.25em;
}

.label {
display: table-cell;
padding-inline-end: 2em;
inline-size: 300px;
min-inline-size: 300px;
}

.shortcut {
font-family: monospace;
display: table-cell;
vertical-align: middle;
text-transform: capitalize;
}

:deep(.promptCard) {
overflow-x: hidden;
}

@media only screen and (width <= 600px) {
.label {
min-inline-size: 200px;
inline-size: 200px;
}

.secondarySection {
flex: 0;
}

.primarySection {
justify-content: flex-start;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
<template>
<FtPrompt
:label="$t('KeyboardShortcutPrompt.Keyboard Shortcuts')"
@click="hideKeyboardShortcutPrompt"
>
<div class="titleAndCloseButton">
<h2>
{{ $t('KeyboardShortcutPrompt.Keyboard Shortcuts') }}
</h2>
<FtIconButton
:title="$t('Close')"
:icon="['fas', 'xmark']"
theme="destructive"
@click="hideKeyboardShortcutPrompt"
/>
</div>
<div
v-if="primarySections"
class="primarySections"
>
<div
v-for="(primarySection, index) of primarySections"
:key="index"
class="primarySection"
>
<div
v-for="secondarySection in primarySection.secondarySections"
:key="secondarySection.title"
class="secondarySection"
>
<h3 class="center">
{{ secondarySection.title }}
</h3>
<div class="labelsAndShortcuts">
<div
v-for="[label, shortcut] in secondarySection.shortcutDictionary"
:key="label"
class="labelAndShortcut"
>
<p
class="label"
>
{{ label }}
</p>
<p class="shortcut">
{{ shortcut }}
</p>
</div>
</div>
</div>
</div>
</div>
</FtPrompt>
</template>

<script setup>
import { computed } from 'vue'
import { KeyboardShortcuts } from '../../../constants'
import { getLocalizedShortcut } from '../../helpers/utils'
import FtPrompt from '../ft-prompt/ft-prompt.vue'
import store from '../../store/index'
import { useI18n } from '../../composables/use-i18n-polyfill'
import FtIconButton from '../ft-icon-button/ft-icon-button.vue'
const { t } = useI18n()
const generalPlayerShortcuts = computed(() =>
getLocalizedShortcutNamesAndValues(KeyboardShortcuts.VIDEO_PLAYER.GENERAL)
)
const playbackPlayerShortcuts = computed(() =>
getLocalizedShortcutNamesAndValues(KeyboardShortcuts.VIDEO_PLAYER.PLAYBACK)
)
const generalAppShortcuts = computed(() =>
getLocalizedShortcutNamesAndValues(KeyboardShortcuts.APP.GENERAL)
)
const situationalAppShortcuts = computed(() =>
getLocalizedShortcutNamesAndValues(KeyboardShortcuts.APP.SITUATIONAL)
)
const primarySections = computed(() => [
{
secondarySections: [
{
title: t('KeyboardShortcutPrompt.Sections.Video.Playback'),
shortcutDictionary: playbackPlayerShortcuts.value
},
{
title: t('KeyboardShortcutPrompt.Sections.Video.General'),
shortcutDictionary: generalPlayerShortcuts.value
},
]
},
{
secondarySections: [
{
title: t('KeyboardShortcutPrompt.Sections.App.General'),
shortcutDictionary: generalAppShortcuts.value
},
{
title: t('KeyboardShortcutPrompt.Sections.App.Situational'),
shortcutDictionary: situationalAppShortcuts.value
}
]
}
])
const isMac = process.platform === 'darwin'
const localizedShortcutNameDictionary = computed(() => {
return new Map([
['SHOW_SHORTCUTS', t('KeyboardShortcutPrompt.Show Keyboard Shortcuts')],
['HISTORY_BACKWARD', t('KeyboardShortcutPrompt.History Backward')],
['HISTORY_FORWARD', t('KeyboardShortcutPrompt.History Forward')],
['FULLSCREEN', t('KeyboardShortcutPrompt.Fullscreen')],
['NAVIGATE_TO_SETTINGS', t('KeyboardShortcutPrompt.Navigate to Settings')],
(
isMac
? ['NAVIGATE_TO_HISTORY_MAC', t('KeyboardShortcutPrompt.Navigate to History')]
: ['NAVIGATE_TO_HISTORY', t('KeyboardShortcutPrompt.Navigate to History')]
),
['NEW_WINDOW', t('KeyboardShortcutPrompt.New Window')],
['MINIMIZE_WINDOW', t('KeyboardShortcutPrompt.Minimize Window')],
['CLOSE_WINDOW', t('KeyboardShortcutPrompt.Close Window')],
['RESTART_WINDOW', t('KeyboardShortcutPrompt.Restart Window')],
['FORCE_RESTART_WINDOW', t('KeyboardShortcutPrompt.Force Restart Window')],
['TOGGLE_DEVTOOLS', t('KeyboardShortcutPrompt.Toggle Developer Tools')],
['RESET_ZOOM', t('KeyboardShortcutPrompt.Reset Zoom')],
['ZOOM_IN', t('KeyboardShortcutPrompt.Zoom In')],
['ZOOM_OUT', t('KeyboardShortcutPrompt.Zoom Out')],
['FOCUS_SEARCH', t('KeyboardShortcutPrompt.Focus Search')],
['SEARCH_IN_NEW_WINDOW', t('KeyboardShortcutPrompt.Search in New Window')],
['REFRESH', t('KeyboardShortcutPrompt.Refresh')],
['FOCUS_SECONDARY_SEARCH', t('KeyboardShortcutPrompt.Focus Secondary Search')],
['CAPTIONS', t('KeyboardShortcutPrompt.Captions')],
['THEATRE_MODE', t('KeyboardShortcutPrompt.Theatre Mode')],
['FULLSCREEN', t('KeyboardShortcutPrompt.Fullscreen')],
['FULLWINDOW', t('KeyboardShortcutPrompt.Full Window')],
['PICTURE_IN_PICTURE', t('KeyboardShortcutPrompt.Picture in Picture')],
['MUTE', t('KeyboardShortcutPrompt.Mute')],
['VOLUME_UP', t('KeyboardShortcutPrompt.Volume Up')],
['VOLUME_DOWN', t('KeyboardShortcutPrompt.Volume Down')],
['TAKE_SCREENSHOT', t('KeyboardShortcutPrompt.Take Screenshot')],
['STATS', t('KeyboardShortcutPrompt.Stats')],
['PLAY', t('KeyboardShortcutPrompt.Play')],
['LARGE_REWIND', t('KeyboardShortcutPrompt.Large Rewind')],
['LARGE_FAST_FORWARD', t('KeyboardShortcutPrompt.Large Fast Forward')],
['SMALL_REWIND', t('KeyboardShortcutPrompt.Small Rewind')],
['SMALL_FAST_FORWARD', t('KeyboardShortcutPrompt.Small Fast Forward')],
['DECREASE_VIDEO_SPEED', t('KeyboardShortcutPrompt.Decrease Video Speed')],
['INCREASE_VIDEO_SPEED', t('KeyboardShortcutPrompt.Increase Video Speed')],
['SKIP_N_TENTHS', t('KeyboardShortcutPrompt.Skip by Tenths')],
['LAST_CHAPTER', t('KeyboardShortcutPrompt.Last Chapter')],
['NEXT_CHAPTER', t('KeyboardShortcutPrompt.Next Chapter')],
['LAST_FRAME', t('KeyboardShortcutPrompt.Last Frame')],
['NEXT_FRAME', t('KeyboardShortcutPrompt.Next Frame')],
])
})
function hideKeyboardShortcutPrompt() {
store.dispatch('hideKeyboardShortcutPrompt')
}
function getLocalizedShortcutNamesAndValues(dictionary) {
const localizedDictionary = localizedShortcutNameDictionary.value
return Object.entries(dictionary)
.filter(([key]) =>
localizedDictionary.has(key)
)
.map(([shortcutNameKey, shortcut]) => {
const localizedShortcutName = localizedDictionary.get(shortcutNameKey)
const localizedShortcut = getLocalizedShortcut(shortcut)
return [localizedShortcutName, localizedShortcut]
})
}
</script>

<style scoped src="./FtKeyboardShortcutPrompt.css" />
Loading

0 comments on commit 6693654

Please sign in to comment.