Add navigation action system

This commit is contained in:
Aleksi Lassila
2023-12-27 23:09:49 +02:00
parent d255fce52d
commit ac994277a5
2 changed files with 234 additions and 48 deletions

View File

@@ -1,56 +1,58 @@
<script lang="ts">
import Carousel from "./lib/components/Carousel/Carousel.svelte";
import I18n from "./lib/components/Lang/I18n.svelte";
import { _ } from "svelte-i18n";
import CarouselPlaceholderItems from "./lib/components/Carousel/CarouselPlaceholderItems.svelte";
import { Link, navigate, Route, Router } from "svelte-navigator";
import { fade } from "svelte/transition";
import { networks } from "./lib/discover";
import NetworkCard from "./lib/components/NetworkCard.svelte";
import I18n from './lib/components/Lang/I18n.svelte';
import { Link, navigate, Route, Router } from 'svelte-navigator';
import { fade } from 'svelte/transition';
import { handleKeyboardNavigation, navigationContainers } from './lib/actions/focusAction';
function handleKeyDown(event: KeyboardEvent) {
if (event.key === "ArrowRight") {
console.log("right");
navigate("/about");
}
if (event.key === "ArrowLeft") {
console.log("left");
navigate("/");
}
}
let focusableElements: HTMLElement[] = [];
function registerArrayFocus(node: HTMLElement) {
focusableElements.push(node);
console.log('Added node', node);
return {
destroy() {
focusableElements = focusableElements.filter((el) => el !== node);
console.log('Removed node', node);
}
};
}
const navBarContainer = navigationContainers.navBar.getRegisterer();
const homeContainer = navigationContainers.home.getRegisterer();
</script>
<I18n />
<main class="bg-stone-950 text-white">
<Router>
<nav>
<Link to="/">Home</Link>
<Link to="about">About</Link>
</nav>
<main class="bg-stone-950 text-white flex">
<Router>
<nav class="border">
<Link to="/">
<div use:navBarContainer tabindex="0">Home</div>
</Link>
<Link to="about">
<div use:navBarContainer tabindex="0">About</div>
</Link>
</nav>
<Carousel>
<div slot="title" class="text-lg font-semibold text-zinc-300">
{$_("discover.upcomingSeries")}
</div>
<CarouselPlaceholderItems />
</Carousel>
<Carousel>
<div slot="title" class="text-lg font-semibold text-zinc-300">
{$_("discover.TVNetworks")}
</div>
{#each Object.values(networks) as network (network.tmdbNetworkId)}
<NetworkCard {network} />
{/each}
</Carousel>
<Route path="/">
<div transition:fade|global>Home path</div>
</Route>
<Route path="about">
<div transition:fade|global>about path</div>
</Route>
</Router>
<div class="flex-1">
<Route path="/">
<div class="flex flex-row">
<div use:homeContainer tabindex="0" transition:fade|global class="focus:ring">
Home path
</div>
<div use:homeContainer tabindex="0" transition:fade|global class="focus:ring">
Another item
</div>
<div use:homeContainer tabindex="0" transition:fade|global class="focus:ring">
Button perhaps?
</div>
</div>
</Route>
<Route path="about">
<div transition:fade|global>about path</div>
</Route>
</div>
</Router>
</main>
<svelte:window on:keydown={handleKeyDown} />
<svelte:window on:keydown={handleKeyboardNavigation} />

View File

@@ -0,0 +1,184 @@
export class Container {
id: symbol;
name: string;
parent?: Container;
htmlElements: HTMLElement[] = [];
private upNeighbor?: Container;
private downNeighbor?: Container;
private leftNeighbor?: Container;
private rightNeighbor?: Container;
direction: 'horizontal' | 'vertical' = 'horizontal';
focusIndex: number = 0;
static focusedObject: Container;
static objects = new Map<symbol, Container>();
constructor(name: string = '') {
this.id = Symbol();
this.name = name;
Container.objects.set(this.id, this);
if (!Container.focusedObject) {
Container.focusedObject = this;
}
}
getRegisterer() {
return (htmlElement: HTMLElement) => {
this.addHtmlElement(htmlElement);
return {
destroy: () => {
this.removeHtmlElement(htmlElement);
}
};
};
}
giveFocus(direction: 'up' | 'down' | 'left' | 'right') {
const shouldCycle =
this.direction === 'horizontal'
? (direction === 'left' && this.focusIndex !== 0) ||
(direction === 'right' && this.focusIndex !== this.htmlElements.length - 1)
: (direction === 'up' && this.focusIndex !== 0) ||
(direction === 'down' && this.focusIndex !== this.htmlElements.length - 1);
if (shouldCycle) {
console.log('Cycling through', this.htmlElements);
if (direction === 'up') {
this.focusIndex--;
} else if (direction === 'down') {
this.focusIndex++;
} else if (direction === 'left') {
this.focusIndex--;
} else if (direction === 'right') {
this.focusIndex++;
}
this.focusElement();
} else {
console.log("Giving focus to neighbor's", direction, 'neighbor');
const upNeighbor = this.getUpNeighbor();
const downNeighbor = this.getDownNeighbor();
const leftNeighbor = this.getLeftNeighbor();
const rightNeighbor = this.getRightNeighbor();
if (direction === 'up' && upNeighbor) {
if (upNeighbor.direction === 'vertical')
upNeighbor.focusIndex = upNeighbor.htmlElements.length - 1;
Container.focusedObject = upNeighbor;
upNeighbor.focusElement();
} else if (direction === 'down' && downNeighbor) {
if (downNeighbor.direction === 'vertical') downNeighbor.focusIndex = 0;
Container.focusedObject = downNeighbor;
downNeighbor.focusElement();
} else if (direction === 'left' && leftNeighbor) {
if (leftNeighbor.direction === 'horizontal')
leftNeighbor.focusIndex = leftNeighbor.htmlElements.length - 1;
Container.focusedObject = leftNeighbor;
leftNeighbor.focusElement();
} else if (direction === 'right' && rightNeighbor) {
if (rightNeighbor.direction === 'horizontal') rightNeighbor.focusIndex = 0;
Container.focusedObject = rightNeighbor;
rightNeighbor.focusElement();
}
}
}
getUpNeighbor() {
return this.upNeighbor;
}
getDownNeighbor() {
return this.downNeighbor;
}
getLeftNeighbor() {
return this.leftNeighbor;
}
getRightNeighbor() {
return this.rightNeighbor;
}
focusElement() {
this.htmlElements[this.focusIndex].focus();
}
setParent(parent: Container) {
this.parent = parent;
return this;
}
addHtmlElement(htmlElement: HTMLElement) {
this.htmlElements.push(htmlElement);
return this;
}
removeHtmlElement(htmlElement: HTMLElement) {
this.htmlElements = this.htmlElements.filter((element) => element !== htmlElement);
return this;
}
setUpNeighbor(upNeighbor: Container) {
this.upNeighbor = upNeighbor;
upNeighbor.downNeighbor = this;
return this;
}
setDownNeighbor(downNeighbor: Container) {
this.downNeighbor = downNeighbor;
downNeighbor.upNeighbor = this;
return this;
}
setLeftNeighbor(leftNeighbor: Container) {
this.leftNeighbor = leftNeighbor;
leftNeighbor.rightNeighbor = this;
return this;
}
setRightNeighbor(rightNeighbor: Container) {
this.rightNeighbor = rightNeighbor;
rightNeighbor.leftNeighbor = this;
return this;
}
setDirection(direction: 'horizontal' | 'vertical') {
this.direction = direction;
return this;
}
}
export function handleKeyboardNavigation(event: KeyboardEvent) {
const currentlyFocusedObject = Container.focusedObject;
if (!currentlyFocusedObject) {
console.error('No focused object!!!');
return;
}
if (event.key === 'ArrowUp') {
currentlyFocusedObject.giveFocus('up');
} else if (event.key === 'ArrowDown') {
currentlyFocusedObject.giveFocus('down');
} else if (event.key === 'ArrowLeft') {
currentlyFocusedObject.giveFocus('left');
} else if (event.key === 'ArrowRight') {
currentlyFocusedObject.giveFocus('right');
}
}
const navBar = new Container().setDirection('vertical');
const home = new Container();
home.setLeftNeighbor(navBar);
export const navigationContainers = {
home,
navBar
};