diff --git a/src/index.tsx b/src/index.tsx index cacc90b8..50f75819 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,4 +1,5 @@ -import { createRenderer, Config, loadFonts } from "@lightningtv/solid"; +import * as s from "solid-js"; +import { createRenderer, Config, loadFonts, ElementNode, isElementNode, activeElement } from "@lightningtv/solid"; import { WebGlCoreRenderer, SdfTextRenderer, @@ -10,8 +11,7 @@ import { import { Inspector } from "@lightningjs/renderer/inspector"; import { HashRouter } from "@lightningtv/solid/primitives"; -import { Route } from "@solidjs/router"; -import { lazy } from "solid-js"; +import { Route, RouteSectionProps, RouteProps } from "@solidjs/router"; import 'solid-devtools'; import App from "./pages/App"; import Browse from "./pages/Browse"; @@ -23,34 +23,34 @@ import fonts from "./fonts"; import { browsePreload } from "./api/browsePreload"; import { entityPreload } from "./api/entityPreload"; -const Grid = lazy(() => import("./pages/Grid")); -const Loops = lazy(() => import("./pages/Loops")); -const Infinite = lazy(() => import("./pages/Infinite")); -const TMDBGrid = lazy(() => import("./pages/TMDBGrid")); -const Portal = lazy(() => import("./pages/Portal")); -const TextPage = lazy(() => import("./pages/Text")); -const TextPosterPage = lazy(() => import("./pages/TextPoster")); -const CreatePage = lazy(() => import("./pages/Create")); -const ViewportPage = lazy(() => import("./pages/Viewport")); -const PositioningPage = lazy(() => import("./pages/Positioning")); -const LayoutPage = lazy(() => import("./pages/Layout")); -const FocusBasicsPage = lazy(() => import("./pages/FocusBasics")); -const KeyHandlingPage = lazy(() => import("./pages/KeyHandling")); -const TransitionsPage = lazy(() => import("./pages/Transitions")); -const ComponentsPage = lazy(() => import("./pages/Components")); -const FocusHandlingPage = lazy(() => import("./pages/FocusHandling")); -const GradientsPage = lazy(() => import("./pages/Gradients")); -const FlexPage = lazy(() => import("./pages/Flex")); -const FlexGrowPage = lazy(() => import("./pages/FlexGrow")); -const FlexMenuPage = lazy(() => import("./pages/FlexMenu")); -const FlexSizePage = lazy(() => import("./pages/FlexSize")); -const FlexColumnSizePage = lazy(() => import("./pages/FlexColumnSize")); -const FlexColumnPage = lazy(() => import("./pages/FlexColumn")); -const ButtonsMaterialPage = lazy(() => import("./pages/ButtonsMaterial")); -const SuperFlexPage = lazy(() => import("./pages/SuperFlex")); -const Entity = lazy(() => import("./pages/Entity")); -const People = lazy(() => import("./pages/People")); -const FireboltPage = lazy(() => import("./pages/Firebolt")); +const Grid = s.lazy(() => import("./pages/Grid")); +const Loops = s.lazy(() => import("./pages/Loops")); +const Infinite = s.lazy(() => import("./pages/Infinite")); +const TMDBGrid = s.lazy(() => import("./pages/TMDBGrid")); +const Portal = s.lazy(() => import("./pages/Portal")); +const TextPage = s.lazy(() => import("./pages/Text")); +const TextPosterPage = s.lazy(() => import("./pages/TextPoster")); +const CreatePage = s.lazy(() => import("./pages/Create")); +const ViewportPage = s.lazy(() => import("./pages/Viewport")); +const PositioningPage = s.lazy(() => import("./pages/Positioning")); +const LayoutPage = s.lazy(() => import("./pages/Layout")); +const FocusBasicsPage = s.lazy(() => import("./pages/FocusBasics")); +const KeyHandlingPage = s.lazy(() => import("./pages/KeyHandling")); +const TransitionsPage = s.lazy(() => import("./pages/Transitions")); +const ComponentsPage = s.lazy(() => import("./pages/Components")); +const FocusHandlingPage = s.lazy(() => import("./pages/FocusHandling")); +const GradientsPage = s.lazy(() => import("./pages/Gradients")); +const FlexPage = s.lazy(() => import("./pages/Flex")); +const FlexGrowPage = s.lazy(() => import("./pages/FlexGrow")); +const FlexMenuPage = s.lazy(() => import("./pages/FlexMenu")); +const FlexSizePage = s.lazy(() => import("./pages/FlexSize")); +const FlexColumnSizePage = s.lazy(() => import("./pages/FlexColumnSize")); +const FlexColumnPage = s.lazy(() => import("./pages/FlexColumn")); +const ButtonsMaterialPage = s.lazy(() => import("./pages/ButtonsMaterial")); +const SuperFlexPage = s.lazy(() => import("./pages/SuperFlex")); +const Entity = s.lazy(() => import("./pages/Entity")); +const People = s.lazy(() => import("./pages/People")); +const FireboltPage = s.lazy(() => import("./pages/Firebolt")); const urlParams = new URLSearchParams(window.location.search); let numImageWorkers = 3; @@ -104,6 +104,7 @@ if (rendererMode === "canvas") { const { renderer, render } = createRenderer(); loadFonts(fonts); + // Prepare for RC3 of Renderer import { Rounded, @@ -114,6 +115,7 @@ import { LinearGradient, HolePunch, } from '@lightningjs/renderer/webgl/shaders'; + const shManager = renderer.stage.shManager; shManager.registerShaderType('rounded', Rounded) shManager.registerShaderType('roundedWithBorder', RoundedWithBorder) @@ -122,40 +124,168 @@ shManager.registerShaderType('roundedWithBorderWithShadow', RoundedWithBorderAnd shManager.registerShaderType('radialGradient', RadialGradient) shManager.registerShaderType('linearGradient', LinearGradient) shManager.registerShaderType('holePunch', HolePunch) -render(() => ( + + +function KeepAliveRoutes(routesProps: { + children: s.JSX.Element + size: number +}): s.JSX.Element { + + type Route = RouteProps + + type RoutesStackItem = { + key: string + props: RouteSectionProps + dispose: () => void + view: ElementNode + activeElement: ElementNode | null + } + const [routesStack, setRoutesStack] = s.createSignal([]); + + let lastKey = ''; + + const rootElement = + {routesStack().map(item => item.view) as any} + + + // children are actually props + let children = s.children(() => routesProps.children).toArray as () => Route[] + + return s.createMemo(() => { + + let routesIn = children() + let routesOut: Route[] = [] + + for (let routeIn of routesIn) { + + // Skip routes without a component + if (routeIn.component == null) { + routesOut.push(routeIn) + continue + } + + // Overwrite the route.component with a wrapper + let Component = routeIn.component // so it can be used like + let component: s.Component> = props => { + + let key = props.location.pathname; + + // Keep the last active element to restore focus later + let lastActiveElement = activeElement() ?? null + for (let item of routesStack()) { + if (item.key === lastKey) { + item.activeElement = lastActiveElement + break + } + } + + lastKey = key; + + let stackItem!: RoutesStackItem + searchStack: { + // Handle already existing route + for (let item of routesStack()) { + if (item.key === key) { + stackItem = item; + stackItem.props = props; + // Focus on the last active element + if (stackItem.activeElement != null) { + stackItem.activeElement.setFocus(); + } + // ...or first focusable child + else { + for (let child of stackItem.view.children) { + if (isElementNode(child)) { + child.setFocus(); + break + } + } + } + break searchStack; + } + } + + // Create new route + // in a root so it can be disposed manually later + s.createRoot(dispose => { + stackItem = { + key, + props, + dispose, + view: null!, + activeElement: null, + } + stackItem.view = + + as any + }) + + // Add to stack & dispose the oldest route if stack is full + setRoutesStack(stack => { + stack = [...stack, stackItem]; + if (stack.length > routesProps.size) { + stack.shift()?.dispose(); + } + return stack; + }) + } + + // Show only the current route + for (let item of routesStack()) { + item.view.hidden = item.key !== key + } + + return rootElement; + } + + routesOut.push({...routeIn, component}) + } + + return routesOut + }) as any; +} + +render(() => <> }> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -)); +);