diff --git a/src/main/index.ts b/src/main/index.ts index afdf695..c8ccf2b 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -9,7 +9,7 @@ import { getHlsCacheEnabled } from './prefs/hlsCachePref' import log from 'electron-log/main' log.initialize() -log.transports.file.level = 'info' +log.transports.file.level = 'error' log.transports.console.level = 'debug' const isDev = !app.isPackaged diff --git a/src/main/ipc/channels.ts b/src/main/ipc/channels.ts index 2f0bee4..6aa9121 100644 --- a/src/main/ipc/channels.ts +++ b/src/main/ipc/channels.ts @@ -30,6 +30,7 @@ export const IPC = { appGetCacheSize: 'app:get-cache-size', appClearCache: 'app:clear-cache', appSetHlsCacheEnabled: 'app:set-hls-cache-enabled', + appSetFileLoggingEnabled: 'app:set-file-logging-enabled', /** main → renderer */ gamepadInput: 'gamepad-input' diff --git a/src/main/ipc/handlers.ts b/src/main/ipc/handlers.ts index 5fd9f0e..bd6b810 100644 --- a/src/main/ipc/handlers.ts +++ b/src/main/ipc/handlers.ts @@ -37,6 +37,9 @@ export function registerIpcHandlers( safeHandle(IPC.appSetHlsCacheEnabled, (enabled: boolean) => { setHlsCacheEnabled(Boolean(enabled)) }) + safeHandle(IPC.appSetFileLoggingEnabled, (enabled: boolean) => { + log.transports.file.level = Boolean(enabled) ? 'info' : 'error' + }) safeHandle(IPC.authStatus, () => auth.getStatus()) safeHandle(IPC.authConfigured, () => auth.isConfigured()) diff --git a/src/preload/index.ts b/src/preload/index.ts index 183b2b7..963da85 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -37,7 +37,8 @@ const api = { quit: (): Promise => ipcRenderer.invoke(IPC.appQuit), getCacheSize: (): Promise => ipcRenderer.invoke(IPC.appGetCacheSize), clearCache: (): Promise => ipcRenderer.invoke(IPC.appClearCache), - setHlsCacheEnabled: (enabled: boolean): Promise => ipcRenderer.invoke(IPC.appSetHlsCacheEnabled, enabled) + setHlsCacheEnabled: (enabled: boolean): Promise => ipcRenderer.invoke(IPC.appSetHlsCacheEnabled, enabled), + setFileLoggingEnabled: (enabled: boolean): Promise => ipcRenderer.invoke(IPC.appSetFileLoggingEnabled, enabled) }, auth: { isConfigured: (): Promise => ipcRenderer.invoke(IPC.authConfigured), diff --git a/src/renderer/src/context/SettingsContext.tsx b/src/renderer/src/context/SettingsContext.tsx index a396757..a0d5f76 100644 --- a/src/renderer/src/context/SettingsContext.tsx +++ b/src/renderer/src/context/SettingsContext.tsx @@ -7,6 +7,7 @@ export interface AppSettings { sidebarWidth: number badgeGap: number hlsCacheEnabled: boolean + fileLoggingEnabled: boolean } const STORAGE_KEY = 't4sd:settings' @@ -20,7 +21,8 @@ const DEFAULTS: AppSettings = { streamBadgeMode: 'both', sidebarWidth: SIDEBAR_DEFAULT, badgeGap: BADGE_GAP_DEFAULT, - hlsCacheEnabled: false + hlsCacheEnabled: false, + fileLoggingEnabled: false } export { SIDEBAR_MIN, SIDEBAR_MAX, SIDEBAR_DEFAULT, BADGE_GAP_MIN, BADGE_GAP_MAX, BADGE_GAP_DEFAULT } @@ -39,7 +41,8 @@ function loadSettings(): AppSettings { : DEFAULTS.streamBadgeMode, sidebarWidth: sw >= SIDEBAR_MIN && sw <= SIDEBAR_MAX ? sw : SIDEBAR_DEFAULT, badgeGap: bg >= BADGE_GAP_MIN && bg <= BADGE_GAP_MAX ? bg : BADGE_GAP_DEFAULT, - hlsCacheEnabled: typeof parsed.hlsCacheEnabled === 'boolean' ? parsed.hlsCacheEnabled : DEFAULTS.hlsCacheEnabled + hlsCacheEnabled: typeof parsed.hlsCacheEnabled === 'boolean' ? parsed.hlsCacheEnabled : DEFAULTS.hlsCacheEnabled, + fileLoggingEnabled: typeof parsed.fileLoggingEnabled === 'boolean' ? parsed.fileLoggingEnabled : DEFAULTS.fileLoggingEnabled } } catch { return DEFAULTS @@ -60,6 +63,7 @@ interface SettingsCtx { setSidebarWidth: (width: number) => void setBadgeGap: (gap: number) => void setHlsCacheEnabled: (enabled: boolean) => void + setFileLoggingEnabled: (enabled: boolean) => void } const SettingsContext = createContext(null) @@ -71,6 +75,7 @@ export function SettingsProvider({ children }: { children: React.ReactNode }): J document.documentElement.style.setProperty('--sidebar-width', `${settings.sidebarWidth}px`) document.documentElement.style.setProperty('--badge-gap', `${settings.badgeGap}px`) void window.t4sd.app.setHlsCacheEnabled(settings.hlsCacheEnabled) + void window.t4sd.app.setFileLoggingEnabled(settings.fileLoggingEnabled) }, []) // eslint-disable-line react-hooks/exhaustive-deps const setStreamBadgeMode = useCallback((mode: StreamBadgeMode) => { @@ -110,8 +115,17 @@ export function SettingsProvider({ children }: { children: React.ReactNode }): J void window.t4sd.app.setHlsCacheEnabled(enabled) }, []) + const setFileLoggingEnabled = useCallback((enabled: boolean) => { + setSettings((prev) => { + const next = { ...prev, fileLoggingEnabled: enabled } + saveSettings(next) + return next + }) + void window.t4sd.app.setFileLoggingEnabled(enabled) + }, []) + return ( - + {children} ) diff --git a/src/renderer/src/screens/AccountScreen.tsx b/src/renderer/src/screens/AccountScreen.tsx index 97fe27b..27aa49a 100644 --- a/src/renderer/src/screens/AccountScreen.tsx +++ b/src/renderer/src/screens/AccountScreen.tsx @@ -13,10 +13,12 @@ export default function AccountScreen({ hasFocus, onRequestSidebar, onLogout }: const [user, setUser] = useState(null) const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState('') + const [confirmVisible, setConfirmVisible] = useState(false) + const [confirmIndex, setConfirmIndex] = useState(0) // 0 = Nein, 1 = Ja useEffect(() => { - if (hasFocus) logoutBtnRef.current?.focus({ preventScroll: true }) - }, [hasFocus]) + if (hasFocus && !confirmVisible) logoutBtnRef.current?.focus({ preventScroll: true }) + }, [hasFocus, confirmVisible]) useEffect(() => { let cancelled = false @@ -47,16 +49,40 @@ export default function AccountScreen({ hasFocus, onRequestSidebar, onLogout }: useEffect(() => { if (!hasFocus) return const onKey = (e: KeyboardEvent): void => { - if (e.key === 'ArrowLeft') { + if (confirmVisible) { + if (e.key === 'ArrowLeft') { + e.preventDefault() + setConfirmIndex(0) + } else if (e.key === 'ArrowRight') { + e.preventDefault() + setConfirmIndex(1) + } else if (e.key === 'Escape') { + e.preventDefault() + setConfirmVisible(false) + setConfirmIndex(0) + } else if (e.key === 'Enter') { + e.preventDefault() + if (confirmIndex === 1) { + void handleLogout() + } else { + setConfirmVisible(false) + setConfirmIndex(0) + } + } + return + } + if (e.key === 'ArrowLeft' || e.key === 'Escape') { e.preventDefault() onRequestSidebar() } else if (e.key === 'Enter') { - void handleLogout() + e.preventDefault() + setConfirmVisible(true) + setConfirmIndex(0) } } window.addEventListener('keydown', onKey) return () => window.removeEventListener('keydown', onKey) - }, [hasFocus, onRequestSidebar]) + }, [hasFocus, confirmVisible, confirmIndex, onRequestSidebar]) async function handleLogout(): Promise { await window.t4sd.auth.logout() @@ -92,9 +118,29 @@ export default function AccountScreen({ hasFocus, onRequestSidebar, onLogout }: )}

App-Version {window.t4sd.appVersion}

- + {confirmVisible ? ( +
+

Wirklich abmelden?

+
+ + +
+
+ ) : ( + + )} ) diff --git a/src/renderer/src/screens/SettingsScreen.tsx b/src/renderer/src/screens/SettingsScreen.tsx index dd3c137..201c8b6 100644 --- a/src/renderer/src/screens/SettingsScreen.tsx +++ b/src/renderer/src/screens/SettingsScreen.tsx @@ -42,15 +42,16 @@ function formatCacheSize(bytes: number): string { return `${(mb / 1024).toFixed(2)} GB` } -/** Total focusable rows: badge options + sidebar slider + badge gap slider + hls toggle + cache action */ +/** Total focusable rows: badge options + sidebar slider + badge gap slider + hls toggle + file logging toggle + cache action */ const SIDEBAR_SLIDER_ROW = OPTIONS.length const BADGE_GAP_SLIDER_ROW = OPTIONS.length + 1 const HLS_CACHE_TOGGLE_ROW = OPTIONS.length + 2 const CACHE_ACTION_ROW = OPTIONS.length + 3 -const TOTAL_ROWS = OPTIONS.length + 4 +const FILE_LOGGING_TOGGLE_ROW = OPTIONS.length + 4 +const TOTAL_ROWS = OPTIONS.length + 5 export default function SettingsScreen({ hasFocus, onRequestSidebar }: Props): JSX.Element { - const { settings, setStreamBadgeMode, setSidebarWidth, setBadgeGap, setHlsCacheEnabled } = useSettings() + const { settings, setStreamBadgeMode, setSidebarWidth, setBadgeGap, setHlsCacheEnabled, setFileLoggingEnabled } = useSettings() const [focusedIndex, setFocusedIndex] = useState(() => Math.max(0, OPTIONS.findIndex((o) => o.mode === settings.streamBadgeMode)) ) @@ -128,6 +129,8 @@ export default function SettingsScreen({ hasFocus, onRequestSidebar }: Props): J setBadgeGap(BADGE_GAP_DEFAULT) } else if (focusedIndex === HLS_CACHE_TOGGLE_ROW) { setHlsCacheEnabled(!settings.hlsCacheEnabled) + } else if (focusedIndex === FILE_LOGGING_TOGGLE_ROW) { + setFileLoggingEnabled(!settings.fileLoggingEnabled) } else if (focusedIndex === CACHE_ACTION_ROW) { void handleClearCache() } @@ -137,7 +140,7 @@ export default function SettingsScreen({ hasFocus, onRequestSidebar }: Props): J } window.addEventListener('keydown', onKey) return () => window.removeEventListener('keydown', onKey) - }, [hasFocus, focusedIndex, settings.sidebarWidth, settings.badgeGap, settings.hlsCacheEnabled, onRequestSidebar, setStreamBadgeMode, setSidebarWidth, setBadgeGap, setHlsCacheEnabled, handleClearCache]) + }, [hasFocus, focusedIndex, settings.sidebarWidth, settings.badgeGap, settings.hlsCacheEnabled, settings.fileLoggingEnabled, onRequestSidebar, setStreamBadgeMode, setSidebarWidth, setBadgeGap, setHlsCacheEnabled, setFileLoggingEnabled, handleClearCache]) const rowRefs = useRef>([]) @@ -150,6 +153,7 @@ export default function SettingsScreen({ hasFocus, onRequestSidebar }: Props): J const sidebarSliderFocused = hasFocus && focusedIndex === SIDEBAR_SLIDER_ROW const badgeGapSliderFocused = hasFocus && focusedIndex === BADGE_GAP_SLIDER_ROW const hlsCacheToggleFocused = hasFocus && focusedIndex === HLS_CACHE_TOGGLE_ROW + const fileLoggingToggleFocused = hasFocus && focusedIndex === FILE_LOGGING_TOGGLE_ROW const cacheActionFocused = hasFocus && focusedIndex === CACHE_ACTION_ROW return ( @@ -325,6 +329,30 @@ export default function SettingsScreen({ hasFocus, onRequestSidebar }: Props): J {clearing ? 'Wird geleert …' : 'Cache leeren'} + + {!hasFocus && ( diff --git a/src/renderer/src/styles/global.css b/src/renderer/src/styles/global.css index 8444b3c..850ad81 100644 --- a/src/renderer/src/styles/global.css +++ b/src/renderer/src/styles/global.css @@ -64,6 +64,12 @@ body, background: var(--color-accent); } +.btn--focused { + outline: 3px solid var(--color-focus); + outline-offset: 4px; + background: rgba(255, 255, 255, 0.14); +} + .btn--primary:hover { background: #a970ff; } @@ -1439,6 +1445,23 @@ body, text-align: center; } +.account__confirm { + display: flex; + flex-direction: column; + align-items: center; + gap: 20px; +} + +.account__confirm-text { + margin: 0; + font-size: 20px; +} + +.account__confirm-buttons { + display: flex; + gap: 16px; +} + /* ── Chapter Overlay (Modal) ──────────────────────────── */ .chapter-overlay { position: fixed; diff --git a/src/renderer/src/types/t4sd.d.ts b/src/renderer/src/types/t4sd.d.ts index c958958..1d9fbf4 100644 --- a/src/renderer/src/types/t4sd.d.ts +++ b/src/renderer/src/types/t4sd.d.ts @@ -40,6 +40,7 @@ export interface T4sdApi { getCacheSize: () => Promise clearCache: () => Promise setHlsCacheEnabled: (enabled: boolean) => Promise + setFileLoggingEnabled: (enabled: boolean) => Promise } auth: { isConfigured: () => Promise