mirror of
https://github.com/NoCrypt/migu.git
synced 2026-04-05 00:59:49 +00:00
fix: dpad navigation improvements
This commit is contained in:
parent
3dcf5abb9b
commit
67407a5693
8 changed files with 86 additions and 16 deletions
|
|
@ -128,7 +128,8 @@ img[src=''], img[src=' '] {
|
|||
|
||||
*:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: var(--dm-button-primary-box-shadow-focus) !important;
|
||||
border-radius: 5px;
|
||||
box-shadow: inset 0 0 0 1.5px #eee !important;
|
||||
}
|
||||
|
||||
.modal:focus-visible {
|
||||
|
|
|
|||
|
|
@ -730,5 +730,3 @@ class AnilistClient {
|
|||
}
|
||||
|
||||
export const anilistClient = new AnilistClient()
|
||||
|
||||
globalThis.alc = anilistClient
|
||||
|
|
|
|||
|
|
@ -14,6 +14,13 @@ document.addEventListener('pointerup', () => {
|
|||
})
|
||||
})
|
||||
|
||||
/** @typedef {{element: Element, x: number, y: number, inViewport: boolean}} ElementPosition */
|
||||
|
||||
/**
|
||||
* Adds click event listener to the specified node.
|
||||
* @param {HTMLElement} node - The node to attach the click event listener to.
|
||||
* @param {Function} [cb=noop] - The callback function to be executed on click.
|
||||
*/
|
||||
export function click (node, cb = noop) {
|
||||
node.tabIndex = 0
|
||||
node.role = 'button'
|
||||
|
|
@ -38,6 +45,11 @@ export function click (node, cb = noop) {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: this needs to be re-written.... again... it should detect pointer type and have separate functionality for mouse and touch and none for dpad
|
||||
/**
|
||||
* Adds hover and click event listeners to the specified node.
|
||||
* @param {HTMLElement} node - The node to attach the event listeners to.
|
||||
*/
|
||||
export function hoverClick (node, [cb = noop, hoverUpdate = noop]) {
|
||||
let pointerType = 'mouse'
|
||||
node.tabIndex = 0
|
||||
|
|
@ -93,18 +105,30 @@ const Directions = { up: 1, right: 2, down: 3, left: 4 }
|
|||
const InverseDirections = { up: 'down', down: 'up', left: 'right', right: 'left' }
|
||||
const DirectionKeyMap = { ArrowDown: 'down', ArrowUp: 'up', ArrowLeft: 'left', ArrowRight: 'right' }
|
||||
|
||||
/**
|
||||
* Calculates the direction between two points.
|
||||
* @param {Object} anchor - The anchor point.
|
||||
* @param {Object} relative - The relative point.
|
||||
* @returns {number} - The direction between the two points.
|
||||
*/
|
||||
function getDirection (anchor, relative) {
|
||||
return Math.round((Math.atan2(relative.y - anchor.y, relative.x - anchor.x) * 180 / Math.PI + 180) / 90)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the distance between two points.
|
||||
* @param {Object} anchor - The anchor point.
|
||||
* @param {Object} relative - The relative point.
|
||||
* @returns {number} - The distance between the two points.
|
||||
*/
|
||||
function getDistance (anchor, relative) {
|
||||
return Math.hypot(relative.x - anchor.x, relative.y - anchor.y)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets keyboard-focusable elements within a specified element
|
||||
* @param {Element} [element=document.body] element
|
||||
* @returns {Element[]}
|
||||
* Gets keyboard-focusable elements within a specified element.
|
||||
* @param {Element} [element=document.body] - The element to search within.
|
||||
* @returns {Element[]} - An array of keyboard-focusable elements.
|
||||
*/
|
||||
function getKeyboardFocusableElements (element = document.body) {
|
||||
return [...element.querySelectorAll('a[href], button:not([disabled]), fieldset:not([disabled]), input:not([disabled]), optgroup:not([disabled]), option:not([disabled]), select:not([disabled]), textarea:not([disabled]), details, [tabindex]:not([tabindex="-1"]), [contenteditable], [controls]')].filter(
|
||||
|
|
@ -113,7 +137,9 @@ function getKeyboardFocusableElements (element = document.body) {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param {Element} element
|
||||
* Gets the position of an element.
|
||||
* @param {Element} element - The element to get the position of.
|
||||
* @returns {ElementPosition} - The position of the element.
|
||||
*/
|
||||
function getElementPosition (element) {
|
||||
const { x, y, width, height, top, left, bottom, right } = element.getBoundingClientRect()
|
||||
|
|
@ -121,6 +147,10 @@ function getElementPosition (element) {
|
|||
return { element, x: x + width * 0.5, y: y + height * 0.5, inViewport }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the positions of all focusable elements.
|
||||
* @returns {ElementPosition[]} - An array of element positions.
|
||||
*/
|
||||
function getFocusableElementPositions () {
|
||||
const elements = []
|
||||
for (const element of getKeyboardFocusableElements(document.querySelector('.modal.show') ?? document.body)) {
|
||||
|
|
@ -130,6 +160,11 @@ function getFocusableElementPositions () {
|
|||
return elements
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an element is within the viewport.
|
||||
* @param {Object} rect - The coordinates of the element.
|
||||
* @returns {boolean} - True if the element is within the viewport, false otherwise.
|
||||
*/
|
||||
function isInViewport ({ top, left, bottom, right }) {
|
||||
return top >= 0 && left >= 0 && bottom <= window.innerHeight && right <= window.innerWidth
|
||||
}
|
||||
|
|
@ -141,6 +176,12 @@ function isInViewport ({ top, left, bottom, right }) {
|
|||
// return false
|
||||
// }
|
||||
|
||||
/**
|
||||
* @param {ElementPosition[]} keyboardFocusable
|
||||
* @param {ElementPosition} currentElement
|
||||
* @param {string} direction
|
||||
* @returns {ElementPosition[]}
|
||||
*/
|
||||
function getElementsInDesiredDirection (keyboardFocusable, currentElement, direction) {
|
||||
// first try finding visible elements in desired direction
|
||||
return keyboardFocusable.filter(position => {
|
||||
|
|
@ -154,6 +195,10 @@ function getElementsInDesiredDirection (keyboardFocusable, currentElement, direc
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates using D-pad keys.
|
||||
* @param {string} [direction='up'] - The direction to navigate.
|
||||
*/
|
||||
function navigateDPad (direction = 'up') {
|
||||
const keyboardFocusable = getFocusableElementPositions()
|
||||
const currentElement = !document.activeElement || document.activeElement === document.body ? keyboardFocusable[0] : getElementPosition(document.activeElement)
|
||||
|
|
@ -168,8 +213,15 @@ function navigateDPad (direction = 'up') {
|
|||
return reducer
|
||||
}, { distance: Infinity, element: null })
|
||||
|
||||
closestElement.element.focus()
|
||||
closestElement.element.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'smooth' })
|
||||
/** @type {{element: HTMLElement}} */
|
||||
const { element } = closestElement
|
||||
|
||||
const isInput = element.matches('input[type=text], input[type=url], input[type=number], textarea')
|
||||
// make readonly
|
||||
if (isInput) element.readOnly = true
|
||||
element.focus()
|
||||
if (isInput) setTimeout(() => { element.readOnly = false })
|
||||
element.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'smooth' })
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -144,9 +144,9 @@ export const defaults = {
|
|||
doHURL: 'https://cloudflare-dns.com/dns-query',
|
||||
disableSubtitleBlur: SUPPORTS.isAndroid,
|
||||
showDetailsInRPC: true,
|
||||
smoothScroll: true,
|
||||
smoothScroll: !SUPPORTS.isAndroid,
|
||||
cards: 'small',
|
||||
expandingSidebar: true,
|
||||
expandingSidebar: !SUPPORTS.isAndroid,
|
||||
torrentPathNew: undefined,
|
||||
font: undefined,
|
||||
angle: 'default',
|
||||
|
|
|
|||
|
|
@ -985,6 +985,25 @@
|
|||
break
|
||||
}
|
||||
}
|
||||
|
||||
function handleSeekbarKey (e) {
|
||||
if (e.key === 'ArrowLeft') {
|
||||
e.stopPropagation()
|
||||
e.stopImmediatePropagation()
|
||||
e.preventDefault()
|
||||
rewind()
|
||||
} else if (e.key === 'ArrowRight') {
|
||||
e.stopPropagation()
|
||||
e.stopImmediatePropagation()
|
||||
e.preventDefault()
|
||||
forward()
|
||||
} else if (e.key === 'ArrowDown') {
|
||||
e.stopPropagation()
|
||||
e.stopImmediatePropagation()
|
||||
e.preventDefault()
|
||||
document.querySelector('div[data-name=\'toggleFullscreen\']')?.focus()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- <svelte:window bind:innerWidth bind:innerHeight /> -->
|
||||
|
|
@ -1101,7 +1120,7 @@
|
|||
{/if}
|
||||
</div>
|
||||
<div class='bottom d-flex z-40 flex-column px-20'>
|
||||
<div class='w-full d-flex align-items-center h-20 mb-5 seekbar' tabindex='0' role='button'>
|
||||
<div class='w-full d-flex align-items-center h-20 mb-5 seekbar' tabindex='0' role='button' on:keydown={handleSeekbarKey}>
|
||||
<Seekbar
|
||||
accentColor='var(--accent-color)'
|
||||
class='font-size-20'
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@
|
|||
{:then changelog}
|
||||
{#each changelog as { version, date, body }}
|
||||
<hr class='my-20' />
|
||||
<div class='row py-20 px-20 px-sm-0 position-relative'>
|
||||
<div class='row py-20 px-20 px-sm-0 position-relative' tabindex='0' role='button'>
|
||||
<div class='col-sm-3 order-last order-sm-first text-white'>
|
||||
<div class='position-sticky top-0 pt-20'>
|
||||
{new Date(date).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}
|
||||
|
|
|
|||
|
|
@ -54,8 +54,8 @@
|
|||
{@const completed = !watched && userProgress >= episode}
|
||||
{@const target = userProgress + 1 === episode}
|
||||
{@const progress = !watched && ($animeProgress?.[episode] ?? 0)}
|
||||
<div class='w-full my-20 content-visibility-auto scale' class:opacity-half={completed} class:px-20={!target} class:h-150={image || summary} use:click={() => play(episode)}>
|
||||
<div class='rounded w-full h-full overflow-hidden d-flex flex-xsm-column flex-row pointer' class:border={target} class:bg-black={completed} class:bg-dark={!completed}>
|
||||
<div class='w-full my-20 content-visibility-auto scale' class:opacity-half={completed} class:px-20={!target} class:h-150={image || summary}>
|
||||
<div class='rounded w-full h-full overflow-hidden d-flex flex-xsm-column flex-row pointer' class:border={target} class:bg-black={completed} class:bg-dark={!completed} use:click={() => play(episode)}>
|
||||
{#if image}
|
||||
<div class='h-full'>
|
||||
<img alt='thumbnail' src={image} class='img-cover h-full' />
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "Miru",
|
||||
"version": "5.1.6",
|
||||
"version": "5.1.7",
|
||||
"private": true,
|
||||
"author": "ThaUnknown_ <ThaUnknown@users.noreply.github.com>",
|
||||
"description": "Stream anime torrents, real-time with no waiting for downloads.",
|
||||
|
|
|
|||
Loading…
Reference in a new issue