Skip to content

Commit

Permalink
feat (spatial navigation): This allows for navigation using the arrow…
Browse files Browse the repository at this point in the history
… keys on keyboard and remote control. Also add support for play/pause button on webos
  • Loading branch information
gibahjoe committed Feb 18, 2023
1 parent bddb6de commit b8c3921
Show file tree
Hide file tree
Showing 11 changed files with 105 additions and 10 deletions.
14 changes: 14 additions & 0 deletions frontend/assets/global.scss
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,17 @@ body {
.overflow-hidden {
overflow: hidden;
}

.card-margin:focus-within,
.card-margin:focus {
box-shadow: 0 0 0 5px var(--v-primary-base);
border-radius: 5px;
outline: none;
background-color: var(--v-primary-base);
}
button.primary:focus,
button.primary:focus-visible {
box-shadow: 0 0 0 5px white !important;
outline: none !important;
}

4 changes: 3 additions & 1 deletion frontend/components/Buttons/PlayButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<div class="d-inline-flex">
<v-btn
v-if="canPlay(item) && (fab || iconOnly)"
v-focus
:fab="fab"
:text="iconOnly"
:color="iconOnly ? null : 'primary'"
Expand All @@ -14,6 +15,7 @@
</v-btn>
<v-btn
v-else-if="!fab"
v-focus
:disabled="disabled || !canPlay(item)"
:loading="loading"
class="mr-2"
Expand All @@ -39,7 +41,7 @@ import { BaseItemDto } from '@jellyfin/client-axios';
import Vue from 'vue';
import { mapStores } from 'pinia';
import { playbackManagerStore } from '~/store';
import { canResume, canPlay } from '~/utils/items';
import { canPlay, canResume } from '~/utils/items';
import { ticksToMs } from '~/utils/time';
export default Vue.extend({
Expand Down
19 changes: 16 additions & 3 deletions frontend/components/Item/Card/Card.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<template>
<div :class="{ 'card-margin': margin }">
<div
v-focus="link"
:class="{ 'card-margin': margin }"
@keyup.enter="cardClicked()"
>
<component
:is="link ? 'nuxt-link' : 'div'"
:to="link ? getItemDetailsLink(item) : null"
Expand Down Expand Up @@ -87,10 +91,10 @@ import Vue from 'vue';
import { mapStores } from 'pinia';
import { BaseItemDto, ImageType } from '@jellyfin/client-axios';
import {
canPlay,
CardShapes,
getShapeFromItemType,
getItemDetailsLink,
canPlay
getShapeFromItemType
} from '~/utils/items';
import { taskManagerStore } from '~/store';
Expand Down Expand Up @@ -250,6 +254,15 @@ export default Vue.extend({
isFinePointer(): boolean {
return window.matchMedia('(pointer:fine)').matches;
},
cardClicked() {
if (this.link) {
const itemLink = getItemDetailsLink(this.item);
if (itemLink) {
this.$router.push(itemLink);
}
}
},
getItemDetailsLink,
canPlay
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/components/Item/SeasonTabs.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div>
<v-tabs v-model="currentTab" class="mb-3" background-color="transparent">
<v-tab v-for="season in seasons" :key="season.Id">
<v-tab v-for="season in seasons" :key="season.Id" v-focus>
{{ season.Name }}
</v-tab>
</v-tabs>
Expand Down
4 changes: 3 additions & 1 deletion frontend/components/Layout/Navigation/NavigationDrawer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<v-list-item
v-for="item in items"
:key="item.Id"
v-focus
:to="item.to"
router
exact
Expand All @@ -31,6 +32,7 @@
<v-list-item
v-for="library in userViews.getNavigationDrawerItems"
:key="library.Id"
v-focus
:to="library.to"
router
exact
Expand All @@ -52,7 +54,7 @@
<script lang="ts">
import Vue from 'vue';
import { mapStores } from 'pinia';
import { userViewsStore, pageStore } from '~/store';
import { pageStore, userViewsStore } from '~/store';
interface LayoutButton {
icon: string;
Expand Down
14 changes: 13 additions & 1 deletion frontend/components/Players/PlayerManager.vue
Original file line number Diff line number Diff line change
Expand Up @@ -373,14 +373,18 @@ export default Vue.extend({
let spaceEnabled = false;
// Adds support for remote
// Some keys are 'Unidentified' on LGtv.
const key = e.key === 'Unidentified' ? `${e.keyCode}` : e.key;
if (e.key === 'Spacebar' || e.key === ' ') {
spaceEnabled =
focusEl?.classList.contains('v-dialog__content') ||
focusEl?.classList.contains('hide-pointer') ||
focusEl?.className === '';
}
switch (e.key) {
switch (key) {
case 'Spacebar':
case ' ':
if (spaceEnabled) {
Expand All @@ -389,14 +393,22 @@ export default Vue.extend({
break;
case 'k':
case 'Enter':
case '415':
case '19':
case '13':
this.playbackManager.playPause();
break;
case 'ArrowRight':
case 'l':
case '417':
case '39':
this.playbackManager.skipForward();
break;
case 'ArrowLeft':
case 'j':
case '412':
case '37':
this.playbackManager.skipBackward();
break;
case 'f':
Expand Down
2 changes: 1 addition & 1 deletion frontend/layouts/default.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<v-app>
<v-app v-focus-section:app>
<backdrop />
<navigation-drawer />
<app-bar />
Expand Down
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"uuid": "8.3.2",
"vee-validate": "3.4.14",
"vue-awesome-swiper": "4.1.1",
"vue-js-spatial-navigation": "2.0.14",
"vue-virtual-scroller": "1.0.10",
"vuedraggable": "2.24.3"
},
Expand Down
4 changes: 3 additions & 1 deletion frontend/plugins/nuxt/browserDetectionPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ export class BrowserDetector {
isChrome(): boolean {
// The Edge user agent will also contain the "Chrome" keyword, so we need
// to make sure this is not Edge. Same happens for webos.
return this.userAgentContains('Chrome') && !this.isEdge() && !this.isWebOS();
return (
this.userAgentContains('Chrome') && !this.isEdge() && !this.isWebOS()
);
}

/**
Expand Down
28 changes: 27 additions & 1 deletion frontend/plugins/vue/components.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Vue from 'vue';
import { ValidationProvider, ValidationObserver } from 'vee-validate';
import { ValidationObserver, ValidationProvider } from 'vee-validate';
import draggable from 'vuedraggable';
// @ts-expect-error - target typing doesn't exist as we declared it in params.
import { DynamicScroller, DynamicScrollerItem } from 'vue-virtual-scroller';
Expand All @@ -8,12 +8,38 @@ import VueAwesomeSwiper from 'vue-awesome-swiper';
import Swiper from 'swiper';
import 'swiper/css/swiper.css';

// @ts-expect-error - target typing doesn't exist in npm
import vjsn from 'vue-js-spatial-navigation';

declare module 'vue/types/vue' {
interface Vue {
$swiper: Swiper;
}
}

const config = {
// selector: 'a[href]',
straightOnly: false,
straightOverlapThreshold: 0.5,
rememberSource: false,
disabled: false,
defaultElement: '',
enterTo: '',
leaveFor: null,
restrict: 'self-first',
tabIndexIgnoreList:
'a, input, select, textarea, button, iframe, [contentEditable=true]',
navigableFilter: (e: HTMLElement): boolean => {
if (e?.parentElement?.parentElement?.classList?.contains('card-overlay')) {
return false;
}

return true;
},
scrollOptions: { behavior: 'smooth', block: 'center' }
};

Vue.use(vjsn, config);
Vue.use(VueAwesomeSwiper);
Vue.component('ValidationProvider', ValidationProvider);
Vue.component('ValidationObserver', ValidationObserver);
Expand Down
23 changes: 23 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit b8c3921

Please sign in to comment.