mirror of
https://github.com/Andre-Bzk/twitch4steamdeck.git
synced 2026-11-15 15:43:08 +01:00
feat: add file logging toggle and logout confirmation
File logging defaulted to always-on meant the log grew silently on every run with no way to turn it off. Changing the default to error-only and exposing the toggle in Settings lets users opt in only when debugging. The logout button previously triggered immediately without warning, making accidental logouts unrecoverable without re-running the device code flow. A confirmation step (Ja/Nein, gamepad-navigable) prevents that.
This commit is contained in:
+1
-1
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -37,7 +37,8 @@ const api = {
|
||||
quit: (): Promise<void> => ipcRenderer.invoke(IPC.appQuit),
|
||||
getCacheSize: (): Promise<number> => ipcRenderer.invoke(IPC.appGetCacheSize),
|
||||
clearCache: (): Promise<void> => ipcRenderer.invoke(IPC.appClearCache),
|
||||
setHlsCacheEnabled: (enabled: boolean): Promise<void> => ipcRenderer.invoke(IPC.appSetHlsCacheEnabled, enabled)
|
||||
setHlsCacheEnabled: (enabled: boolean): Promise<void> => ipcRenderer.invoke(IPC.appSetHlsCacheEnabled, enabled),
|
||||
setFileLoggingEnabled: (enabled: boolean): Promise<void> => ipcRenderer.invoke(IPC.appSetFileLoggingEnabled, enabled)
|
||||
},
|
||||
auth: {
|
||||
isConfigured: (): Promise<boolean> => ipcRenderer.invoke(IPC.authConfigured),
|
||||
|
||||
@@ -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<SettingsCtx | null>(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 (
|
||||
<SettingsContext.Provider value={{ settings, setStreamBadgeMode, setSidebarWidth, setBadgeGap, setHlsCacheEnabled }}>
|
||||
<SettingsContext.Provider value={{ settings, setStreamBadgeMode, setSidebarWidth, setBadgeGap, setHlsCacheEnabled, setFileLoggingEnabled }}>
|
||||
{children}
|
||||
</SettingsContext.Provider>
|
||||
)
|
||||
|
||||
@@ -13,10 +13,12 @@ export default function AccountScreen({ hasFocus, onRequestSidebar, onLogout }:
|
||||
const [user, setUser] = useState<OwnUserInfo | null>(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 (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<void> {
|
||||
await window.t4sd.auth.logout()
|
||||
@@ -92,9 +118,29 @@ export default function AccountScreen({ hasFocus, onRequestSidebar, onLogout }:
|
||||
)}
|
||||
</div>
|
||||
<p className="account__version">App-Version {window.t4sd.appVersion}</p>
|
||||
<button ref={logoutBtnRef} className="btn" onClick={handleLogout}>
|
||||
{confirmVisible ? (
|
||||
<div className="account__confirm">
|
||||
<p className="account__confirm-text">Wirklich abmelden?</p>
|
||||
<div className="account__confirm-buttons">
|
||||
<button
|
||||
className={['btn', confirmIndex === 0 ? 'btn--focused' : ''].filter(Boolean).join(' ')}
|
||||
onClick={() => { setConfirmVisible(false); setConfirmIndex(0) }}
|
||||
>
|
||||
Nein
|
||||
</button>
|
||||
<button
|
||||
className={['btn', confirmIndex === 1 ? 'btn--focused' : ''].filter(Boolean).join(' ')}
|
||||
onClick={() => { void handleLogout() }}
|
||||
>
|
||||
Ja
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<button ref={logoutBtnRef} className="btn" onClick={() => { setConfirmVisible(true); setConfirmIndex(0) }}>
|
||||
Abmelden
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -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<Array<HTMLElement | null>>([])
|
||||
|
||||
@@ -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'}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
ref={(el) => { rowRefs.current[FILE_LOGGING_TOGGLE_ROW] = el }}
|
||||
className={[
|
||||
'settings-option',
|
||||
settings.fileLoggingEnabled ? 'settings-option--active' : '',
|
||||
fileLoggingToggleFocused ? 'settings-option--focused' : ''
|
||||
].filter(Boolean).join(' ')}
|
||||
onClick={() => {
|
||||
setFocusedIndex(FILE_LOGGING_TOGGLE_ROW)
|
||||
setFileLoggingEnabled(!settings.fileLoggingEnabled)
|
||||
}}
|
||||
tabIndex={fileLoggingToggleFocused ? 0 : -1}
|
||||
>
|
||||
<span className="settings-option__radio" aria-hidden="true">
|
||||
{settings.fileLoggingEnabled ? '●' : '○'}
|
||||
</span>
|
||||
<span className="settings-option__label">Datei-Logging</span>
|
||||
<span className="settings-option__preview">
|
||||
{settings.fileLoggingEnabled
|
||||
? 'Aktiv — Info-Logs in main.log'
|
||||
: 'Inaktiv — nur Fehler werden geloggt (empfohlen)'}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{!hasFocus && (
|
||||
|
||||
@@ -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;
|
||||
|
||||
Vendored
+1
@@ -40,6 +40,7 @@ export interface T4sdApi {
|
||||
getCacheSize: () => Promise<number>
|
||||
clearCache: () => Promise<void>
|
||||
setHlsCacheEnabled: (enabled: boolean) => Promise<void>
|
||||
setFileLoggingEnabled: (enabled: boolean) => Promise<void>
|
||||
}
|
||||
auth: {
|
||||
isConfigured: () => Promise<boolean>
|
||||
|
||||
Reference in New Issue
Block a user