diff --git a/lg/appinfo.json b/lg/appinfo.json index 249cae30..d5c7ef68 100644 --- a/lg/appinfo.json +++ b/lg/appinfo.json @@ -5,5 +5,9 @@ "title": "Solid Demo App", "main": "index.html", "icon": "icon.png", - "largeIcon": "largeIcon.png" + "largeIcon": "largeIcon.png", + "disableBackHistoryAPI": true, + "accessibility": { + "supportsAudioGuidance": true + } } diff --git a/patches/@solidjs__router.patch b/patches/@solidjs__router.patch deleted file mode 100644 index eb934382..00000000 --- a/patches/@solidjs__router.patch +++ /dev/null @@ -1,92 +0,0 @@ -diff --git a/dist/routing.js b/dist/routing.js -index 9477639bcb9958680e10361f84ba7f2cbb8e807f..1c020c8caed9044f2ae74b8af44321b5a36a9869 100644 ---- a/dist/routing.js -+++ b/dist/routing.js -@@ -2,7 +2,7 @@ import { runWithOwner, batch } from "solid-js"; - import { createComponent, createContext, createMemo, createRenderEffect, createSignal, on, onCleanup, untrack, useContext, startTransition, resetErrorBoundaries } from "solid-js"; - import { isServer, getRequestEvent } from "solid-js/web"; - import { createBeforeLeave } from "./lifecycle.js"; --import { mockBase, createMemoObject, extractSearchParams, invariant, resolvePath, createMatcher, joinPaths, scoreRoute, mergeSearchString, expandOptionals } from "./utils.js"; -+import { mockBase, createMemoObject, createMemoWithoutProxy, extractSearchParams, invariant, resolvePath, createMatcher, joinPaths, scoreRoute, mergeSearchString, expandOptionals } from "./utils.js"; - const MAX_REDIRECTS = 100; - export const RouterContextObj = createContext(); - export const RouteContextObj = createContext(); -@@ -248,14 +248,38 @@ export function createRouterContext(integration, branches, getContext, options = - } - return getRouteMatches(branches(), location.pathname); - }); -- const params = createMemoObject(() => { -+ const collectDynamicParams =(branches) => { -+ const dynamicParams= []; -+ -+ branches.forEach(branch => { -+ branch.routes.forEach(route => { -+ if (route.pattern) { -+ const matches = route.pattern.match(/:(\w+)/g); -+ if (matches) { -+ matches.forEach(param => { -+ const p = param.slice(1); // Remove the `:` -+ if (!dynamicParams.includes(p)) -+ dynamicParams.push(p) -+ }); -+ } -+ } -+ }); -+ }); -+ -+ return dynamicParams; -+ } -+ -+ const SUPPORTS_PROXY = typeof Proxy === "function"; -+ const paramsHandler = () => { - const m = matches(); - const params = {}; - for (let i = 0; i < m.length; i++) { - Object.assign(params, m[i].params); - } - return params; -- }); -+ }; -+ const params = SUPPORTS_PROXY ? createMemoObject(paramsHandler) : createMemoWithoutProxy(paramsHandler, collectDynamicParams(branches())); -+ - const baseRoute = { - pattern: basePath, - path: () => basePath, -diff --git a/dist/utils.js b/dist/utils.js -index fec17645ae81b9fc80252430d07481ea616c3aa3..601332ad2a9fd85e1ef5ede020510e7020c875fd 100644 ---- a/dist/utils.js -+++ b/dist/utils.js -@@ -156,3 +156,33 @@ export function expandOptionals(pattern) { - } - return expandOptionals(suffix).reduce((results, expansion) => [...results, ...prefixes.map(p => p + expansion)], []); - } -+ -+export function createMemoWithoutProxy(fn, allKeys) { -+ const map = new Map(); -+ const owner = getOwner(); -+ const target = {}; -+ -+ const handler = (property) => { -+ if (!map.has(property)) { -+ runWithOwner(owner, () => -+ map.set( -+ property, -+ createMemo(() => fn()[property]) -+ ) -+ ); -+ } -+ return map.get(property)(); -+ }; -+ -+ const keys = allKeys ? allKeys : Object.keys(fn()) || []; -+ -+ keys.forEach((key) => { -+ Object.defineProperty(target, key, { -+ get: () => handler(key), -+ enumerable: true, -+ configurable: true, -+ }); -+ }); -+ -+ return target; -+ } diff --git a/patches/@vitejs__plugin-legacy.patch b/patches/@vitejs__plugin-legacy.patch deleted file mode 100644 index 745d79e8..00000000 --- a/patches/@vitejs__plugin-legacy.patch +++ /dev/null @@ -1,224 +0,0 @@ -diff --git a/dist/index.cjs b/dist/index.cjs -index e8a593a533ca14c65a60360e64fb3d62e83778db..f818e17620d4debc6a5be8f8ca6b6b5c31bcf107 100644 ---- a/dist/index.cjs -+++ b/dist/index.cjs -@@ -164,7 +164,7 @@ function joinUrlSegments(a, b) { - if (!a || !b) { - return a || b || ""; - } -- if (a[a.length - 1] === "/") { -+ if (a.endsWith("/")) { - a = a.substring(0, a.length - 1); - } - if (b[0] !== "/") { -@@ -209,6 +209,7 @@ function viteLegacyPlugin(options = {}) { - } - const debugFlags = (process.env.DEBUG || "").split(","); - const isDebug = debugFlags.includes("vite:*") || debugFlags.includes("vite:legacy"); -+ const assumptions = options.assumptions || {}; - const facadeToLegacyChunkMap = /* @__PURE__ */ new Map(); - const facadeToLegacyPolyfillMap = /* @__PURE__ */ new Map(); - const facadeToModernPolyfillMap = /* @__PURE__ */ new Map(); -@@ -345,6 +346,7 @@ function viteLegacyPlugin(options = {}) { - await detectPolyfills( - `Promise.resolve(); Promise.all();`, - targets, -+ assumptions, - legacyPolyfills - ); - } -@@ -454,7 +456,12 @@ function viteLegacyPlugin(options = {}) { - } - if (!isLegacyChunk(chunk, opts)) { - if (options.modernPolyfills && !Array.isArray(options.modernPolyfills) && genModern) { -- await detectPolyfills(raw, modernTargets, polyfillsDiscovered.modern); -+ await detectPolyfills( -+ raw, -+ modernTargets, -+ assumptions, -+ polyfillsDiscovered.modern -+ ); - } - const ms = new MagicString__default(raw); - if (genLegacy && chunk.isEntry) { -@@ -496,6 +503,7 @@ function viteLegacyPlugin(options = {}) { - compact: !!config.build.minify, - sourceMaps, - inputSourceMap: void 0, -+ assumptions, - presets: [ - // forcing our plugin to run before preset-env by wrapping it in a - // preset so we can catch the injected import statements... -@@ -649,13 +657,14 @@ function viteLegacyPlugin(options = {}) { - }; - return [legacyConfigPlugin, legacyGenerateBundlePlugin, legacyPostPlugin]; - } --async function detectPolyfills(code, targets, list) { -+async function detectPolyfills(code, targets, assumptions, list) { - const babel2 = await loadBabel(); - const result = babel2.transform(code, { - ast: true, - babelrc: false, - configFile: false, - compact: false, -+ assumptions, - presets: [ - [ - (await import('@babel/preset-env')).default, -@@ -712,7 +721,8 @@ async function buildPolyfillChunk(mode, imports, bundle, facadeToChunkMap, build - output: { - format, - hashCharacters: rollupOutputOptions.hashCharacters, -- entryFileNames: rollupOutputOptions.entryFileNames -+ entryFileNames: rollupOutputOptions.entryFileNames, -+ sourcemapBaseUrl: rollupOutputOptions.sourcemapBaseUrl - } - } - }, -diff --git a/dist/index.d.cts b/dist/index.d.cts -index 3872f9aa9f22bd3f72c20f31b53296fea31db86c..f4adce20734c5bd272bcbb810fd200d8c91fd335 100644 ---- a/dist/index.d.cts -+++ b/dist/index.d.cts -@@ -31,10 +31,17 @@ interface Options { - * default: true - */ - renderModernChunks?: boolean; -+ /** -+ * @see https://babeljs.io/docs/assumptions -+ * -+ * default: {} -+ */ -+ assumptions?: Record; - } - - declare function viteLegacyPlugin(options?: Options): Plugin[]; --declare function detectPolyfills(code: string, targets: any, list: Set): Promise; -+declare function detectPolyfills(code: string, targets: any, assumptions: Record, list: Set): Promise; - declare const cspHashes: string[]; - --export { type Options, cspHashes, viteLegacyPlugin as default, detectPolyfills }; -+export = viteLegacyPlugin; -+export { type Options, cspHashes, detectPolyfills }; -diff --git a/dist/index.d.mts b/dist/index.d.mts -index 3872f9aa9f22bd3f72c20f31b53296fea31db86c..19701da58abdcbfbdedc2499a6fbcc4c60edb165 100644 ---- a/dist/index.d.mts -+++ b/dist/index.d.mts -@@ -31,10 +31,16 @@ interface Options { - * default: true - */ - renderModernChunks?: boolean; -+ /** -+ * @see https://babeljs.io/docs/assumptions -+ * -+ * default: {} -+ */ -+ assumptions?: Record; - } - - declare function viteLegacyPlugin(options?: Options): Plugin[]; --declare function detectPolyfills(code: string, targets: any, list: Set): Promise; -+declare function detectPolyfills(code: string, targets: any, assumptions: Record, list: Set): Promise; - declare const cspHashes: string[]; - - export { type Options, cspHashes, viteLegacyPlugin as default, detectPolyfills }; -diff --git a/dist/index.d.ts b/dist/index.d.ts -index 3872f9aa9f22bd3f72c20f31b53296fea31db86c..f4adce20734c5bd272bcbb810fd200d8c91fd335 100644 ---- a/dist/index.d.ts -+++ b/dist/index.d.ts -@@ -31,10 +31,17 @@ interface Options { - * default: true - */ - renderModernChunks?: boolean; -+ /** -+ * @see https://babeljs.io/docs/assumptions -+ * -+ * default: {} -+ */ -+ assumptions?: Record; - } - - declare function viteLegacyPlugin(options?: Options): Plugin[]; --declare function detectPolyfills(code: string, targets: any, list: Set): Promise; -+declare function detectPolyfills(code: string, targets: any, assumptions: Record, list: Set): Promise; - declare const cspHashes: string[]; - --export { type Options, cspHashes, viteLegacyPlugin as default, detectPolyfills }; -+export = viteLegacyPlugin; -+export { type Options, cspHashes, detectPolyfills }; -diff --git a/dist/index.mjs b/dist/index.mjs -index e06d4a9fcb81500735f1d550447ffa07095853d5..ef479a91fbfb33a47b071c3341779fd7da858cc4 100644 ---- a/dist/index.mjs -+++ b/dist/index.mjs -@@ -152,7 +152,7 @@ function joinUrlSegments(a, b) { - if (!a || !b) { - return a || b || ""; - } -- if (a[a.length - 1] === "/") { -+ if (a.endsWith("/")) { - a = a.substring(0, a.length - 1); - } - if (b[0] !== "/") { -@@ -197,6 +197,7 @@ function viteLegacyPlugin(options = {}) { - } - const debugFlags = (process.env.DEBUG || "").split(","); - const isDebug = debugFlags.includes("vite:*") || debugFlags.includes("vite:legacy"); -+ const assumptions = options.assumptions || {}; - const facadeToLegacyChunkMap = /* @__PURE__ */ new Map(); - const facadeToLegacyPolyfillMap = /* @__PURE__ */ new Map(); - const facadeToModernPolyfillMap = /* @__PURE__ */ new Map(); -@@ -333,6 +334,7 @@ function viteLegacyPlugin(options = {}) { - await detectPolyfills( - `Promise.resolve(); Promise.all();`, - targets, -+ assumptions, - legacyPolyfills - ); - } -@@ -442,7 +444,12 @@ function viteLegacyPlugin(options = {}) { - } - if (!isLegacyChunk(chunk, opts)) { - if (options.modernPolyfills && !Array.isArray(options.modernPolyfills) && genModern) { -- await detectPolyfills(raw, modernTargets, polyfillsDiscovered.modern); -+ await detectPolyfills( -+ raw, -+ modernTargets, -+ assumptions, -+ polyfillsDiscovered.modern -+ ); - } - const ms = new MagicString(raw); - if (genLegacy && chunk.isEntry) { -@@ -484,6 +491,7 @@ function viteLegacyPlugin(options = {}) { - compact: !!config.build.minify, - sourceMaps, - inputSourceMap: void 0, -+ assumptions, - presets: [ - // forcing our plugin to run before preset-env by wrapping it in a - // preset so we can catch the injected import statements... -@@ -637,13 +645,14 @@ function viteLegacyPlugin(options = {}) { - }; - return [legacyConfigPlugin, legacyGenerateBundlePlugin, legacyPostPlugin]; - } --async function detectPolyfills(code, targets, list) { -+async function detectPolyfills(code, targets, assumptions, list) { - const babel2 = await loadBabel(); - const result = babel2.transform(code, { - ast: true, - babelrc: false, - configFile: false, - compact: false, -+ assumptions, - presets: [ - [ - (await import('@babel/preset-env')).default, -@@ -700,7 +709,8 @@ async function buildPolyfillChunk(mode, imports, bundle, facadeToChunkMap, build - output: { - format, - hashCharacters: rollupOutputOptions.hashCharacters, -- entryFileNames: rollupOutputOptions.entryFileNames -+ entryFileNames: rollupOutputOptions.entryFileNames, -+ sourcemapBaseUrl: rollupOutputOptions.sourcemapBaseUrl - } - } - }, diff --git a/src/api/index.ts b/src/api/index.ts index cca60d30..7c0f966d 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -2,7 +2,8 @@ import API_KEY_V4 from "./key"; const API_BASE = "https://api.themoviedb.org/3"; let tmdbConfig; let baseImageUrl; -const basePosterSize = "w185"; +const urlParams = new URLSearchParams(window.location.search); +const basePosterSize = urlParams.get("posterSize") || "w185"; const defaultFetchParams = { headers: { diff --git a/src/components/ContentBlock.tsx b/src/components/ContentBlock.tsx index 542a281c..29d3624f 100644 --- a/src/components/ContentBlock.tsx +++ b/src/components/ContentBlock.tsx @@ -1,4 +1,5 @@ import { View, Text } from "@lightningtv/solid"; +import { Marquee } from "@lightningtv/solid/primitives"; import { For, Show } from "solid-js"; import theme from "theme"; @@ -19,9 +20,8 @@ const HeadlineStyles = { fontWeight: 700, maxLines: 1, width: blockWidth, - contain: "width" }; -const Headline = (props) => ; +const Headline = (props) => ; const DescriptionStyles = { ...theme.typography.body1, @@ -99,7 +99,7 @@ const Metadata = (props) => ( const ContentBlock = (props) => ( - {props.content.title} + {props.content.title} {props.content.description} ; + + /** + * signal passed in to represent the actual value within the input + */ + valueSignal: Signal; + + /** + * when true the content will be masked to the user + */ + password?: boolean; + + /** + * character to use as a mask when password is true + */ + mask?: string; +} + +const ContainerStyle: NodeStyles = { + display: 'flex', + flexBoundary: 'fixed', + padding: 20, + width: 450, + height: 70, + borderRadius: 8, + border: { + color: '#c3c3c3', + width: 2, + }, + $focus: { + border: { + color: '#FFFFFF', + width: 2, + }, + }, +}; +const TextStyle: TextStyles = { + fontSize: 46, + lineHeight: 70, +}; + +const getformatValueText = (props, value) => + (props.password ? (props.mask ?? '').repeat(value.length ?? 0) : value); + +const Input: Component = props => { + const [ value, setValue ] = props.valueSignal ?? createSignal(''); + const [ position, setPosition ] = createSignal(props.position ?? value().length); + const [ keyEvent, setKeyEvent ] = props.keyEvents ?? createSignal(''); + const formatValueText = createMemo(() => getformatValueText(props, value())); + + const formatInputText = (key: string) => { + if (key === undefined || key === '') + return; + + + const inputText = value(); + let currentPosition = value().length; + let newValue = ''; + switch (key.toLowerCase()) { + case 'bksp': + case 'delete': + newValue = + currentPosition > 0 ? + inputText.slice(0, currentPosition - 1) + inputText.slice(currentPosition) : + inputText; + currentPosition--; + break; + case 'done': + break; + case 'space': + newValue = + currentPosition > 0 ? + `${inputText.slice(0, currentPosition) } ${ inputText.slice(currentPosition)}` : + ` ${ inputText}`; + currentPosition++; + break; + case 'clear': + newValue = ''; + currentPosition = 0; + break; + default: + newValue = + currentPosition > 0 ? + inputText.slice(0, currentPosition) + key + inputText.slice(currentPosition) : + key + inputText; + currentPosition++; + break; + } + + setKeyEvent(''); + setValue(newValue); + // setPosition(currentPosition); + return ''; + }; + + createEffect(on(keyEvent, formatInputText, { defer: true })); + + function onRight() { + setPosition(p => Math.max(p + 1, value().length)); + return true; + } + + function onLeft() { + setPosition(p => Math.max(p - 1, 0)); + return true; + } + + return ( + + + {formatValueText() || props.placeholder || ''} + + + ); +}; + +export default Input; diff --git a/src/components/Keyboard.tsx b/src/components/Keyboard.tsx new file mode 100644 index 00000000..e66fd739 --- /dev/null +++ b/src/components/Keyboard.tsx @@ -0,0 +1,202 @@ +import * as s from 'solid-js'; +import * as lng from '@lightningtv/solid'; +import * as lngp from '@lightningtv/solid/primitives'; +import Input from './Input'; + +const actionKeyContainerStyle: lng.NodeStyles = { + width: 144, + alpha: 0.8, + height: 60, + scale: 1, + color: '#0000FF', + borderRadius: 6, + $focus: { + alpha: 1, + scale: 1.05, + }, + transition: { scale: true }, +}; + +const ActionKeyIconStyle: lng.NodeStyles = { + y: 6, + x: 48, + width: 48, + height: 48, + color: '#c6c6c6', +}; + +const keyContainerStyle: lng.NodeStyles = { + height: 60, + color: '#000000', + scale: 1, + borderRadius: 6, + $focus: { + scale: 1.05, + color: '#0000FF', + }, +}; + +const BaseKeyTextStyle: lng.TextStyles = { + fontSize: 42, + lineHeight: 60, +}; + +const KeyText: lng.TextStyles = { + ...BaseKeyTextStyle, + width: 48, + contain: 'both', + textAlign: 'center', +}; + +export function onKeyPressWhenKeyboardOpen(setKeyEvent: s.Setter, event: KeyboardEvent) { + if (event.key.length === 1) + setKeyEvent(event.key); + else if (event.key === 'Backspace') + setKeyEvent('delete'); + if (event.key.length === 1 && (/[a-zA-Z0-9._@-]/).test(event.key) || event.key === 'Backspace') + return true; + return false; +}; + +export interface KeyProps extends lng.NodeProps { + key?: string; + title?: string; + textColor?: string; +} + +export const Key: s.Component = props => ( + + {props.key || props.title} + +); + +export interface ActionKeyProps extends lng.NodeProps { + key: string +} + +export const ActionKey: s.Component = props => ( + + + + {props.key} + + + + + + + + + + {props.key.title} + + + +); + +export const Keyboard: s.Component = props => { + const [layout, setLayout] = s.createSignal('default'); + const config = s.createMemo(() => (props.formats[layout()])); + const onEnter = (_e, _keyboard, key) => { + if (typeof key.key === "string") { + return false; + } + + if (key.key.title === 'shift') { + setLayout(p => p === 'uppercase' ? 'default' : 'uppercase'); + return true; + } + + if (key.key.title === 'symbol') { + setLayout(p => p === 'symbol' ? 'default' : 'symbol'); + return true; + } + + return false; + }; + + const handleEnter = lngp.chainFunctions(onEnter, props.onEnter); + + return + + {keyRow => ( + + + {key => + }> + + + } + + + )} + + + ; +}; + +export interface FullScreenKeyboardProps extends lng.NodeProps { + type?: 'default' | 'uppercase' | 'symbol'; + valueSignal: s.Signal; + placeholder?: string; + formats?: any; +} + +export const FullScreenKeyboard: s.Component = props => { + const keyEvents = s.createSignal(''); + const [_keyEvent, setKeyEvent] = keyEvents; + + const onEnter = (_e, _keyboard, key) => { + if (key.key === 'save' || key.key === 'Save') + return false; + + if (key.key) + setKeyEvent(key.key as string); + + return true; + }; + + return ( + onKeyPressWhenKeyboardOpen(setKeyEvent, keyBoardEvent)} + > + + + + + + + + ); +}; diff --git a/src/components/NavDrawer/NavDrawer.tsx b/src/components/NavDrawer/NavDrawer.tsx index c27d4230..1cc165fe 100644 --- a/src/components/NavDrawer/NavDrawer.tsx +++ b/src/components/NavDrawer/NavDrawer.tsx @@ -94,22 +94,25 @@ export default function NavDrawer(props) { onFocus={onFocus} onBlur={onBlur} style={styles.Column} + announce={"Main Menu"} scroll="none" > handleNavigate("/browse/all")} + announce={["Trending Browse", "button"]} icon="trending" > Trending - handleNavigate("/browse/movie")}> + handleNavigate("/browse/movie")}> Movies - handleNavigate("/browse/tv")}> + handleNavigate("/browse/tv")}> TV handleNavigate("/examples")} > Examples diff --git a/src/components/index.tsx b/src/components/index.tsx index fedfefa4..ae3e6124 100644 --- a/src/components/index.tsx +++ b/src/components/index.tsx @@ -15,8 +15,10 @@ export function Thumbnail(props: IntrinsicNodeProps & { item: Tile }) { return ( ); @@ -34,14 +36,14 @@ export interface TileRowProps extends IntrinsicNodeProps { export function TileRow(props: TileRowProps) { return ( - {(item) => } + {(item, index) => } ); } export function Button(props) { return ( - + {props.children} ); @@ -94,7 +96,7 @@ export function TitleRow(props: TileRowProps) { {props.title} - + {(item) => ( )} diff --git a/src/index.tsx b/src/index.tsx index 14c1a525..5fc30dd4 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -59,6 +59,7 @@ 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 LoginPage = lazy(() => import("./pages/Login")); let numImageWorkers = 3; const urlParams = new URLSearchParams(window.location.search); @@ -73,6 +74,7 @@ if (numWorkers) { const deviceLogicalPixelRatio = { "720": 0.666667, + "medium": 0.8, "1080": 1, "4k": 2, default: window.innerHeight / 1080 @@ -82,6 +84,7 @@ const logFps = true; Config.debug = false; // Config.keyDebug = true; Config.animationsEnabled = animationsEnabled === "true"; +Config.simpleAnimationsEnabled = true; Config.fontSettings.fontFamily = "Roboto"; Config.fontSettings.color = "#f6f6f6"; Config.fontSettings.fontSize = 32; @@ -147,6 +150,7 @@ render(() => ( + diff --git a/src/pages/App.tsx b/src/pages/App.tsx index ea07b692..de28a7ad 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -1,6 +1,5 @@ import { useFocusManager, - useAnnouncer, useMouse, } from "@lightningtv/solid/primitives"; import { KeyMap, KeyHoldMap } from "@lightningtv/core/focusManager"; diff --git a/src/pages/Browse.tsx b/src/pages/Browse.tsx index c75f827e..44fc3382 100644 --- a/src/pages/Browse.tsx +++ b/src/pages/Browse.tsx @@ -113,7 +113,7 @@ const Browse = (props) => { { {(items) => ( )} diff --git a/src/pages/Create.tsx b/src/pages/Create.tsx index 654fc2fa..940a3897 100644 --- a/src/pages/Create.tsx +++ b/src/pages/Create.tsx @@ -123,22 +123,11 @@ const CreatePage = () => { } const borderStyles = { - borderLeft: { - width: 8, + border: { + width: 0, color: 0x05b2b626 }, - borderTop: { - width: 8, - color: 0x25a2bd26 - }, - borderRight: { - width: 8, - color: 0x05b2b626 - }, - borderBottom: { - width: 8, - color: 0xc5b23626 - } + borderRadius: 32 } as const; const childTestPassedStyles = { @@ -165,6 +154,7 @@ const CreatePage = () => { } return ( + <> Title of the Page @@ -190,10 +180,16 @@ const CreatePage = () => { color={"#00ff00"} x={900} y={400} - alpha={hasFocus(myBox) ? 1 : 0.2} /> + + + ); }; diff --git a/src/pages/Entity.tsx b/src/pages/Entity.tsx index 589c89db..664e3c99 100644 --- a/src/pages/Entity.tsx +++ b/src/pages/Entity.tsx @@ -18,6 +18,7 @@ import * as player from "../video"; const Entity = (props) => { const [backdropAlpha, setBackdropAlpha] = createSignal(0); + const [playFocused, setPlayFocused] = createSignal(false); const navigate = useNavigate(); createEffect( @@ -84,9 +85,12 @@ const Entity = (props) => { */ return ( - entityActions.setFocus()} onEscape={onEscape}> + entityActions.setFocus()} onEscape={onEscape} + announce={[props.data.entity().heroContent.title, 'PAUSE-1', props.data.entity().heroContent.description]} + announceContext="Press LEFT or RIGHT to review items, press UP or DOWN to review categories, press CENTER to select"> { onDown={() => columnRef.setFocus()} onEnter={onEnterTrailer} > - @@ -121,6 +125,7 @@ const Entity = (props) => { @@ -128,6 +133,7 @@ const Entity = (props) => { Cast and Crew { function Block(props) { const styles = { - width: props.flexGrow ? undefined : 200, // Allow flexible width if flexGrow exists + width: props.flexGrow ? 0 : 200, // Allow flexible width if flexGrow exists height: 100, y: 5 }; diff --git a/src/pages/LeftNavWrapper.tsx b/src/pages/LeftNavWrapper.tsx index 22e4d66c..101ebc0c 100644 --- a/src/pages/LeftNavWrapper.tsx +++ b/src/pages/LeftNavWrapper.tsx @@ -26,9 +26,10 @@ declare module "@lightningtv/solid" { } const LeftNavWrapper = (props) => { - + const navigate = useNavigate(); const announcer = useAnnouncer(); + announcer.debug = true; announcer.enabled = false; let navDrawer, lastFocused; @@ -104,7 +105,6 @@ const LeftNavWrapper = (props) => { {lastKey()} - {lastError()} diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx new file mode 100644 index 00000000..a99d2721 --- /dev/null +++ b/src/pages/Login.tsx @@ -0,0 +1,253 @@ +import * as s from "solid-js"; +import * as lng from "@lightningtv/solid"; +import * as lngp from "@lightningtv/solid/primitives"; +import { setGlobalBackground } from "../state"; +import Input from "../components/Input"; +import {Keyboard} from "../components/Keyboard"; + +const LoginPage = () => { + const Title = { + fontSize: 42, + fontWeight: "bold", + } as const; + + const formats = { + uppercase: [ + [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "0", + { + title: "Delete", + size: "md", + keyId: "delete", + announce: "delete, button", + }, + ], + [ + "Q", + "W", + "E", + "R", + "T", + "Y", + "U", + "I", + "O", + "P", + { + title: "#@!", + size: "md", + toggle: "symbols", + announce: "symbol mode, button", + keyId: "symbols", + }, + ], + [ + "A", + "S", + "D", + "F", + "G", + "H", + "J", + "K", + "L", + "@", + { + title: "áöû", + size: "md", + toggle: "accents", + announce: "accents, button", + keyId: "accents", + }, + ], + [ + "Z", + "X", + "C", + "V", + "B", + "N", + "M", + { title: ".", announce: "period, button" }, + { title: "-", announce: "dash, button" }, + { title: "_", announce: "underscore, button" }, + { + title: "shift", + size: "md", + toggle: "default", + announce: "shift off, button", + keyId: "shift", + }, + ], + [ + { title: ".com", announce: "dot, com", size: "md" }, + { title: ".net", announce: "dot, net", size: "md" }, + { title: ".edu", announce: "dot, edu", size: "md" }, + { title: ".org", announce: "dot, org", size: "md" }, + { title: ".co", announce: "dot, co", size: "md" }, + { title: ".uk", announce: "dot, uk", size: "md" }, + ], + [ + { + title: "Clear", + size: "lg", + keyId: "clear", + announce: "clear, button", + }, + { + title: "Space", + size: "xl", + keyId: "space", + announce: "space, button", + }, + { + title: "Save", + size: "lg", + keyId: "save", + announce: "save, button", + }, + ], + ], + default: [ + [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "0", + { + title: "Delete", + size: "md", + keyId: "delete", + announce: "delete, button", + }, + ], + [ + "q", + "w", + "e", + "r", + "t", + "y", + "u", + "i", + "o", + "p", + { + title: "#@!", + size: "md", + toggle: "symbols", + announce: "symbol mode, button", + keyId: "symbols", + }, + ], + [ + "a", + "s", + "d", + "f", + "g", + "h", + "j", + "k", + "l", + "@", + ], + [ + "z", + "x", + "c", + "v", + "b", + "n", + "m", + { title: "_", announce: "underscore, button" }, + { title: ".", announce: "period, button" }, + { title: "-", announce: "dash, button" }, + { + title: "shift", + size: "md", + toggle: "uppercase", + announce: "shift on, button", + keyId: "shift", + }, + ], + [ + { title: ".com", announce: "dot, com", size: "md" }, + { title: ".net", announce: "dot, net", size: "md" }, + { title: ".edu", announce: "dot, edu", size: "md" }, + { title: ".org", announce: "dot, org", size: "md" }, + { title: ".co", announce: "dot, co", size: "md" }, + { title: ".uk", announce: "dot, uk", size: "md" }, + ], + [ + { + title: "Clear", + size: "lg", + keyId: "clear", + announce: "clear, button", + }, + { + title: "Space", + size: "xl", + keyId: "space", + announce: "space, button", + }, + { + title: "Save", + size: "lg", + keyId: "save", + announce: "save, button", + }, + ], + ], + }; + const keyEvent = s.createSignal(""); + const valueSignal = s.createSignal(""); + const [ _keyEvent, setKeyEvent ] = keyEvent; + + const onEnter: lng.KeyHandler = (_e, _keyboard, key) => { + if (typeof key.key === "string") { + setKeyEvent(key.key as string); + } + else if (typeof key.key === "object" && key.key && 'title' in key.key) { + if (key.key.title === 'save' || key.key.title === 'Save') { + console.log('perform save action', valueSignal[0]()); + return true; + } + setKeyEvent(key.key.title as string); + } + }; + + s.onMount(() => { + setGlobalBackground("#000000"); + }); + + return ( + + + + Username + + + + + + ); +}; + +export default LoginPage; diff --git a/src/pages/Loops.tsx b/src/pages/Loops.tsx index 4106f7e8..c45bb3cf 100644 --- a/src/pages/Loops.tsx +++ b/src/pages/Loops.tsx @@ -1,6 +1,6 @@ -import { createEffect, on, createSignal, For, Index } from "solid-js"; -import { ElementNode, View, Text } from "@lightningtv/solid"; -import { LazyUp, Column, Row } from "@lightningtv/solid/primitives"; +import { createSignal, For, Index } from "solid-js"; +import { View, Text } from "@lightningtv/solid"; +import { LazyRow, Column, Row } from "@lightningtv/solid/primitives"; import { List } from "@solid-primitives/list"; import { Poster } from "../components"; import { setGlobalBackground } from "../state"; @@ -103,11 +103,9 @@ const Loops = (props) => { - LazyUp Loop + Lazy Row Loop - { y={50} > {(item, index) => } - + diff --git a/src/pages/Portal.tsx b/src/pages/Portal.tsx index 8c3634ce..7f3a0b01 100644 --- a/src/pages/Portal.tsx +++ b/src/pages/Portal.tsx @@ -2,6 +2,7 @@ import { createSignal, createSelector, For, children, createEffect } from "solid import { ElementNode, View, Text, assertTruthy } from "@lightningtv/solid"; import { Column, Row, useFocusStack } from "@lightningtv/solid/primitives"; import { useNavigate } from "@solidjs/router"; +import { Announcer } from "@lightningtv/solid/primitives"; import styles from "../styles"; const Portal = (props) => { @@ -103,6 +104,11 @@ const Portal = (props) => { id: "examples/tmdb", description: "TMDB Example" }, + { + title: "Login and Forms", + id: "login", + description: "Login with forms Example" + }, { title: "Grid Primitive for Layout", id: "tmdbgrid", @@ -220,6 +226,10 @@ const Portal = (props) => { } }); + setTimeout(() => { + Announcer.speak('Welcome to the examples'); + }, 1000); + return ( <>