mirror of
https://github.com/Andre-Bzk/twitch4steamdeck.git
synced 2026-11-15 15:43:08 +01:00
refactor: extract magic values into named constant modules
Scattered numeric literals had no names at their use sites, making it impossible to know whether two identical values were intentionally the same or coincidentally equal. Moving them to typed constants makes the relationship explicit and ensures a single edit propagates everywhere.
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
export const TRIGGER_THRESHOLD = 8
|
||||||
|
export const DEDUP_WINDOW_MS = 40
|
||||||
|
export const HOTPLUG_SCAN_INTERVAL_MS = 3_000
|
||||||
@@ -32,6 +32,7 @@
|
|||||||
import * as fs from 'node:fs'
|
import * as fs from 'node:fs'
|
||||||
import * as path from 'node:path'
|
import * as path from 'node:path'
|
||||||
import type { BrowserWindow } from 'electron'
|
import type { BrowserWindow } from 'electron'
|
||||||
|
import { DEDUP_WINDOW_MS, HOTPLUG_SCAN_INTERVAL_MS, TRIGGER_THRESHOLD } from '../constants/input'
|
||||||
|
|
||||||
// evdev input_event auf 64-bit Linux:
|
// evdev input_event auf 64-bit Linux:
|
||||||
// { u64 tv_sec (8 Bytes), u64 tv_usec (8 Bytes), u16 type (2), u16 code (2), s32 value (4) } = 24 Bytes
|
// { u64 tv_sec (8 Bytes), u64 tv_usec (8 Bytes), u16 type (2), u16 code (2), s32 value (4) } = 24 Bytes
|
||||||
@@ -70,16 +71,10 @@ const BUTTON_MAP: Record<number, string> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const STICK_THRESHOLD = 16384 // ~50% von 32767
|
const STICK_THRESHOLD = 16384 // ~50% von 32767
|
||||||
// Trigger-Schwellwert: Achsenwert > 0 (nicht gedrückt) bis ~255–1023 je Controller
|
|
||||||
const TRIGGER_THRESHOLD = 8
|
|
||||||
const REPEAT_INITIAL_MS = 400
|
const REPEAT_INITIAL_MS = 400
|
||||||
const REPEAT_INTERVAL_MS = 150
|
const REPEAT_INTERVAL_MS = 150
|
||||||
// Trigger-Achsen sollen kein Auto-Repeat auslösen (300s-Sprünge nicht wiederholen)
|
// Trigger-Achsen sollen kein Auto-Repeat auslösen (300s-Sprünge nicht wiederholen)
|
||||||
const NO_REPEAT_ABS_CODES = new Set([ABS_Z, ABS_RZ])
|
const NO_REPEAT_ABS_CODES = new Set([ABS_Z, ABS_RZ])
|
||||||
const SCAN_INTERVAL_MS = 3000
|
|
||||||
// Bluetooth-/Steam-Setups können dieselbe Eingabe über mehrere Devices liefern.
|
|
||||||
// Nahezu zeitgleiche identische Events werden global entdoppelt.
|
|
||||||
const DUPLICATE_EVENT_WINDOW_MS = 40
|
|
||||||
|
|
||||||
interface AxisState {
|
interface AxisState {
|
||||||
key: string | null
|
key: string | null
|
||||||
@@ -367,7 +362,7 @@ export function startGamepadReader(getWindow: () => BrowserWindow | null): () =>
|
|||||||
const onKey = (key: string): void => {
|
const onKey = (key: string): void => {
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
const lastAt = lastKeyAt.get(key) ?? 0
|
const lastAt = lastKeyAt.get(key) ?? 0
|
||||||
if (now - lastAt < DUPLICATE_EVENT_WINDOW_MS) return
|
if (now - lastAt < DEDUP_WINDOW_MS) return
|
||||||
lastKeyAt.set(key, now)
|
lastKeyAt.set(key, now)
|
||||||
|
|
||||||
const win = getWindow()
|
const win = getWindow()
|
||||||
@@ -427,7 +422,7 @@ export function startGamepadReader(getWindow: () => BrowserWindow | null): () =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
scanDevices()
|
scanDevices()
|
||||||
const timer = setInterval(scanDevices, SCAN_INTERVAL_MS)
|
const timer = setInterval(scanDevices, HOTPLUG_SCAN_INTERVAL_MS)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(timer)
|
clearInterval(timer)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'
|
|||||||
import { GamepadHintItem } from './GamepadPrompt'
|
import { GamepadHintItem } from './GamepadPrompt'
|
||||||
import { ClapperboardIcon, EyeIcon, GamepadIcon, PauseIcon, PlayIcon, QualityIcon, StopIcon } from './Icons'
|
import { ClapperboardIcon, EyeIcon, GamepadIcon, PauseIcon, PlayIcon, QualityIcon, StopIcon } from './Icons'
|
||||||
import { formatCount, formatTimestamp } from '../lib/formatting'
|
import { formatCount, formatTimestamp } from '../lib/formatting'
|
||||||
|
import { DOUBLE_TAP_MS, OVERLAY_HIDE_DELAY_MS } from '../constants/playback'
|
||||||
|
|
||||||
interface PlaybackOverlayProps {
|
interface PlaybackOverlayProps {
|
||||||
playState: 'playing' | 'paused'
|
playState: 'playing' | 'paused'
|
||||||
@@ -30,9 +31,6 @@ interface PlaybackOverlayProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const HIDE_DELAY_MS = 5_000
|
|
||||||
const DOUBLE_TAP_MS = 300
|
|
||||||
|
|
||||||
export function PlaybackOverlay({
|
export function PlaybackOverlay({
|
||||||
playState,
|
playState,
|
||||||
durationSeconds,
|
durationSeconds,
|
||||||
@@ -81,7 +79,7 @@ export function PlaybackOverlay({
|
|||||||
|
|
||||||
const scheduleHide = useCallback(() => {
|
const scheduleHide = useCallback(() => {
|
||||||
clearHideTimer()
|
clearHideTimer()
|
||||||
hideTimerRef.current = setTimeout(() => setVisible(false), HIDE_DELAY_MS)
|
hideTimerRef.current = setTimeout(() => setVisible(false), OVERLAY_HIDE_DELAY_MS)
|
||||||
}, [clearHideTimer])
|
}, [clearHideTimer])
|
||||||
|
|
||||||
/** Call on any user interaction to show overlay and reset auto-hide timer. */
|
/** Call on any user interaction to show overlay and reset auto-hide timer. */
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Hls from 'hls.js'
|
import Hls from 'hls.js'
|
||||||
import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'
|
import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'
|
||||||
|
import { POSITION_REPORT_INTERVAL_MS } from '../constants/playback'
|
||||||
|
|
||||||
export interface VideoPlayerHandle {
|
export interface VideoPlayerHandle {
|
||||||
seek(delta: number): void
|
seek(delta: number): void
|
||||||
@@ -24,8 +25,6 @@ interface Props {
|
|||||||
onTimeUpdate?: (seconds: number) => void
|
onTimeUpdate?: (seconds: number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const POSITION_REPORT_INTERVAL_MS = 5_000
|
|
||||||
|
|
||||||
export const VideoPlayer = forwardRef<VideoPlayerHandle, Props>(
|
export const VideoPlayer = forwardRef<VideoPlayerHandle, Props>(
|
||||||
({ hlsUrl, startPosition, isLive, vodId, durationSeconds, onPlaying, onPaused, onEnded, onError, onTimeUpdate }, ref) => {
|
({ hlsUrl, startPosition, isLive, vodId, durationSeconds, onPlaying, onPaused, onEnded, onError, onTimeUpdate }, ref) => {
|
||||||
const videoRef = useRef<HTMLVideoElement>(null)
|
const videoRef = useRef<HTMLVideoElement>(null)
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export const AXIS_THRESHOLD = 0.5
|
||||||
|
export const REPEAT_INITIAL_MS = 400
|
||||||
|
export const REPEAT_INTERVAL_MS = 150
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export const OVERLAY_HIDE_DELAY_MS = 5_000
|
||||||
|
export const DOUBLE_TAP_MS = 300
|
||||||
|
export const POSITION_REPORT_INTERVAL_MS = 5_000
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export const CARD_W = 260
|
||||||
|
export const CARD_GAP = 16
|
||||||
|
export const STREAM_W = 220
|
||||||
|
export const STREAM_GAP = 14
|
||||||
@@ -20,6 +20,8 @@
|
|||||||
* Axis 0 (left stick X) → Left/Right
|
* Axis 0 (left stick X) → Left/Right
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { AXIS_THRESHOLD, REPEAT_INITIAL_MS, REPEAT_INTERVAL_MS } from '../constants/input'
|
||||||
|
|
||||||
const BUTTON_MAP: Record<number, string> = {
|
const BUTTON_MAP: Record<number, string> = {
|
||||||
0: 'Enter', // A
|
0: 'Enter', // A
|
||||||
1: 'Escape', // B
|
1: 'Escape', // B
|
||||||
@@ -35,10 +37,6 @@ const BUTTON_MAP: Record<number, string> = {
|
|||||||
15: 'ArrowRight'
|
15: 'ArrowRight'
|
||||||
}
|
}
|
||||||
|
|
||||||
const AXIS_THRESHOLD = 0.5
|
|
||||||
const REPEAT_INITIAL_MS = 400
|
|
||||||
const REPEAT_INTERVAL_MS = 150
|
|
||||||
|
|
||||||
interface AxisRepeat {
|
interface AxisRepeat {
|
||||||
key: string | null
|
key: string | null
|
||||||
heldSince: number
|
heldSince: number
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { GamepadHintItem } from '../components/GamepadPrompt'
|
|||||||
import LanguageBadge from '../components/LanguageBadge'
|
import LanguageBadge from '../components/LanguageBadge'
|
||||||
import type { FollowedChannelInfo, GameInfo } from '../types/t4sd'
|
import type { FollowedChannelInfo, GameInfo } from '../types/t4sd'
|
||||||
import { formatViewers, formatViewersFull } from '../lib/formatting'
|
import { formatViewers, formatViewersFull } from '../lib/formatting'
|
||||||
|
import { STREAM_GAP, STREAM_W } from '../constants/ui'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
hasFocus: boolean
|
hasFocus: boolean
|
||||||
@@ -15,8 +16,6 @@ interface Props {
|
|||||||
type LoadState = 'loading' | 'ok' | 'error'
|
type LoadState = 'loading' | 'ok' | 'error'
|
||||||
type FocusRegion = 'shelf' | 'grid'
|
type FocusRegion = 'shelf' | 'grid'
|
||||||
|
|
||||||
const STREAM_W = 220
|
|
||||||
const STREAM_GAP = 14
|
|
||||||
|
|
||||||
export default function BrowseScreen({
|
export default function BrowseScreen({
|
||||||
hasFocus,
|
hasFocus,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
formatViewers,
|
formatViewers,
|
||||||
formatWatchedAt
|
formatWatchedAt
|
||||||
} from '../lib/formatting'
|
} from '../lib/formatting'
|
||||||
|
import { CARD_GAP, CARD_W } from '../constants/ui'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
channel: FollowedChannelInfo
|
channel: FollowedChannelInfo
|
||||||
@@ -21,9 +22,6 @@ interface Props {
|
|||||||
|
|
||||||
type FocusRegion = 'hero' | 'shelf' | 'chapters'
|
type FocusRegion = 'hero' | 'shelf' | 'chapters'
|
||||||
|
|
||||||
const CARD_W = 260
|
|
||||||
const CARD_GAP = 16
|
|
||||||
|
|
||||||
function resolveThumbnail(url: string | undefined, w = 1280, h = 720): string | undefined {
|
function resolveThumbnail(url: string | undefined, w = 1280, h = 720): string | undefined {
|
||||||
return url?.replace('{width}', String(w)).replace('{height}', String(h))
|
return url?.replace('{width}', String(w)).replace('{height}', String(h))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user