From 058ac24954e4f2aace79450eece1b494f7a74f97 Mon Sep 17 00:00:00 2001 From: AquaWolf <3daquawolf@gmail.com> Date: Fri, 28 Feb 2025 21:30:23 +0100 Subject: [PATCH 1/9] added first draft of the play route --- packages/www/package.json | 3 +- packages/www/src/App.tsx | 2 + packages/www/src/index.tsx | 10 ++- packages/www/src/pages/play.tsx | 149 ++++++++++++++++++++++++++++++++ 4 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 packages/www/src/pages/play.tsx diff --git a/packages/www/package.json b/packages/www/package.json index 96c96968..fe20260c 100644 --- a/packages/www/package.json +++ b/packages/www/package.json @@ -27,6 +27,7 @@ "@solid-primitives/storage": "^4.3.1", "@solidjs/router": "^0.15.3", "modern-normalize": "^3.0.1", - "solid-js": "^1.9.5" + "solid-js": "^1.9.5", + "solid-notifications": "^1.1.2" } } \ No newline at end of file diff --git a/packages/www/src/App.tsx b/packages/www/src/App.tsx index efd3487d..59e032ad 100644 --- a/packages/www/src/App.tsx +++ b/packages/www/src/App.tsx @@ -7,6 +7,7 @@ import '@fontsource/geist-sans/700.css'; import '@fontsource/geist-sans/800.css'; import '@fontsource/geist-sans/900.css'; import { TeamCreate } from './pages/new'; +import { PlayComponent } from './pages/play'; import { styled } from "@macaron-css/solid"; import { useStorage } from './providers/account'; import { darkClass, lightClass, theme } from './ui/theme'; @@ -116,6 +117,7 @@ export const App: Component = () => { {WorkspaceRoute} */} + { diff --git a/packages/www/src/index.tsx b/packages/www/src/index.tsx index 608b9165..0bada0f9 100644 --- a/packages/www/src/index.tsx +++ b/packages/www/src/index.tsx @@ -8,6 +8,7 @@ import { render } from "solid-js/web"; import "modern-normalize/modern-normalize.css"; import { App } from "./App"; import { StorageProvider } from "./providers/account"; +import { ToastProvider, Toaster } from "solid-notifications"; const root = document.getElementById("root"); @@ -19,9 +20,12 @@ if (import.meta.env.DEV && !(root instanceof HTMLElement)) { render( () => ( - - - + + + + + + ), root! ); \ No newline at end of file diff --git a/packages/www/src/pages/play.tsx b/packages/www/src/pages/play.tsx new file mode 100644 index 00000000..669f97fe --- /dev/null +++ b/packages/www/src/pages/play.tsx @@ -0,0 +1,149 @@ +import { Text } from "@nestri/www/ui/text"; +import { createSignal, createEffect, onCleanup, onMount } from "solid-js"; +import { useParams } from "@solidjs/router"; +import { Modal } from "@nestri/ui"; +import { Keyboard, Mouse, WebRTCStream } from "@nestri/input"; + +export function PlayComponent() { + const params = useParams(); + const id = params.id; + + const [showBannerModal, setShowBannerModal] = createSignal(false); + const [showButtonModal, setShowButtonModal] = createSignal(false); + const [gamepadConnected, setGamepadConnected] = createSignal(false); + const [buttonPressed, setButtonPressed] = createSignal(null); + const [leftStickX, setLeftStickX] = createSignal(0); + const [leftStickY, setLeftStickY] = createSignal(0); + const [hasStream, setHasStream] = createSignal(false); + const [showOffline, setShowOffline] = createSignal(false); + + const [canvas, setCanvas] = createSignal(undefined); + let video: HTMLVideoElement; + let webrtc: WebRTCStream; + let nestriMouse: Mouse , nestriKeyboard: Keyboard; + + const initializeInputDevices = () => { + const canvasElement = canvas(); + if (!canvasElement || !webrtc) return; + try { + nestriMouse = new Mouse({ canvas: canvasElement, webrtc }); + nestriKeyboard = new Keyboard({ canvas: canvasElement, webrtc }); + console.log("Input devices initialized successfully"); + } catch (error) { + console.error("Failed to initialize input devices:", error); + } + }; + + /*const initializeGamepad = () => { + console.log("Initializing gamepad..."); + + const updateGamepadState = () => { + const gamepads = navigator.getGamepads(); + const gamepad = gamepads[0]; + if (gamepad) { + setButtonPressed(gamepad.buttons.findIndex(btn => btn.pressed) !== -1 ? "Button pressed" : null); + setLeftStickX(Number(gamepad.axes[0].toFixed(2))); + setLeftStickY(Number(gamepad.axes[1].toFixed(2))); + } + requestAnimationFrame(updateGamepadState); + }; + + window.addEventListener("gamepadconnected", () => { + setGamepadConnected(true); + console.log("Gamepad connected!"); + updateGamepadState(); + }); + + window.addEventListener("gamepaddisconnected", () => { + setGamepadConnected(false); + console.log("Gamepad disconnected!"); + }); + };*/ + + const lockPlay = async () => { + const canvasElement = canvas(); + if (!canvasElement || !hasStream()) return; + try { + await canvasElement.requestPointerLock(); + await canvasElement.requestFullscreen(); + //initializeGamepad(); + } catch (error) { + console.error("Error during lock sequence:", error); + } + }; + + const setupPointerLockListener = () => { + document.addEventListener("pointerlockchange", () => { + const canvasElement = canvas(); + if (!canvasElement) return; + if (document.pointerLockElement === canvasElement) { + initializeInputDevices(); + } else { + nestriKeyboard?.dispose(); + nestriMouse?.dispose(); + } + }); + }; + + const handleVideoInput = async () => { + const canvasElement = canvas(); + if (!video || !canvasElement) return; + + try { + + await video.play(); + if (canvasElement && video) { + canvasElement.width = video.videoWidth; + canvasElement.height = video.videoHeight; + + + const ctx = canvasElement.getContext("2d"); + const renderer = () => { + if (ctx && hasStream() && video) { + ctx.drawImage(video, 0, 0); + video.requestVideoFrameCallback(renderer); + } + }; + + video.requestVideoFrameCallback(renderer); + } + } catch (error) { + console.error("Error playing video:", error); + } + }; + + + onMount(() => { + setupPointerLockListener(); + video = document.createElement("video"); + video.style.visibility = "hidden"; + webrtc = new WebRTCStream("https://relay.dathorse.com", id, async (mediaStream) => { + if (video && mediaStream) { + video.srcObject = mediaStream; + setHasStream(true); + setShowOffline(false); + await handleVideoInput(); + } else { + setShowOffline(true); + setHasStream(false); + } + }); + }); + + onCleanup(() => { + nestriKeyboard?.dispose(); + nestriMouse?.dispose(); + }); + + return ( + <> + {showOffline() ? ( + + Offline + + ) : ( + + )} + > + ); + } \ No newline at end of file From 90e0533fdd384c5ba4392cc5f72de99a915a1eb6 Mon Sep 17 00:00:00 2001 From: AquaWolf <3daquawolf@gmail.com> Date: Fri, 28 Feb 2025 21:47:46 +0100 Subject: [PATCH 2/9] right parameter --- packages/www/src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/www/src/App.tsx b/packages/www/src/App.tsx index 59e032ad..965e7da9 100644 --- a/packages/www/src/App.tsx +++ b/packages/www/src/App.tsx @@ -117,7 +117,7 @@ export const App: Component = () => { {WorkspaceRoute} */} - + { From 805a8a611571c3bb7dfea9836f6d9b4bfd29d654 Mon Sep 17 00:00:00 2001 From: AquaWolf <3daquawolf@gmail.com> Date: Mon, 3 Mar 2025 17:39:38 +0100 Subject: [PATCH 3/9] some changes to the play route --- packages/www/src/pages/play.tsx | 106 ++++++++++++++++++++++++++++++-- 1 file changed, 101 insertions(+), 5 deletions(-) diff --git a/packages/www/src/pages/play.tsx b/packages/www/src/pages/play.tsx index 669f97fe..8c9dbb25 100644 --- a/packages/www/src/pages/play.tsx +++ b/packages/www/src/pages/play.tsx @@ -1,8 +1,8 @@ import { Text } from "@nestri/www/ui/text"; -import { createSignal, createEffect, onCleanup, onMount } from "solid-js"; +import { createSignal, createEffect, onCleanup, onMount, Show } from "solid-js"; import { useParams } from "@solidjs/router"; -import { Modal } from "@nestri/ui"; import { Keyboard, Mouse, WebRTCStream } from "@nestri/input"; +import { Container, FullScreen } from "@nestri/www/ui/layout"; export function PlayComponent() { const params = useParams(); @@ -15,6 +15,7 @@ export function PlayComponent() { const [leftStickX, setLeftStickX] = createSignal(0); const [leftStickY, setLeftStickY] = createSignal(0); const [hasStream, setHasStream] = createSignal(false); + const [nestriLock, setNestriLock] = createSignal(false); const [showOffline, setShowOffline] = createSignal(false); const [canvas, setCanvas] = createSignal(undefined); @@ -67,6 +68,25 @@ export function PlayComponent() { await canvasElement.requestPointerLock(); await canvasElement.requestFullscreen(); //initializeGamepad(); + + if (document.fullscreenElement !== null) { + if ('keyboard' in navigator && 'lock' in (navigator.keyboard as any)) { + const keys = [ + "AltLeft", "AltRight", "Tab", "Escape", + "ContextMenu", "MetaLeft", "MetaRight" + ]; + + try { + await (navigator.keyboard as any).lock(keys); + setNestriLock(true); + console.log("Keyboard lock acquired"); + } catch (e) { + console.warn("Keyboard lock failed:", e); + setNestriLock(false); + } + } + } + } catch (error) { console.error("Error during lock sequence:", error); } @@ -79,6 +99,15 @@ export function PlayComponent() { if (document.pointerLockElement === canvasElement) { initializeInputDevices(); } else { + + if (!showBannerModal) { + const playing = sessionStorage.getItem("showedBanner"); + setShowBannerModal(!playing || playing !== "true"); + setShowButtonModal(playing === "false"); + } + + + nestriKeyboard?.dispose(); nestriMouse?.dispose(); } @@ -114,6 +143,9 @@ export function PlayComponent() { onMount(() => { + const canvasElement = canvas(); + if(!canvasElement) return; + setupPointerLockListener(); video = document.createElement("video"); video.style.visibility = "hidden"; @@ -123,9 +155,17 @@ export function PlayComponent() { setHasStream(true); setShowOffline(false); await handleVideoInput(); - } else { + } else if (mediaStream === null) { + console.log("MediaStream is null, Room is offline"); setShowOffline(true); setHasStream(false); + + const ctx = canvasElement.getContext("2d"); + if (ctx) ctx.clearRect(0, 0, canvasElement.width, canvasElement.height); + } else if (video && video.srcObject !== null) { + setHasStream(true); + setShowOffline(true); + await handleVideoInput(); } }); }); @@ -136,14 +176,70 @@ export function PlayComponent() { }); return ( - <> + {showOffline() ? ( Offline + setShowButtonModal(true)}>Show Modal ) : ( )} - > + + + + ); + } + + interface ModalProps { + show: () => boolean; + setShow: (value: boolean) => void; + closeOnBackdropClick?: boolean; + handleVideoInput?: () => Promise; + lockPlay?: () => Promise; + } + + function Modal(props: ModalProps) { + return ( + + props.closeOnBackdropClick && props.setShow(false)} + > + e.stopPropagation()} // Prevent closing when clicking inside modal + > + + + { + props.setShow(false); + sessionStorage.setItem("showedBanner", "true"); + await props.handleVideoInput?.(); + await props.lockPlay?.(); + }} + > + Continue Playing + + + Shutdown Nestri + + + + + + ); } \ No newline at end of file From a727a9b710a629c952fed7a67b6719b8c809b2fc Mon Sep 17 00:00:00 2001 From: AquaWolf <3daquawolf@gmail.com> Date: Mon, 3 Mar 2025 21:07:13 +0100 Subject: [PATCH 4/9] some wip with styles --- packages/www/src/pages/play.tsx | 95 ++++++++++++++++++++++++++------- packages/www/src/ui/theme.ts | 9 +++- 2 files changed, 83 insertions(+), 21 deletions(-) diff --git a/packages/www/src/pages/play.tsx b/packages/www/src/pages/play.tsx index 8c9dbb25..5bc4712e 100644 --- a/packages/www/src/pages/play.tsx +++ b/packages/www/src/pages/play.tsx @@ -1,9 +1,36 @@ import { Text } from "@nestri/www/ui/text"; -import { createSignal, createEffect, onCleanup, onMount, Show } from "solid-js"; +import { createSignal, createEffect, onCleanup, onMount, Show} from "solid-js"; +import { Portal } from "solid-js/web"; import { useParams } from "@solidjs/router"; import { Keyboard, Mouse, WebRTCStream } from "@nestri/input"; import { Container, FullScreen } from "@nestri/www/ui/layout"; +import { styled } from "@macaron-css/solid"; +import { theme } from "../ui/theme"; +const Canvas = styled("canvas", { + base: { + aspectRatio: 16 / 9, + width: "100%", + height: "100%", + objectFit: "contain", + maxHeight: "100vh", + }}); + + const ModalContainer = styled("div", { + base: { + width: "100%", + maxWidth: 370, + maxHeight: "75vh", + borderRadius: 12, + borderWidth: 1, + borderStyle: "solid", + borderColor: theme.color.gray.d400, + backgroundColor: theme.color.pink.d400, + boxShadow: theme.color.boxShadow, + backdropFilter: "blur(20px)", + padding: "20px 25px" + } + }) export function PlayComponent() { const params = useParams(); const id = params.id; @@ -149,7 +176,7 @@ export function PlayComponent() { setupPointerLockListener(); video = document.createElement("video"); video.style.visibility = "hidden"; - webrtc = new WebRTCStream("https://relay.dathorse.com", id, async (mediaStream) => { + webrtc = new WebRTCStream("http://192.168.1.200:8088", id, async (mediaStream) => { if (video && mediaStream) { video.srcObject = mediaStream; setHasStream(true); @@ -175,15 +202,23 @@ export function PlayComponent() { nestriMouse?.dispose(); }); + const { Modal, openModal } = createModal(); + return ( - + <> + + open modal + + + > + /* {showOffline() ? ( Offline setShowButtonModal(true)}>Show Modal ) : ( - + )} - + */ ); } @@ -203,19 +238,43 @@ export function PlayComponent() { lockPlay?: () => Promise; } + function createModal() { + const [open, setOpen] = createSignal(false); + + return { + openModal() { + setOpen(true); + }, + Modal() { + return ( + + + + + Hello from modal + setOpen(false)}>close modal + + + + + ); + }, + }; + } + function Modal(props: ModalProps) { return ( - - props.closeOnBackdropClick && props.setShow(false)} - > - e.stopPropagation()} // Prevent closing when clicking inside modal > @@ -238,8 +297,6 @@ export function PlayComponent() { - - - + ); } \ No newline at end of file diff --git a/packages/www/src/ui/theme.ts b/packages/www/src/ui/theme.ts index 31c223b1..c51974ef 100644 --- a/packages/www/src/ui/theme.ts +++ b/packages/www/src/ui/theme.ts @@ -233,6 +233,7 @@ const light = (() => { }, }; + const boxShadow = "0 0 0 1px rgba(19,21,23,0.08), 0 3.3px 2.7px rgba(0,0,0,.03),0 8.3px 6.9px rgba(0,0,0,.04),0 17px 14.2px rgba(0,0,0,.05),0 35px 29.2px rgba(0,0,0,.06),0px -4px 4px 0px rgba(0,0,0,.07) inset"; return { gray, blue, @@ -248,7 +249,8 @@ const light = (() => { focusBorder, focusColor, d1000, - text + text, + boxShadow }; })() @@ -391,6 +393,8 @@ const dark = (() => { }, }; + const boxShadow = "0 0 0 1px rgba(255,255,255,0.08), 0 3.3px 2.7px rgba(0,0,0,.1),0 8.3px 6.9px rgba(0,0,0,.13),0 17px 14.2px rgba(0,0,0,.17),0 35px 29.2px rgba(0,0,0,.22),0px -4px 4px 0px rgba(0,0,0,.04) inset"; + return { gray, blue, @@ -406,7 +410,8 @@ const dark = (() => { focusBorder, focusColor, d1000, - text + text, + boxShadow }; })() From c994dc112c86abc3e661bcecbe0e3cf13a8be717 Mon Sep 17 00:00:00 2001 From: AquaWolf <3daquawolf@gmail.com> Date: Mon, 3 Mar 2025 21:16:27 +0100 Subject: [PATCH 5/9] added new App changes and background for theme --- packages/www/src/App.tsx | 29 ++--------------------------- packages/www/src/ui/theme.ts | 9 +++++---- 2 files changed, 7 insertions(+), 31 deletions(-) diff --git a/packages/www/src/App.tsx b/packages/www/src/App.tsx index 965e7da9..aad73ca6 100644 --- a/packages/www/src/App.tsx +++ b/packages/www/src/App.tsx @@ -6,7 +6,6 @@ import '@fontsource/geist-sans/600.css'; import '@fontsource/geist-sans/700.css'; import '@fontsource/geist-sans/800.css'; import '@fontsource/geist-sans/900.css'; -import { TeamCreate } from './pages/new'; import { PlayComponent } from './pages/play'; import { styled } from "@macaron-css/solid"; import { useStorage } from './providers/account'; @@ -35,10 +34,10 @@ globalStyle("html", { // Hardcode colors "@media": { "(prefers-color-scheme: light)": { - backgroundColor: "hsla(0,0%,98%)", + backgroundColor: "#f5f5f5", }, "(prefers-color-scheme: dark)": { - backgroundColor: "hsla(0,0%,0%)", + backgroundColor: "#1e1e1e", }, }, }); @@ -91,32 +90,8 @@ export const App: Component = () => { {props.children} - // - // - // - // - // - // - // - // - // - // {props.children} - // - // - // - // - // - // - // - // )} > - {/* - - - - {WorkspaceRoute} */} - { } const background = { - d100: 'hsla(0,0%,100%)', - d200: 'hsla(0,0%,98%)' + d100: '#f5f5f5', + d200: 'oklch(from #f5f5f5 calc(l + (-0.06 * clamp(0, calc((l - 0.714) * 1000), 1) + 0.03)) c h)' }; const contrastFg = '#ffffff'; @@ -371,9 +371,10 @@ const dark = (() => { } const background = { - d100: 'hsla(0,0%,4%)', - d200: 'hsla(0,0%,0%)' + d200: '#171717', + d100: "oklch(from #171717 calc(l + (-0.06 * clamp(0, calc((l - 0.714) * 1000), 1) + 0.03)) c h)" }; + const contrastFg = '#ffffff'; const focusBorder = `0 0 0 1px ${grayAlpha.d600}, 0px 0px 0px 4px rgba(255,255,255,0.24)`; const focusColor = blue.d900 From 1e782385934ec684a1a84a2c1bc1eec72eab16c1 Mon Sep 17 00:00:00 2001 From: Wanjohi Date: Mon, 3 Mar 2025 23:36:18 +0300 Subject: [PATCH 6/9] fix: Colors --- packages/www/src/App.tsx | 9 +- packages/www/src/index.tsx | 8 +- packages/www/src/pages/play.tsx | 542 +++++++++++++++++--------------- packages/www/src/pages/test.tsx | 18 ++ 4 files changed, 309 insertions(+), 268 deletions(-) create mode 100644 packages/www/src/pages/test.tsx diff --git a/packages/www/src/App.tsx b/packages/www/src/App.tsx index aad73ca6..766feb33 100644 --- a/packages/www/src/App.tsx +++ b/packages/www/src/App.tsx @@ -14,6 +14,7 @@ import { AuthProvider, useAuth } from './providers/auth'; import { Navigate, Route, Router } from "@solidjs/router"; import { globalStyle, macaron$ } from "@macaron-css/core"; import { Component, createSignal, Match, onCleanup, Switch } from 'solid-js'; +import TestComponent from './pages/test'; const Root = styled("div", { base: { @@ -87,12 +88,14 @@ export const App: Component = () => { ( - - {props.children} - + // + // {props.children} + props.children + // )} > + { diff --git a/packages/www/src/index.tsx b/packages/www/src/index.tsx index 0bada0f9..80a66cb8 100644 --- a/packages/www/src/index.tsx +++ b/packages/www/src/index.tsx @@ -8,7 +8,7 @@ import { render } from "solid-js/web"; import "modern-normalize/modern-normalize.css"; import { App } from "./App"; import { StorageProvider } from "./providers/account"; -import { ToastProvider, Toaster } from "solid-notifications"; +// import { ToastProvider, Toaster } from "solid-notifications"; const root = document.getElementById("root"); @@ -20,12 +20,12 @@ if (import.meta.env.DEV && !(root instanceof HTMLElement)) { render( () => ( - - + // + // - + // ), root! ); \ No newline at end of file diff --git a/packages/www/src/pages/play.tsx b/packages/www/src/pages/play.tsx index 5bc4712e..66f7bc67 100644 --- a/packages/www/src/pages/play.tsx +++ b/packages/www/src/pages/play.tsx @@ -1,11 +1,11 @@ import { Text } from "@nestri/www/ui/text"; -import { createSignal, createEffect, onCleanup, onMount, Show} from "solid-js"; +import { createSignal, createEffect, onCleanup, onMount, Show } from "solid-js"; import { Portal } from "solid-js/web"; import { useParams } from "@solidjs/router"; import { Keyboard, Mouse, WebRTCStream } from "@nestri/input"; import { Container, FullScreen } from "@nestri/www/ui/layout"; import { styled } from "@macaron-css/solid"; -import { theme } from "../ui/theme"; +import { lightClass, theme, darkClass } from "@nestri/www/ui/theme"; const Canvas = styled("canvas", { base: { @@ -14,289 +14,309 @@ const Canvas = styled("canvas", { height: "100%", objectFit: "contain", maxHeight: "100vh", - }}); - - const ModalContainer = styled("div", { - base: { - width: "100%", - maxWidth: 370, - maxHeight: "75vh", - borderRadius: 12, - borderWidth: 1, - borderStyle: "solid", - borderColor: theme.color.gray.d400, - backgroundColor: theme.color.pink.d400, - boxShadow: theme.color.boxShadow, - backdropFilter: "blur(20px)", - padding: "20px 25px" - } - }) + } +}); + +const ModalContainer = styled("div", { + base: { + width: "100%", + maxWidth: 370, + maxHeight: "75vh", + height: "auto", + // borderRadius: 12, + // borderWidth: 1, + // borderStyle: "solid", + // borderColor: theme.color.gray.d400, + // backgroundColor: theme.color.pink.d400, + backgroundColor: theme.color.red.d300, + // boxShadow: theme.color.boxShadow, + // backdropFilter: "blur(20px)", + padding: "20px 25px" + } +}) + export function PlayComponent() { - const params = useParams(); - const id = params.id; - - const [showBannerModal, setShowBannerModal] = createSignal(false); - const [showButtonModal, setShowButtonModal] = createSignal(false); - const [gamepadConnected, setGamepadConnected] = createSignal(false); - const [buttonPressed, setButtonPressed] = createSignal(null); - const [leftStickX, setLeftStickX] = createSignal(0); - const [leftStickY, setLeftStickY] = createSignal(0); - const [hasStream, setHasStream] = createSignal(false); - const [nestriLock, setNestriLock] = createSignal(false); - const [showOffline, setShowOffline] = createSignal(false); + const params = useParams(); + const id = params.id; + + const [showBannerModal, setShowBannerModal] = createSignal(false); + const [showButtonModal, setShowButtonModal] = createSignal(false); + const [gamepadConnected, setGamepadConnected] = createSignal(false); + const [buttonPressed, setButtonPressed] = createSignal(null); + const [leftStickX, setLeftStickX] = createSignal(0); + const [leftStickY, setLeftStickY] = createSignal(0); + const [hasStream, setHasStream] = createSignal(false); + const [nestriLock, setNestriLock] = createSignal(false); + const [showOffline, setShowOffline] = createSignal(false); + + const [canvas, setCanvas] = createSignal(undefined); + let video: HTMLVideoElement; + let webrtc: WebRTCStream; + let nestriMouse: Mouse, nestriKeyboard: Keyboard; + + const initializeInputDevices = () => { + const canvasElement = canvas(); + if (!canvasElement || !webrtc) return; + try { + nestriMouse = new Mouse({ canvas: canvasElement, webrtc }); + nestriKeyboard = new Keyboard({ canvas: canvasElement, webrtc }); + console.log("Input devices initialized successfully"); + } catch (error) { + console.error("Failed to initialize input devices:", error); + } + }; + + /*const initializeGamepad = () => { + console.log("Initializing gamepad..."); - const [canvas, setCanvas] = createSignal(undefined); - let video: HTMLVideoElement; - let webrtc: WebRTCStream; - let nestriMouse: Mouse , nestriKeyboard: Keyboard; - - const initializeInputDevices = () => { - const canvasElement = canvas(); - if (!canvasElement || !webrtc) return; - try { - nestriMouse = new Mouse({ canvas: canvasElement, webrtc }); - nestriKeyboard = new Keyboard({ canvas: canvasElement, webrtc }); - console.log("Input devices initialized successfully"); - } catch (error) { - console.error("Failed to initialize input devices:", error); - } + const updateGamepadState = () => { + const gamepads = navigator.getGamepads(); + const gamepad = gamepads[0]; + if (gamepad) { + setButtonPressed(gamepad.buttons.findIndex(btn => btn.pressed) !== -1 ? "Button pressed" : null); + setLeftStickX(Number(gamepad.axes[0].toFixed(2))); + setLeftStickY(Number(gamepad.axes[1].toFixed(2))); + } + requestAnimationFrame(updateGamepadState); }; - - /*const initializeGamepad = () => { - console.log("Initializing gamepad..."); - - const updateGamepadState = () => { - const gamepads = navigator.getGamepads(); - const gamepad = gamepads[0]; - if (gamepad) { - setButtonPressed(gamepad.buttons.findIndex(btn => btn.pressed) !== -1 ? "Button pressed" : null); - setLeftStickX(Number(gamepad.axes[0].toFixed(2))); - setLeftStickY(Number(gamepad.axes[1].toFixed(2))); - } - requestAnimationFrame(updateGamepadState); - }; - - window.addEventListener("gamepadconnected", () => { - setGamepadConnected(true); - console.log("Gamepad connected!"); - updateGamepadState(); - }); - - window.addEventListener("gamepaddisconnected", () => { - setGamepadConnected(false); - console.log("Gamepad disconnected!"); - }); - };*/ - - const lockPlay = async () => { - const canvasElement = canvas(); - if (!canvasElement || !hasStream()) return; - try { - await canvasElement.requestPointerLock(); - await canvasElement.requestFullscreen(); - //initializeGamepad(); - - if (document.fullscreenElement !== null) { - if ('keyboard' in navigator && 'lock' in (navigator.keyboard as any)) { - const keys = [ - "AltLeft", "AltRight", "Tab", "Escape", - "ContextMenu", "MetaLeft", "MetaRight" - ]; - - try { - await (navigator.keyboard as any).lock(keys); - setNestriLock(true); - console.log("Keyboard lock acquired"); - } catch (e) { - console.warn("Keyboard lock failed:", e); - setNestriLock(false); - } - } - } - - } catch (error) { - console.error("Error during lock sequence:", error); - } - }; - - const setupPointerLockListener = () => { - document.addEventListener("pointerlockchange", () => { - const canvasElement = canvas(); - if (!canvasElement) return; - if (document.pointerLockElement === canvasElement) { - initializeInputDevices(); - } else { - - if (!showBannerModal) { - const playing = sessionStorage.getItem("showedBanner"); - setShowBannerModal(!playing || playing !== "true"); - setShowButtonModal(playing === "false"); - } + + window.addEventListener("gamepadconnected", () => { + setGamepadConnected(true); + console.log("Gamepad connected!"); + updateGamepadState(); + }); + + window.addEventListener("gamepaddisconnected", () => { + setGamepadConnected(false); + console.log("Gamepad disconnected!"); + }); + };*/ - + const lockPlay = async () => { + const canvasElement = canvas(); + if (!canvasElement || !hasStream()) return; + try { + await canvasElement.requestPointerLock(); + await canvasElement.requestFullscreen(); + //initializeGamepad(); - nestriKeyboard?.dispose(); - nestriMouse?.dispose(); - } - }); - }; + if (document.fullscreenElement !== null) { + if ('keyboard' in navigator && 'lock' in (navigator.keyboard as any)) { + const keys = [ + "AltLeft", "AltRight", "Tab", "Escape", + "ContextMenu", "MetaLeft", "MetaRight" + ]; - const handleVideoInput = async () => { - const canvasElement = canvas(); - if (!video || !canvasElement) return; - - try { - - await video.play(); - if (canvasElement && video) { - canvasElement.width = video.videoWidth; - canvasElement.height = video.videoHeight; - - - const ctx = canvasElement.getContext("2d"); - const renderer = () => { - if (ctx && hasStream() && video) { - ctx.drawImage(video, 0, 0); - video.requestVideoFrameCallback(renderer); - } - }; - - video.requestVideoFrameCallback(renderer); - } - } catch (error) { - console.error("Error playing video:", error); + try { + await (navigator.keyboard as any).lock(keys); + setNestriLock(true); + console.log("Keyboard lock acquired"); + } catch (e) { + console.warn("Keyboard lock failed:", e); + setNestriLock(false); + } } - }; - - - onMount(() => { + } + + } catch (error) { + console.error("Error during lock sequence:", error); + } + }; + + const setupPointerLockListener = () => { + document.addEventListener("pointerlockchange", () => { const canvasElement = canvas(); - if(!canvasElement) return; - - setupPointerLockListener(); - video = document.createElement("video"); - video.style.visibility = "hidden"; - webrtc = new WebRTCStream("http://192.168.1.200:8088", id, async (mediaStream) => { - if (video && mediaStream) { - video.srcObject = mediaStream; - setHasStream(true); - setShowOffline(false); - await handleVideoInput(); - } else if (mediaStream === null) { - console.log("MediaStream is null, Room is offline"); - setShowOffline(true); - setHasStream(false); - - const ctx = canvasElement.getContext("2d"); - if (ctx) ctx.clearRect(0, 0, canvasElement.width, canvasElement.height); - } else if (video && video.srcObject !== null) { - setHasStream(true); - setShowOffline(true); - await handleVideoInput(); + if (!canvasElement) return; + if (document.pointerLockElement === canvasElement) { + initializeInputDevices(); + } else { + + if (!showBannerModal) { + const playing = sessionStorage.getItem("showedBanner"); + setShowBannerModal(!playing || playing !== "true"); + setShowButtonModal(playing === "false"); } - }); + + + + nestriKeyboard?.dispose(); + nestriMouse?.dispose(); + } }); - - onCleanup(() => { - nestriKeyboard?.dispose(); - nestriMouse?.dispose(); + }; + + const handleVideoInput = async () => { + const canvasElement = canvas(); + if (!video || !canvasElement) return; + + try { + + await video.play(); + if (canvasElement && video) { + canvasElement.width = video.videoWidth; + canvasElement.height = video.videoHeight; + + + const ctx = canvasElement.getContext("2d"); + const renderer = () => { + if (ctx && hasStream() && video) { + ctx.drawImage(video, 0, 0); + video.requestVideoFrameCallback(renderer); + } + }; + + video.requestVideoFrameCallback(renderer); + } + } catch (error) { + console.error("Error playing video:", error); + } + }; + + + onMount(() => { + const canvasElement = canvas(); + if (!canvasElement) return; + + setupPointerLockListener(); + video = document.createElement("video"); + video.style.visibility = "hidden"; + webrtc = new WebRTCStream("http://192.168.1.200:8088", id, async (mediaStream) => { + if (video && mediaStream) { + video.srcObject = mediaStream; + setHasStream(true); + setShowOffline(false); + await handleVideoInput(); + } else if (mediaStream === null) { + console.log("MediaStream is null, Room is offline"); + setShowOffline(true); + setHasStream(false); + + const ctx = canvasElement.getContext("2d"); + if (ctx) ctx.clearRect(0, 0, canvasElement.width, canvasElement.height); + } else if (video && video.srcObject !== null) { + setHasStream(true); + setShowOffline(true); + await handleVideoInput(); + } }); - - const { Modal, openModal } = createModal(); + }); - return ( - <> + onCleanup(() => { + nestriKeyboard?.dispose(); + nestriMouse?.dispose(); + }); + + const { Modal, openModal } = createModal(); + + return ( + <> open modal > - /* - {showOffline() ? ( - - Offline - setShowButtonModal(true)}>Show Modal - - ) : ( - - )} - - - */ - ); - } + /* + {showOffline() ? ( + + Offline + setShowButtonModal(true)}>Show Modal + + ) : ( + + )} + + + */ + ); +} + +interface ModalProps { + show: () => boolean; + setShow: (value: boolean) => void; + closeOnBackdropClick?: boolean; + handleVideoInput?: () => Promise; + lockPlay?: () => Promise; +} + +function createModal() { + const [open, setOpen] = createSignal(false); + const [theme, setTheme] = createSignal( + window.matchMedia("(prefers-color-scheme: dark)").matches + ? "dark" + : "light", + ); + + const darkMode = window.matchMedia("(prefers-color-scheme: dark)"); + const setColorScheme = (e: MediaQueryListEvent) => { + setTheme(e.matches ? "dark" : "light"); + }; + darkMode.addEventListener("change", setColorScheme); + onCleanup(() => { + darkMode.removeEventListener("change", setColorScheme); + }); - interface ModalProps { - show: () => boolean; - setShow: (value: boolean) => void; - closeOnBackdropClick?: boolean; - handleVideoInput?: () => Promise; - lockPlay?: () => Promise; - } - function createModal() { - const [open, setOpen] = createSignal(false); - - return { - openModal() { - setOpen(true); - }, - Modal() { - return ( - - - + + - - Hello from modal - setOpen(false)}>close modal - - - - - ); - }, - }; - } + > + + Hello from modal + setOpen(false)}>close modal + + + + + ); + }, + }; +} + +function Modal(props: ModalProps) { + return ( - function Modal(props: ModalProps) { - return ( - - - e.stopPropagation()} // Prevent closing when clicking inside modal + + e.stopPropagation()} // Prevent closing when clicking inside modal + > + + + { + props.setShow(false); + sessionStorage.setItem("showedBanner", "true"); + await props.handleVideoInput?.(); + await props.lockPlay?.(); + }} > - - - { - props.setShow(false); - sessionStorage.setItem("showedBanner", "true"); - await props.handleVideoInput?.(); - await props.lockPlay?.(); - }} - > - Continue Playing - - - Shutdown Nestri - - - - - ); - } \ No newline at end of file + Continue Playing + + + Shutdown Nestri + + + + + ); +} \ No newline at end of file diff --git a/packages/www/src/pages/test.tsx b/packages/www/src/pages/test.tsx new file mode 100644 index 00000000..f6c7c4bb --- /dev/null +++ b/packages/www/src/pages/test.tsx @@ -0,0 +1,18 @@ +import { styled } from "@macaron-css/solid"; +import { theme } from "../ui/theme"; + + +const Testing = styled("div", { + base: { + height: "100%", + width: "100%", + position: "fixed", + backgroundColor: theme.color.blue.d600 + } +}) + +export default function TestComponent() { + return ( + + ) +} \ No newline at end of file From 4fd339b55feeb747ec7c9e03f66515fe37f04075 Mon Sep 17 00:00:00 2001 From: Wanjohi Date: Mon, 3 Mar 2025 23:42:58 +0300 Subject: [PATCH 7/9] fix: Have a root component --- packages/www/src/App.tsx | 43 ++++++++++++--------------------- packages/www/src/pages/play.tsx | 35 +++++++++------------------ packages/www/src/ui/root.tsx | 40 ++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 52 deletions(-) create mode 100644 packages/www/src/ui/root.tsx diff --git a/packages/www/src/App.tsx b/packages/www/src/App.tsx index 766feb33..1b960f0f 100644 --- a/packages/www/src/App.tsx +++ b/packages/www/src/App.tsx @@ -15,19 +15,20 @@ import { Navigate, Route, Router } from "@solidjs/router"; import { globalStyle, macaron$ } from "@macaron-css/core"; import { Component, createSignal, Match, onCleanup, Switch } from 'solid-js'; import TestComponent from './pages/test'; +import Root from './ui/root'; -const Root = styled("div", { - base: { - inset: 0, - lineHeight: 1, - fontSynthesis: "none", - color: theme.color.d1000.gray, - fontFamily: theme.font.family.body, - textRendering: "optimizeLegibility", - WebkitFontSmoothing: "antialised", - backgroundColor: theme.color.background.d100, - }, -}); +// const Root = styled("div", { +// base: { +// inset: 0, +// lineHeight: 1, +// fontSynthesis: "none", +// color: theme.color.d1000.gray, +// fontFamily: theme.font.family.body, +// textRendering: "optimizeLegibility", +// WebkitFontSmoothing: "antialised", +// backgroundColor: theme.color.background.d100, +// }, +// }); globalStyle("html", { fontSize: 16, @@ -65,25 +66,11 @@ globalStyle("*", { }); export const App: Component = () => { - const [theme, setTheme] = createSignal( - window.matchMedia("(prefers-color-scheme: dark)").matches - ? "dark" - : "light", - ); - - const darkMode = window.matchMedia("(prefers-color-scheme: dark)"); - const setColorScheme = (e: MediaQueryListEvent) => { - setTheme(e.matches ? "dark" : "light"); - }; - darkMode.addEventListener("change", setColorScheme); - onCleanup(() => { - darkMode.removeEventListener("change", setColorScheme); - }); - + const storage = useStorage(); return ( - + ( - window.matchMedia("(prefers-color-scheme: dark)").matches - ? "dark" - : "light", - ); - - const darkMode = window.matchMedia("(prefers-color-scheme: dark)"); - const setColorScheme = (e: MediaQueryListEvent) => { - setTheme(e.matches ? "dark" : "light"); - }; - darkMode.addEventListener("change", setColorScheme); - onCleanup(() => { - darkMode.removeEventListener("change", setColorScheme); - }); - return { openModal() { @@ -268,21 +254,22 @@ function createModal() { return ( - + - - Hello from modal - setOpen(false)}>close modal - - + > + + Hello from modal + setOpen(false)}>close modal + + + ); diff --git a/packages/www/src/ui/root.tsx b/packages/www/src/ui/root.tsx new file mode 100644 index 00000000..7e06d7a8 --- /dev/null +++ b/packages/www/src/ui/root.tsx @@ -0,0 +1,40 @@ +import { createSignal, JSX, onCleanup } from "solid-js"; +import { useStorage } from "@nestri/www/providers/account"; +import { darkClass, lightClass, theme } from "./theme"; +import { styled } from "@macaron-css/solid"; + +const BaseComponent = styled("div", { + base: { + inset: 0, + lineHeight: 1, + fontSynthesis: "none", + color: theme.color.d1000.gray, + fontFamily: theme.font.family.body, + textRendering: "optimizeLegibility", + WebkitFontSmoothing: "antialised", + backgroundColor: theme.color.background.d100, + }, +}); + +export default function Root(props: { children: number | boolean | Node | JSX.ArrayElement | (string & {}) | null | undefined; }) { + const [theme, setTheme] = createSignal( + window.matchMedia("(prefers-color-scheme: dark)").matches + ? "dark" + : "light", + ); + + const darkMode = window.matchMedia("(prefers-color-scheme: dark)"); + const setColorScheme = (e: MediaQueryListEvent) => { + setTheme(e.matches ? "dark" : "light"); + }; + darkMode.addEventListener("change", setColorScheme); + onCleanup(() => { + darkMode.removeEventListener("change", setColorScheme); + }); + + return ( + + {props.children} + + ) +} \ No newline at end of file From fb0cb0b6cae59c81a0f4c60b49606df53fb9607e Mon Sep 17 00:00:00 2001 From: Wanjohi Date: Mon, 3 Mar 2025 23:47:00 +0300 Subject: [PATCH 8/9] fix: Portal mount --- packages/www/src/App.tsx | 43 +++++++++++++++++++++------------ packages/www/src/pages/play.tsx | 5 +--- packages/www/src/ui/root.tsx | 40 ------------------------------ 3 files changed, 29 insertions(+), 59 deletions(-) delete mode 100644 packages/www/src/ui/root.tsx diff --git a/packages/www/src/App.tsx b/packages/www/src/App.tsx index 1b960f0f..766feb33 100644 --- a/packages/www/src/App.tsx +++ b/packages/www/src/App.tsx @@ -15,20 +15,19 @@ import { Navigate, Route, Router } from "@solidjs/router"; import { globalStyle, macaron$ } from "@macaron-css/core"; import { Component, createSignal, Match, onCleanup, Switch } from 'solid-js'; import TestComponent from './pages/test'; -import Root from './ui/root'; -// const Root = styled("div", { -// base: { -// inset: 0, -// lineHeight: 1, -// fontSynthesis: "none", -// color: theme.color.d1000.gray, -// fontFamily: theme.font.family.body, -// textRendering: "optimizeLegibility", -// WebkitFontSmoothing: "antialised", -// backgroundColor: theme.color.background.d100, -// }, -// }); +const Root = styled("div", { + base: { + inset: 0, + lineHeight: 1, + fontSynthesis: "none", + color: theme.color.d1000.gray, + fontFamily: theme.font.family.body, + textRendering: "optimizeLegibility", + WebkitFontSmoothing: "antialised", + backgroundColor: theme.color.background.d100, + }, +}); globalStyle("html", { fontSize: 16, @@ -66,11 +65,25 @@ globalStyle("*", { }); export const App: Component = () => { - + const [theme, setTheme] = createSignal( + window.matchMedia("(prefers-color-scheme: dark)").matches + ? "dark" + : "light", + ); + + const darkMode = window.matchMedia("(prefers-color-scheme: dark)"); + const setColorScheme = (e: MediaQueryListEvent) => { + setTheme(e.matches ? "dark" : "light"); + }; + darkMode.addEventListener("change", setColorScheme); + onCleanup(() => { + darkMode.removeEventListener("change", setColorScheme); + }); + const storage = useStorage(); return ( - + + - setOpen(false)}>close modal - ); diff --git a/packages/www/src/ui/root.tsx b/packages/www/src/ui/root.tsx deleted file mode 100644 index 7e06d7a8..00000000 --- a/packages/www/src/ui/root.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { createSignal, JSX, onCleanup } from "solid-js"; -import { useStorage } from "@nestri/www/providers/account"; -import { darkClass, lightClass, theme } from "./theme"; -import { styled } from "@macaron-css/solid"; - -const BaseComponent = styled("div", { - base: { - inset: 0, - lineHeight: 1, - fontSynthesis: "none", - color: theme.color.d1000.gray, - fontFamily: theme.font.family.body, - textRendering: "optimizeLegibility", - WebkitFontSmoothing: "antialised", - backgroundColor: theme.color.background.d100, - }, -}); - -export default function Root(props: { children: number | boolean | Node | JSX.ArrayElement | (string & {}) | null | undefined; }) { - const [theme, setTheme] = createSignal( - window.matchMedia("(prefers-color-scheme: dark)").matches - ? "dark" - : "light", - ); - - const darkMode = window.matchMedia("(prefers-color-scheme: dark)"); - const setColorScheme = (e: MediaQueryListEvent) => { - setTheme(e.matches ? "dark" : "light"); - }; - darkMode.addEventListener("change", setColorScheme); - onCleanup(() => { - darkMode.removeEventListener("change", setColorScheme); - }); - - return ( - - {props.children} - - ) -} \ No newline at end of file From 402e89422443a2825f0209da0c4c0f67b92b78c3 Mon Sep 17 00:00:00 2001 From: AquaWolf <3daquawolf@gmail.com> Date: Mon, 3 Mar 2025 22:28:53 +0100 Subject: [PATCH 9/9] added modal with correct styling. Open: need to wire the modals correctly and have the welcome modal --- packages/www/src/pages/play.tsx | 126 +++++++++++++++++++++++--------- 1 file changed, 91 insertions(+), 35 deletions(-) diff --git a/packages/www/src/pages/play.tsx b/packages/www/src/pages/play.tsx index 067dbbcf..6df0e5ca 100644 --- a/packages/www/src/pages/play.tsx +++ b/packages/www/src/pages/play.tsx @@ -1,3 +1,5 @@ +// FIXME: We need to make from the modal a reusable component +// FIXME: The mousepointer lock is somehow shifted when the window gets resized import { Text } from "@nestri/www/ui/text"; import { createSignal, createEffect, onCleanup, onMount, Show } from "solid-js"; import { Portal } from "solid-js/web"; @@ -23,18 +25,33 @@ const ModalContainer = styled("div", { maxWidth: 370, maxHeight: "75vh", height: "auto", - // borderRadius: 12, - // borderWidth: 1, - // borderStyle: "solid", - // borderColor: theme.color.gray.d400, - // backgroundColor: theme.color.pink.d400, - backgroundColor: theme.color.red.d300, - // boxShadow: theme.color.boxShadow, - // backdropFilter: "blur(20px)", + borderRadius: 12, + borderWidth: 1, + borderStyle: "solid", + borderColor: theme.color.gray.d400, + backgroundColor: theme.color.gray.d200, + boxShadow: theme.color.boxShadow, + backdropFilter: "blur(20px)", padding: "20px 25px" } }) +const Button = styled("button", { + base: { + outline: "none", + width: "100%", + backgroundColor: theme.color.background.d100, + padding: "12px 16px", + borderRadius: 10, + borderWidth: 1, + borderStyle: "solid", + borderColor: theme.color.gray.d500, + ":hover": { + backgroundColor: theme.color.gray.d300, + } + } +}) + export function PlayComponent() { const params = useParams(); const id = params.id; @@ -54,6 +71,8 @@ export function PlayComponent() { let webrtc: WebRTCStream; let nestriMouse: Mouse, nestriKeyboard: Keyboard; + const { Modal, openModal } = createModal(); + const initializeInputDevices = () => { const canvasElement = canvas(); if (!canvasElement || !webrtc) return; @@ -134,7 +153,10 @@ export function PlayComponent() { if (!showBannerModal) { const playing = sessionStorage.getItem("showedBanner"); setShowBannerModal(!playing || playing !== "true"); - setShowButtonModal(playing === "false"); + if(!playing) { + openModal(); + } + } @@ -206,31 +228,21 @@ export function PlayComponent() { nestriMouse?.dispose(); }); - const { Modal, openModal } = createModal(); - - return ( - <> - - open modal - - - > - /* + return ( {showOffline() ? ( Offline - setShowButtonModal(true)}>Show Modal ) : ( )} - - */ + + ); } @@ -242,6 +254,44 @@ interface ModalProps { lockPlay?: () => Promise; } +function createWelcomeModal() { + const [open, setOpen] = createSignal(false); + + return { + openWelcomeModal() { + setOpen(true); + }, + WelcomeModal(props: ModalProps) { + return ( + + + + + + Happy that you use Nestri! + { + sessionStorage.setItem("showedBanner", "true"); + await props.handleVideoInput?.(); + await props.lockPlay?.(); + }}>Let's go + + + + + + ); + }, + }; +} + function createModal() { const [open, setOpen] = createSignal(false); @@ -249,24 +299,30 @@ function createModal() { openModal() { setOpen(true); }, - Modal() { + Modal(props: ModalProps) { return ( - - - Hello from modal - setOpen(false)}>close modal - - + > + + + { + props.setShow(false); + await props.handleVideoInput?.(); + await props.lockPlay?.(); + }}>Continue Playing + setOpen(false)}>Shutdown Nestri + + + );