diff --git a/build.js b/build.js index cfdb0a6..817372c 100644 --- a/build.js +++ b/build.js @@ -111,6 +111,9 @@ const buildOptions = { }, }, ], + define: { + "process.env.NODE_ENV": JSON.stringify(env), + }, mainFields: ["browser", "module", "main"], conditions: ["production", "default"], }; @@ -119,9 +122,6 @@ if (env === "prod") { Object.assign(buildOptions, { minify: true, pure: ["console.debug"], // Remove all `console.debug()` calls in production builds. - define: { - "process.env.NODE_ENV": '"production"', - }, }); } diff --git a/package-lock.json b/package-lock.json index c7cf360..b54985b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@number-flow/react": "^0.5.10", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-slider": "^1.2.0", + "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tooltip": "^1.2.8", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -1648,6 +1649,35 @@ } } }, + "node_modules/@radix-ui/react-switch": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", + "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-tooltip": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", diff --git a/package.json b/package.json index fdc9eef..ec746a8 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@number-flow/react": "^0.5.10", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-slider": "^1.2.0", + "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tooltip": "^1.2.8", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/src/components/Switch.jsx b/src/components/Switch.jsx new file mode 100644 index 0000000..667c266 --- /dev/null +++ b/src/components/Switch.jsx @@ -0,0 +1,15 @@ +import React from "react"; +import * as SwitchPrimitive from "@radix-ui/react-switch"; + +export default function Switch({ checked, onCheckedChange, ...props }) { + return ( + + + + ); +} diff --git a/src/components/Switch.scss b/src/components/Switch.scss new file mode 100644 index 0000000..fa445bb --- /dev/null +++ b/src/components/Switch.scss @@ -0,0 +1,51 @@ +@use "~@destinygg/libstiny" as dgg; +@use "@destinygg/libstiny/lib/utils/transitions" as transitions; + +.switch { + &__slider { + @include transitions.create-transition(all, default); + position: relative; + display: inline-block; + width: dgg.$switch-width; + height: dgg.$switch-height; + flex-shrink: 0; + cursor: pointer; + background-color: dgg.$switch-background-default-rest; + border-radius: dgg.$semantic-radii-pill; + border: none; + padding: 0; + + &:hover { + background-color: dgg.$switch-background-default-hover; + } + + &[data-state="checked"] { + background-color: dgg.$switch-background-active-rest; + + &:hover { + background-color: dgg.$switch-background-active-hover; + } + } + + // Override libstiny's ::before thumb in favor of the Radix UI Switch thumb element. + &::before { + content: none; + } + } + + &__thumb { + @include transitions.create-transition(all, movement); + display: block; + position: absolute; + height: dgg.$switch-toggle-size; + width: dgg.$switch-toggle-size; + left: dgg.$space-1; + bottom: dgg.$space-1; + background-color: dgg.$switch-toggle-background; + border-radius: 50%; + + &[data-state="checked"] { + transform: translateX(dgg.$switch-toggle-size); + } + } +} diff --git a/src/controls/settings/MainMenu.jsx b/src/controls/settings/MainMenu.jsx index 7280bfe..76c59d6 100644 --- a/src/controls/settings/MainMenu.jsx +++ b/src/controls/settings/MainMenu.jsx @@ -1,21 +1,41 @@ import React from "react"; import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; import { ChevronRight } from "lucide-react"; +import Switch from "../../components/Switch.jsx"; -export default function MainMenu({ onNavigateQuality, selectedQuality }) { +export default function MainMenu({ + onNavigateQuality, + selectedQuality, + isIvsDebug, + onIvsDebugChange, +}) { return ( - { - e.preventDefault(); - onNavigateQuality(); - }} - > - Quality - - {selectedQuality.label} - - - + <> + { + e.preventDefault(); + onNavigateQuality(); + }} + > + Quality + + {selectedQuality.label} + + + + + {process.env.NODE_ENV === "dev" && ( + { + e.preventDefault(); + }} + > + IVS Debug + + + )} + ); } diff --git a/src/controls/settings/SettingsButton.jsx b/src/controls/settings/SettingsButton.jsx index bb1ad95..383eb76 100644 --- a/src/controls/settings/SettingsButton.jsx +++ b/src/controls/settings/SettingsButton.jsx @@ -8,6 +8,7 @@ import MainMenu from "./MainMenu.jsx"; import QualityMenu from "./QualityMenu.jsx"; import { useSettings } from "./useSettings.js"; import { useQualitySelector } from "./useQualitySelector.js"; +import { useIvsDebug } from "./useIvsDebug.js"; export default function SettingsButton({ core, container, shouldShow }) { const { @@ -22,6 +23,8 @@ export default function SettingsButton({ core, container, shouldShow }) { const { selectedQuality, qualityOptions, handleQualityChange } = useQualitySelector(core); + const { isIvsDebug, setIsIvsDebug } = useIvsDebug(core); + return ( )} diff --git a/src/controls/settings/useIvsDebug.js b/src/controls/settings/useIvsDebug.js new file mode 100644 index 0000000..4351a63 --- /dev/null +++ b/src/controls/settings/useIvsDebug.js @@ -0,0 +1,36 @@ +import { useEffect } from "react"; +import { usePreferences } from "../usePreferences"; + +export function useIvsDebug(core) { + const { isIvsDebug, setIsIvsDebug } = usePreferences(); + + useEffect(() => { + if (!isIvsDebug) { + return; + } + + // Sample IVS messages: + // { type: 12, arg: { key: "bufferedPosition", value: 2.334009 } } + // { type: 12, arg: { key: "statistics", value: { bitrate: 3109311, ... } } } + // { type: "PlayerQualityChanged", arg: { name: "720p60", ... } } + const handler = (event) => { + const data = event.data; + const type = data.type; + const detail = data.arg?.key || data.arg?.name; + + if (detail === "bufferedPosition") { + return; + } + + const label = detail ? `${type} (${detail})` : type; + console.debug(`[Kickstiny] IVS Message: ${label}`, data); + }; + + core.worker.addEventListener("message", handler); + return () => { + core.worker.removeEventListener("message", handler); + }; + }, [isIvsDebug, core]); + + return { isIvsDebug, setIsIvsDebug }; +} diff --git a/src/controls/usePreferences.js b/src/controls/usePreferences.js index 0f15294..67741fc 100644 --- a/src/controls/usePreferences.js +++ b/src/controls/usePreferences.js @@ -2,6 +2,7 @@ import { useState, useCallback } from "react"; const QUALITY_STORAGE_KEY = "kickstiny.preference.quality"; const VOLUME_STORAGE_KEY = "kickstiny.preference.volume"; +const IVS_DEBUG_STORAGE_KEY = "kickstiny.preference.ivsDebug"; const DEFAULT_VOLUME = 100; @@ -25,6 +26,16 @@ export function usePreferences() { } }); + const [isIvsDebug, setIsIvsDebugState] = useState(() => { + try { + const stored = window.localStorage.getItem(IVS_DEBUG_STORAGE_KEY); + return stored === "true"; + } catch (err) { + console.log("[Kickstiny] Unable to read ivs debug preference", err); + return false; + } + }); + const setSavedQuality = useCallback((value) => { try { window.localStorage.setItem(QUALITY_STORAGE_KEY, value); @@ -43,10 +54,21 @@ export function usePreferences() { } }, []); + const setIsIvsDebug = useCallback((value) => { + try { + window.localStorage.setItem(IVS_DEBUG_STORAGE_KEY, value.toString()); + setIsIvsDebugState(value); + } catch (err) { + console.log("[Kickstiny] Unable to persist ivs debug preference", err); + } + }, []); + return { savedQuality, savedVolume, + isIvsDebug, setSavedQuality, setSavedVolume, + setIsIvsDebug, }; } diff --git a/src/main.scss b/src/main.scss index c5c3e74..a4e1b64 100644 --- a/src/main.scss +++ b/src/main.scss @@ -3,6 +3,7 @@ @use "./components/Tooltip.scss"; @use "./components/Dropdown.scss"; @use "./components/Slider.scss"; +@use "./components/Switch.scss"; @use "./controls/container/Container.scss"; @use "./controls/bar/ControlsBar.scss";