Add Tizen 5.5 Polyfills, implement dpad navigation with specified components

This commit is contained in:
Aleksi Lassila
2024-01-24 01:00:12 +02:00
parent fd1a87a2fe
commit 14eae0fa9b
16 changed files with 322 additions and 147 deletions

2
.idea/reiverr.iml generated
View File

@@ -7,6 +7,8 @@
<excludeFolder url="file://$MODULE_DIR$/tmp" />
<excludeFolder url="file://$MODULE_DIR$/.svelte-kit/generated" />
<excludeFolder url="file://$MODULE_DIR$/.svelte-kit/output" />
<excludeFolder url="file://$MODULE_DIR$/dist" />
<excludeFolder url="file://$MODULE_DIR$/tizen/dist" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />

View File

@@ -55,5 +55,15 @@
"typescript": "^5.2.2",
"vite": "^4.5.1",
"vite-plugin-singlefile": "^0.13.5"
},
"browserslist": {
"production": [
"supports es6-module"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

View File

@@ -1,34 +1,54 @@
<script lang="ts">
import I18n from './lib/components/Lang/I18n.svelte';
import { Link, Route, Router } from 'svelte-navigator';
import { handleKeyboardNavigation, mainContainer } from './lib/actions/focusAction';
import { handleKeyboardNavigation } from './lib/actions/focusAction';
import { onMount } from 'svelte';
import Container from './Container.svelte';
import NavbarItem from './lib/components-new/NavbarItem.svelte';
import { Bookmark, CardStack, Gear, Laptop, MagnifyingGlass } from 'radix-icons-svelte';
import classNames from 'classnames';
import type { Readable } from 'svelte/store';
import SeriesPage from './lib/pages/SeriesPage.svelte';
import MoviesPage from './lib/pages/MoviesPage.svelte';
import LibraryPage from './lib/pages/LibraryPage.svelte';
import ManagePage from './lib/pages/ManagePage.svelte';
import SearchPage from './lib/pages/SearchPage.svelte';
import { onMount } from 'svelte';
import classNames from 'classnames';
import { Bookmark, CardStack, Gear, Laptop, MagnifyingGlass } from 'radix-icons-svelte';
import NavbarItem from './lib/components-new/NavbarItem.svelte';
const navBarContainer = mainContainer.createChild('nav').setDirection('vertical');
const isNavBarOpen = navBarContainer.hasFocusWithin;
// const navBarContainer = mainContainer.createChild('nav').setDirection('vertical');
// const isNavBarOpen = navBarContainer.hasFocusWithin;
//
// const contentContainer = mainContainer.createChild('content').setDirection('vertical');
const contentContainer = mainContainer.createChild('content').setDirection('vertical');
// onMount(() => {
// contentContainer.focus();
// });
onMount(() => {
contentContainer.focus();
});
onMount(() => console.log('didmount'));
let mount = false;
$: console.log('mount', mount);
let isNavBarOpen: Readable<boolean>;
</script>
<I18n />
<main class="bg-stone-950 text-white flex flex-1 w-screen">
<Container horizontal class="bg-stone-950 text-white flex flex-1 w-screen">
<!-- <Container class="flex flex-col">-->
<!-- <Container tag="button" on:click={() => (mount = !mount)}>Button 1</Container>-->
<!-- <Container tag="button" on:click={() => console.log('Button 2 clicked')}>Button 2</Container>-->
<!-- <Container tag="button" on:click={() => console.log('Button 3 clicked')}>Button 3</Container>-->
<!-- </Container>-->
<!-- <div>-->
<!-- <Container tag="button" on:click={() => console.log('test')}>Test</Container>-->
<!-- </div>-->
<!-- {#if mount}-->
<!-- <TestElement />-->
<!-- {/if}-->
<Router>
<nav
<Container
class={classNames('flex flex-col', 'p-4', {
border: $isNavBarOpen
})}
bind:isNavBarOpen
>
<div>
<Link to="" class="rounded-sm flex items-center">
@@ -38,50 +58,50 @@
</div>
<div class="flex flex-col flex-1 justify-center">
<NavbarItem parentContainer={navBarContainer} to="series">
<NavbarItem to="series">
<Laptop class="w-8 h-8 mr-3" slot="icon" />
<span class="text-xl" slot="text"> Series</span>
</NavbarItem>
<NavbarItem parentContainer={navBarContainer} to="movies">
<NavbarItem to="movies">
<CardStack class="w-8 h-8 mr-3" slot="icon" />
<span class="text-xl" slot="text"> Movies</span>
</NavbarItem>
<NavbarItem parentContainer={navBarContainer} to="library">
<NavbarItem to="library">
<Bookmark class="w-8 h-8 mr-3" slot="icon" />
<span class="text-xl" slot="text"> Library</span>
</NavbarItem>
<NavbarItem parentContainer={navBarContainer} to="search">
<NavbarItem to="search">
<MagnifyingGlass class="w-8 h-8 mr-3" slot="icon" />
<span class="text-xl" slot="text"> Search</span>
</NavbarItem>
</div>
<div>
<NavbarItem parentContainer={navBarContainer} to="manage">
<NavbarItem to="manage">
<Gear class="w-8 h-8 mr-3" slot="icon" />
<span class="text-xl" slot="text"> Manage</span>
</NavbarItem>
</div>
</nav>
</Container>
<div class="flex-1 flex flex-col min-w-0">
<Container class="flex-1 flex flex-col min-w-0">
<Route>
<SeriesPage container={contentContainer} />
<SeriesPage />
</Route>
<Route path="movies">
<MoviesPage container={contentContainer} />
<MoviesPage />
</Route>
<Route path="library">
<LibraryPage parent={contentContainer} />
<LibraryPage />
</Route>
<Route path="manage">
<ManagePage container={contentContainer} />
<ManagePage />
</Route>
<Route path="search">
<SearchPage container={contentContainer} />
<SearchPage />
</Route>
</div>
</Container>
</Router>
</main>
</Container>
<svelte:window on:keydown={handleKeyboardNavigation} />

28
src/Container.svelte Normal file
View File

@@ -0,0 +1,28 @@
<script lang="ts">
import { onMount } from 'svelte';
import { Container } from './lib/actions/focusAction';
export let name: string = '';
export let horizontal = false;
const { registerer, ...rest } = new Container(name)
.setDirection(horizontal ? 'horizontal' : 'vertical')
.getStores();
export const container = rest.container;
export const hasFocus = rest.hasFocus;
export const hasFocusWithin = rest.hasFocusWithin;
export let tag = 'div';
onMount(() => {
rest.container._initializeContainer();
return () => {
rest.container._unmountContainer();
};
});
</script>
<svelte:element this={tag} on:click tabindex="0" {...$$restProps} use:registerer>
<slot />
</svelte:element>

View File

@@ -0,0 +1,20 @@
<script lang="ts">
import { onMount } from 'svelte';
import { Container, type FlowDirection } from './lib/actions/focusAction';
export let name: string = '';
export let direction: FlowDirection = 'vertical';
const { registerer, ...rest } = new Container(name).setDirection(direction).getStores();
export const container = rest.container;
export const hasFocus = rest.hasFocus;
export const hasFocusWithin = rest.hasFocusWithin;
onMount(() => {
rest.container._initializeContainer();
});
</script>
<button use:registerer>
<slot />
</button>

22
src/TestElement.svelte Normal file
View File

@@ -0,0 +1,22 @@
<script lang="ts">
import { onMount } from 'svelte';
import Container from './Container.svelte';
import type { Readable } from 'svelte/store';
import classNames from 'classnames';
let hasFocusWithin: Readable<boolean>;
onMount(() => {
console.log('Hello world! onMount testElement');
});
</script>
<Container bind:hasFocusWithin>
<h1
class={classNames({
'bg-red-500': $hasFocusWithin
})}
>
Hello world!
</h1>
</Container>

View File

@@ -1,5 +1,4 @@
import { derived, get, type Readable, type Writable, writable } from 'svelte/store';
import { navigate } from 'svelte-navigator';
export type Registerer = (htmlElement: HTMLElement) => { destroy: () => void };
@@ -19,6 +18,7 @@ export class Container {
right: undefined
};
private focusByDefault: boolean = false;
private isInitialized: boolean = false;
private direction: FlowDirection = 'vertical';
@@ -42,12 +42,13 @@ export class Container {
return false;
});
static objects = new Map<symbol, Container>();
static objects = new Map<HTMLElement, Container>();
constructor(name: string = '') {
this.id = Symbol();
this.name = name;
Container.objects.set(this.id, this);
// Find parents
}
setDirection(direction: FlowDirection) {
@@ -60,12 +61,18 @@ export class Container {
return this;
}
createChild(name: string = '') {
const child = new Container(name);
this.addChild(child);
return child;
setHtmlElement(htmlElement: HTMLElement) {
this.htmlElement = htmlElement;
Container.objects.set(htmlElement, this);
return this;
}
// createChild(htmlElement: HTMLElement, name: string = '') {
// const child = new Container(htmlElement, name);
// this.addChild(child);
// return child;
// }
focus() {
if (this.children.length > 0) {
this.children[get(this.focusIndex)]?.focus();
@@ -89,7 +96,7 @@ export class Container {
isFocusable() {
if (this.htmlElement) {
return true;
return this.htmlElement.tabIndex >= 0;
} else {
for (const child of this.children) {
if (child.isFocusable()) {
@@ -143,57 +150,135 @@ export class Container {
}
}
getChildRegisterer(): Registerer {
return (htmlElement: HTMLElement) => {
if (this.htmlElement) console.warn('Registering to a container that has an element.');
// getChildRegisterer(): Registerer {
// return (htmlElement: HTMLElement) => {
// if (this.htmlElement) console.warn('Registering to a container that has an element.');
//
// this.createChild().addHtmlElement(htmlElement);
//
// if (!get(Container.focusedObject) && this.shouldFocusByDefault()) {
// this.focus();
// }
//
// return {
// destroy: () => {
// this.removeHtmlElement();
// }
// };
// };
// }
//
// getHtmlElementRegisterer(): Registerer {
// return (htmlElement: HTMLElement) => {
// if (this.children.length > 0) {
// console.warn('Registering an html element to a container that has children.');
// for (const child of this.children) {
// this.removeChild(child);
// }
// }
// this.addHtmlElement(htmlElement);
// return {
// destroy: () => {
// this.removeHtmlElement();
// }
// };
// };
// }
this.createChild().addHtmlElement(htmlElement);
_initializeContainer() {
const getParentContainer = (htmlElement: HTMLElement): Container | undefined => {
if (Container.objects.get(htmlElement)) return Container.objects.get(htmlElement);
else if (htmlElement.parentElement) return getParentContainer(htmlElement.parentElement);
else return undefined;
};
if (!this.htmlElement) {
console.error('No html element found for', this);
return;
} else if (this.isInitialized) {
console.warn('Container already initialized', this);
}
const parentContainer = this.htmlElement.parentElement
? getParentContainer(this.htmlElement.parentElement)
: undefined;
if (parentContainer) {
parentContainer.addChild(this);
} else {
console.error('No parent container found for', this.htmlElement);
}
if (!get(Container.focusedObject) && this.shouldFocusByDefault()) {
this.focus();
}
return {
destroy: () => {
this.removeHtmlElement();
}
};
};
}
getHtmlElementRegisterer(): Registerer {
_unmountContainer() {
console.log('Unmounting container', this);
const isFocusedWithin = get(this.hasFocusWithin);
if (this.htmlElement) {
Container.objects.delete(this.htmlElement);
}
const parent = this.parent;
if (parent) {
parent.removeChild(this);
if (isFocusedWithin) {
parent.focus();
}
}
}
private static createRegisterer(
_container?: Container,
flowDirection: FlowDirection = 'vertical'
): Registerer {
const container = _container || new Container().setDirection(flowDirection);
return (htmlElement: HTMLElement) => {
if (this.children.length > 0) {
console.warn('Registering an html element to a container that has children.');
for (const child of this.children) {
this.removeChild(child);
}
}
this.addHtmlElement(htmlElement);
console.log('Registering', htmlElement, container);
container.setHtmlElement(htmlElement);
return {
destroy: () => {
this.removeHtmlElement();
container.parent?.removeChild(container);
Container.objects.delete(htmlElement);
}
};
};
}
static getRegisterer(flowDirection: FlowDirection = 'vertical'): Registerer {
return (htmlElement: HTMLElement) =>
this.createRegisterer(undefined, flowDirection)(htmlElement);
}
getRegisterer(): Registerer {
return (htmlElement: HTMLElement) => Container.createRegisterer(this)(htmlElement);
}
static getStores(element: HTMLElement) {
return Container.objects.get(element)?.getStores();
}
getStores(): {
container: Container;
hasFocus: Readable<boolean>;
hasFocusWithin: Readable<boolean>;
registerer: Registerer;
} {
return {
container: this,
hasFocus: this.hasFocus,
hasFocusWithin: this.hasFocusWithin
hasFocusWithin: this.hasFocusWithin,
registerer: this.getRegisterer()
};
}
private addChild(child: Container) {
this.children.push(child);
child.parent = this;
this.htmlElement = undefined;
return this;
}
@@ -203,21 +288,16 @@ export class Container {
return this;
}
private addHtmlElement(htmlElement: HTMLElement) {
if (this.children.length > 0) {
console.warn('Adding an html element to a container that has children.');
for (const child of this.children) {
this.removeChild(child);
}
}
this.htmlElement = htmlElement;
return this;
}
private removeHtmlElement() {
this.htmlElement = undefined;
return this;
}
// private addHtmlElement(htmlElement: HTMLElement) {
// if (this.children.length > 0) {
// console.warn('Adding an html element to a container that has children.');
// for (const child of this.children) {
// this.removeChild(child);
// }
// }
// this.htmlElement = htmlElement;
// return this;
// }
private shouldFocusByDefault(): boolean {
return this.focusByDefault || this.parent?.shouldFocusByDefault() || false;
@@ -229,7 +309,12 @@ export function handleKeyboardNavigation(event: KeyboardEvent) {
if (!currentlyFocusedObject) {
console.error('No focused object!!!');
mainContainer.focus();
// Find object that can be focused
Container.objects.forEach((container) => {
if (container.isFocusable()) {
container.focus();
}
});
return;
}
@@ -247,6 +332,8 @@ export function handleKeyboardNavigation(event: KeyboardEvent) {
}
export const focusedObject = Container.focusedObject;
export const mainContainer = new Container('main')
.setDirection('horizontal')
.setFocusByDefault(true);
// export const mainContainer = new Container('main')
// .setDirection('horizontal')
// .setFocusByDefault(true);
export const registerer = Container.getRegisterer();

View File

@@ -1,24 +1,22 @@
<script lang="ts">
import Selectable from '../components/Selectable.svelte';
import classNames from 'classnames';
import { useNavigate } from 'svelte-navigator';
import { get } from 'svelte/store';
import { Container } from '../actions/focusAction';
import { get, type Readable } from 'svelte/store';
import Container from '../../Container.svelte';
import { Container as Cont } from '../../lib/actions/focusAction';
export let to: string;
export let parentContainer: Container;
const { container, hasFocus } = parentContainer.createChild('navBarItem').getStores();
let hasFocus: Readable<boolean>;
const navigate = useNavigate();
function handleClick() {
navigate(to);
get(Container.focusedObject)?.giveFocus('right');
get(Cont.focusedObject)?.giveFocus('right');
}
</script>
<button on:click={handleClick}>
<Selectable {container}>
<Container tag="button" on:click={handleClick} bind:hasFocus>
<div
class={classNames('flex items-center my-2', {
'text-amber-200': $hasFocus
@@ -27,5 +25,4 @@
<slot name="icon" />
<slot name="text" />
</div>
</Selectable>
</button>
</Container>

View File

@@ -1,37 +1,31 @@
<script lang="ts">
import CardPlaceholder from '../Card/CardPlaceholder.svelte';
import { Container } from '../../actions/focusAction';
import classNames from 'classnames';
import Container from '../../../Container.svelte';
import type { Readable } from 'svelte/store';
export let size: 'dynamic' | 'md' | 'lg' = 'md';
export let orientation: 'landscape' | 'portrait' = 'landscape';
export let container: Container;
let carousel = container.createChild('carousel').setDirection('horizontal');
let focusIndexStore = carousel.focusIndex;
let focusWithinStore = carousel.hasFocusWithin;
Container.focusedObject.subscribe((fo) => console.log('focusedObject', fo));
carousel.hasFocus.subscribe((hf) => console.log('hasFocus', hf));
let registerer = carousel.getChildRegisterer();
let focusIndex: Readable<number>;
let focusWithin: Readable<boolean>;
</script>
<p
class={classNames({
'bg-blue-500': $focusWithinStore
'bg-blue-500': $focusWithin
})}
>
Index: {$focusIndexStore}
Index: {$focusIndex}
</p>
{#each Array(10) as _, i (i)}
<div
tabindex="0"
use:registerer
<Container
bind:focusIndex
bind:focusWithin
class={classNames({
'bg-red-500': $focusIndexStore === i && $focusWithinStore
'bg-red-500': $focusIndex === i && $focusWithin
})}
>
<CardPlaceholder {size} index={i} {orientation} />
</div>
</Container>
{/each}

View File

@@ -2,13 +2,13 @@
import { Container } from '../actions/focusAction';
export let container: Container;
const registerer = container.getHtmlElementRegisterer();
const registerer = container.getRegisterer();
export let handleClick = () => {
container.focus();
};
</script>
<button use:registerer tabindex="0" on:click={handleClick} class="outline-none ring-0">
<button use:registerer on:click={handleClick} class="outline-none ring-0">
<slot />
</button>

View File

@@ -1,12 +1,9 @@
<script lang="ts">
import type { Container } from '../actions/focusAction';
import { settings } from '../stores/settings.store';
import { jellyfinItemsStore } from '../stores/data.store';
import Carousel from '../components/Carousel/Carousel.svelte';
import CarouselPlaceholderItems from '../components/Carousel/CarouselPlaceholderItems.svelte';
export let parent: Container;
let registerer = parent.getChildRegisterer();
import Container from '../../Container.svelte';
settings.update((prev) => ({
...prev,
@@ -22,13 +19,13 @@
jellyfinItemsStore.subscribe((items) => {
console.warn('GOT ITEMS', items.data);
});
let asd: HTMLDivElement;
$: console.log('asd', asd);
</script>
<div use:registerer bind:this={asd}>
<Container>
<div>LibraryPage</div>
<Container horizontal>
<Carousel>
<CarouselPlaceholderItems container={parent} />
<CarouselPlaceholderItems />
</Carousel>
</div>
</Container>
</Container>

View File

@@ -1,8 +1,5 @@
<script lang="ts">
import type { Container } from '../actions/focusAction';
export let container: Container;
let registerer = container.getChildRegisterer();
import Container from '../../Container.svelte';
</script>
<div use:registerer>ManagePage</div>
<Container>ManagePage</Container>

View File

@@ -1,8 +1,5 @@
<script lang="ts">
import type { Container } from '../actions/focusAction';
export let container: Container;
let registerer = container.getChildRegisterer();
import Container from '../../Container.svelte';
</script>
<div use:registerer>MoviesPage</div>
<Container>MoviesPage</Container>

View File

@@ -1,8 +1,5 @@
<script lang="ts">
import type { Container } from '../actions/focusAction';
export let container: Container;
let registerer = container.getChildRegisterer();
import Container from '../../Container.svelte';
</script>
<div use:registerer>SearchPage</div>
<Container>SearchPage</Container>

View File

@@ -1,8 +1,5 @@
<script lang="ts">
import type { Container } from '../actions/focusAction';
export let container: Container;
let registerer = container.getChildRegisterer();
import Container from '../../Container.svelte';
</script>
<div use:registerer>SeriesPage</div>
<Container>SeriesPage</Container>

View File

@@ -5,7 +5,17 @@ import viteLegacyPlugin from '@vitejs/plugin-legacy';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [viteLegacyPlugin(), svelte(), viteSingleFile()],
plugins: [
viteLegacyPlugin({
// targets: ['chrome >= 64', 'edge >= 79', 'safari >= 11.1', 'firefox >= 67'],
// ignoreBrowserslistConfig: true,
renderLegacyChunks: true,
// modernPolyfills: ['es/global-this']
modernPolyfills: true
}),
svelte(),
viteSingleFile()
],
optimizeDeps: { exclude: ['svelte-navigator'] }
// base: '/dist',