From bb635366b2774c0da171062f8126ba5d4226310c Mon Sep 17 00:00:00 2001 From: Andre <200930841+Andre-Bzk@users.noreply.github.com> Date: Thu, 14 May 2026 12:17:47 +0200 Subject: [PATCH] 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. --- src/main/constants/input.ts | 3 +++ src/main/input/gamepadReader.ts | 11 +++-------- src/renderer/src/components/PlaybackOverlay.tsx | 6 ++---- src/renderer/src/components/VideoPlayer.tsx | 3 +-- src/renderer/src/constants/input.ts | 3 +++ src/renderer/src/constants/playback.ts | 3 +++ src/renderer/src/constants/ui.ts | 4 ++++ src/renderer/src/input/gamepad.ts | 6 ++---- src/renderer/src/screens/BrowseScreen.tsx | 3 +-- src/renderer/src/screens/ChannelScreen.tsx | 4 +--- 10 files changed, 23 insertions(+), 23 deletions(-) create mode 100644 src/main/constants/input.ts create mode 100644 src/renderer/src/constants/input.ts create mode 100644 src/renderer/src/constants/playback.ts create mode 100644 src/renderer/src/constants/ui.ts diff --git a/src/main/constants/input.ts b/src/main/constants/input.ts new file mode 100644 index 0000000..3bd6cfe --- /dev/null +++ b/src/main/constants/input.ts @@ -0,0 +1,3 @@ +export const TRIGGER_THRESHOLD = 8 +export const DEDUP_WINDOW_MS = 40 +export const HOTPLUG_SCAN_INTERVAL_MS = 3_000 diff --git a/src/main/input/gamepadReader.ts b/src/main/input/gamepadReader.ts index f205ba8..3deeecc 100644 --- a/src/main/input/gamepadReader.ts +++ b/src/main/input/gamepadReader.ts @@ -32,6 +32,7 @@ import * as fs from 'node:fs' import * as path from 'node:path' 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: // { 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 = { } 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_INTERVAL_MS = 150 // 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 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 { key: string | null @@ -367,7 +362,7 @@ export function startGamepadReader(getWindow: () => BrowserWindow | null): () => const onKey = (key: string): void => { const now = Date.now() 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) const win = getWindow() @@ -427,7 +422,7 @@ export function startGamepadReader(getWindow: () => BrowserWindow | null): () => } scanDevices() - const timer = setInterval(scanDevices, SCAN_INTERVAL_MS) + const timer = setInterval(scanDevices, HOTPLUG_SCAN_INTERVAL_MS) return () => { clearInterval(timer) diff --git a/src/renderer/src/components/PlaybackOverlay.tsx b/src/renderer/src/components/PlaybackOverlay.tsx index c085ce4..1dfb8d0 100644 --- a/src/renderer/src/components/PlaybackOverlay.tsx +++ b/src/renderer/src/components/PlaybackOverlay.tsx @@ -2,6 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react' import { GamepadHintItem } from './GamepadPrompt' import { ClapperboardIcon, EyeIcon, GamepadIcon, PauseIcon, PlayIcon, QualityIcon, StopIcon } from './Icons' import { formatCount, formatTimestamp } from '../lib/formatting' +import { DOUBLE_TAP_MS, OVERLAY_HIDE_DELAY_MS } from '../constants/playback' interface PlaybackOverlayProps { playState: 'playing' | 'paused' @@ -30,9 +31,6 @@ interface PlaybackOverlayProps { } -const HIDE_DELAY_MS = 5_000 -const DOUBLE_TAP_MS = 300 - export function PlaybackOverlay({ playState, durationSeconds, @@ -81,7 +79,7 @@ export function PlaybackOverlay({ const scheduleHide = useCallback(() => { clearHideTimer() - hideTimerRef.current = setTimeout(() => setVisible(false), HIDE_DELAY_MS) + hideTimerRef.current = setTimeout(() => setVisible(false), OVERLAY_HIDE_DELAY_MS) }, [clearHideTimer]) /** Call on any user interaction to show overlay and reset auto-hide timer. */ diff --git a/src/renderer/src/components/VideoPlayer.tsx b/src/renderer/src/components/VideoPlayer.tsx index d2a4330..7f5d7d0 100644 --- a/src/renderer/src/components/VideoPlayer.tsx +++ b/src/renderer/src/components/VideoPlayer.tsx @@ -1,5 +1,6 @@ import Hls from 'hls.js' import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react' +import { POSITION_REPORT_INTERVAL_MS } from '../constants/playback' export interface VideoPlayerHandle { seek(delta: number): void @@ -24,8 +25,6 @@ interface Props { onTimeUpdate?: (seconds: number) => void } -const POSITION_REPORT_INTERVAL_MS = 5_000 - export const VideoPlayer = forwardRef( ({ hlsUrl, startPosition, isLive, vodId, durationSeconds, onPlaying, onPaused, onEnded, onError, onTimeUpdate }, ref) => { const videoRef = useRef(null) diff --git a/src/renderer/src/constants/input.ts b/src/renderer/src/constants/input.ts new file mode 100644 index 0000000..f4b0976 --- /dev/null +++ b/src/renderer/src/constants/input.ts @@ -0,0 +1,3 @@ +export const AXIS_THRESHOLD = 0.5 +export const REPEAT_INITIAL_MS = 400 +export const REPEAT_INTERVAL_MS = 150 diff --git a/src/renderer/src/constants/playback.ts b/src/renderer/src/constants/playback.ts new file mode 100644 index 0000000..f2b52a3 --- /dev/null +++ b/src/renderer/src/constants/playback.ts @@ -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 diff --git a/src/renderer/src/constants/ui.ts b/src/renderer/src/constants/ui.ts new file mode 100644 index 0000000..bf18d26 --- /dev/null +++ b/src/renderer/src/constants/ui.ts @@ -0,0 +1,4 @@ +export const CARD_W = 260 +export const CARD_GAP = 16 +export const STREAM_W = 220 +export const STREAM_GAP = 14 diff --git a/src/renderer/src/input/gamepad.ts b/src/renderer/src/input/gamepad.ts index 1e647d8..63e7872 100644 --- a/src/renderer/src/input/gamepad.ts +++ b/src/renderer/src/input/gamepad.ts @@ -20,6 +20,8 @@ * Axis 0 (left stick X) → Left/Right */ +import { AXIS_THRESHOLD, REPEAT_INITIAL_MS, REPEAT_INTERVAL_MS } from '../constants/input' + const BUTTON_MAP: Record = { 0: 'Enter', // A 1: 'Escape', // B @@ -35,10 +37,6 @@ const BUTTON_MAP: Record = { 15: 'ArrowRight' } -const AXIS_THRESHOLD = 0.5 -const REPEAT_INITIAL_MS = 400 -const REPEAT_INTERVAL_MS = 150 - interface AxisRepeat { key: string | null heldSince: number diff --git a/src/renderer/src/screens/BrowseScreen.tsx b/src/renderer/src/screens/BrowseScreen.tsx index 99c5541..cf147bc 100644 --- a/src/renderer/src/screens/BrowseScreen.tsx +++ b/src/renderer/src/screens/BrowseScreen.tsx @@ -3,6 +3,7 @@ import { GamepadHintItem } from '../components/GamepadPrompt' import LanguageBadge from '../components/LanguageBadge' import type { FollowedChannelInfo, GameInfo } from '../types/t4sd' import { formatViewers, formatViewersFull } from '../lib/formatting' +import { STREAM_GAP, STREAM_W } from '../constants/ui' interface Props { hasFocus: boolean @@ -15,8 +16,6 @@ interface Props { type LoadState = 'loading' | 'ok' | 'error' type FocusRegion = 'shelf' | 'grid' -const STREAM_W = 220 -const STREAM_GAP = 14 export default function BrowseScreen({ hasFocus, diff --git a/src/renderer/src/screens/ChannelScreen.tsx b/src/renderer/src/screens/ChannelScreen.tsx index 4efd2c7..688c748 100644 --- a/src/renderer/src/screens/ChannelScreen.tsx +++ b/src/renderer/src/screens/ChannelScreen.tsx @@ -13,6 +13,7 @@ import { formatViewers, formatWatchedAt } from '../lib/formatting' +import { CARD_GAP, CARD_W } from '../constants/ui' interface Props { channel: FollowedChannelInfo @@ -21,9 +22,6 @@ interface Props { type FocusRegion = 'hero' | 'shelf' | 'chapters' -const CARD_W = 260 -const CARD_GAP = 16 - function resolveThumbnail(url: string | undefined, w = 1280, h = 720): string | undefined { return url?.replace('{width}', String(w)).replace('{height}', String(h)) }