From 215dfc827e1f8787f368366a346379529a3f1696 Mon Sep 17 00:00:00 2001 From: bigjakk Date: Fri, 10 Apr 2026 13:21:18 -0700 Subject: [PATCH] feat: auto-pause chat on scroll, resume at bottom --- src/main/config.ts | 430 ++++++++++++++++++++++---------------------- src/preload/chat.ts | 26 ++- 2 files changed, 238 insertions(+), 218 deletions(-) diff --git a/src/main/config.ts b/src/main/config.ts index d5ae4e7..b383ff0 100644 --- a/src/main/config.ts +++ b/src/main/config.ts @@ -1,216 +1,214 @@ -import Store from 'electron-store'; - -export interface Keybind { - key: string; - ctrl: boolean; - shift: boolean; - alt: boolean; -} - -export interface SavedAccount { - label: string; - username: string; - password: string; -} - -export interface AppConfig { - window: { - width: number; - height: number; - x: number | undefined; - y: number | undefined; - maximized: boolean; - fullscreen: boolean; - }; - performance: { - fpsUnlocked: boolean; - hardwareAccel: boolean; - gpuPreference: 'high-performance' | 'low-power' | 'default'; - cpuThrottleGame: number; - cpuThrottleMenu: number; - processPriority: string; - }; - game: { - lastServer: string; - socialTabBehaviour: 'New Window' | 'Same Window'; - joinAsSpectator: boolean; - rawInput: boolean; - betterChat: boolean; - chatHistorySize: number; - showPing: boolean; - hpEnemyCounter: boolean; - }; - swapper: { - enabled: boolean; - path: string; - }; - matchmaker: { - enabled: boolean; - regions: string[]; - gamemodes: string[]; - maps: string[]; - minPlayers: number; - maxPlayers: number; - minRemainingTime: number; - openServerBrowser: boolean; - autoJoin: boolean; - }; - keybinds: { - reload: Keybind; - newMatch: Keybind; - copyGameLink: Keybind; - joinFromClipboard: Keybind; - devTools: Keybind; - matchmaker: Keybind; - matchmakerAccept: Keybind; - matchmakerCancel: Keybind; - pauseChat: Keybind; - fullscreenToggle: Keybind; - }; - userscripts: { - enabled: boolean; - path: string; - }; - ui: { - showExitButton: boolean; - deathscreenAnimation: boolean; - hideMenuPopups: boolean; - menuTimer: boolean; - doublePing: boolean; - cssTheme: string; - loadingTheme: string; - backgroundUrl: string; - showChangelog: boolean; - lastSeenVersion: string; - }; - discord: { - enabled: boolean; - }; - translator: { - enabled: boolean; - targetLanguage: string; - showLanguageTag: boolean; - }; - advanced: { - removeUselessFeatures: boolean; - gpuRasterizing: boolean; - helpfulFlags: boolean; - increaseLimits: boolean; - lowLatency: boolean; - experimentalFlags: boolean; - angleBackend: string; - verboseLogging: boolean; - }; - accounts: SavedAccount[]; - tabWindow: { - width: number; - height: number; - x: number | undefined; - y: number | undefined; - maximized: boolean; - }; -} - -export const DEFAULT_KEYBINDS: AppConfig['keybinds'] = { - reload: { key: 'F5', ctrl: false, shift: false, alt: false }, - newMatch: { key: 'F4', ctrl: false, shift: false, alt: false }, - copyGameLink: { key: 'l', ctrl: true, shift: false, alt: false }, - joinFromClipboard: { key: 'j', ctrl: true, shift: false, alt: false }, - devTools: { key: 'F12', ctrl: false, shift: false, alt: false }, - matchmaker: { key: 'F6', ctrl: false, shift: false, alt: false }, - matchmakerAccept: { key: 'Enter', ctrl: false, shift: false, alt: false }, - matchmakerCancel: { key: 'Escape', ctrl: false, shift: false, alt: false }, - pauseChat: { key: 'F10', ctrl: false, shift: false, alt: false }, - fullscreenToggle: { key: 'F11', ctrl: false, shift: false, alt: false }, -}; - - -export const config = new Store({ - name: 'krunker-civilian-config', - defaults: { - window: { - width: 1600, - height: 900, - x: undefined, - y: undefined, - maximized: false, - fullscreen: false, - }, - performance: { - fpsUnlocked: true, - hardwareAccel: true, - gpuPreference: 'high-performance', - cpuThrottleGame: 1, - cpuThrottleMenu: 1.5, - processPriority: 'Normal', - }, - game: { - lastServer: '', - socialTabBehaviour: 'New Window', - joinAsSpectator: false, - rawInput: true, - betterChat: true, - chatHistorySize: 200, - showPing: true, - hpEnemyCounter: true, - }, - swapper: { - enabled: true, - path: '', - }, - matchmaker: { - enabled: true, - regions: [], - gamemodes: [], - maps: [], - minPlayers: 1, - maxPlayers: 6, - minRemainingTime: 120, - openServerBrowser: true, - autoJoin: false, - }, - keybinds: DEFAULT_KEYBINDS, - userscripts: { - enabled: true, - path: '', - }, - ui: { - showExitButton: true, - deathscreenAnimation: true, - hideMenuPopups: false, - menuTimer: true, - doublePing: true, - cssTheme: 'disabled', - loadingTheme: 'disabled', - backgroundUrl: '', - showChangelog: true, - lastSeenVersion: '', - }, - discord: { - enabled: false, - }, - translator: { - enabled: true, - targetLanguage: 'en', - showLanguageTag: true, - }, - advanced: { - removeUselessFeatures: true, - gpuRasterizing: false, - helpfulFlags: true, - increaseLimits: false, - lowLatency: false, - experimentalFlags: false, - angleBackend: 'default', - verboseLogging: false, - }, - accounts: [], - tabWindow: { - width: 1280, - height: 720, - x: undefined, - y: undefined, - maximized: true, - }, - }, -}); +import Store from 'electron-store'; + +export interface Keybind { + key: string; + ctrl: boolean; + shift: boolean; + alt: boolean; +} + +export interface SavedAccount { + label: string; + username: string; + password: string; +} + +export interface AppConfig { + window: { + width: number; + height: number; + x: number | undefined; + y: number | undefined; + maximized: boolean; + fullscreen: boolean; + }; + performance: { + fpsUnlocked: boolean; + hardwareAccel: boolean; + gpuPreference: 'high-performance' | 'low-power' | 'default'; + cpuThrottleGame: number; + cpuThrottleMenu: number; + processPriority: string; + }; + game: { + lastServer: string; + socialTabBehaviour: 'New Window' | 'Same Window'; + joinAsSpectator: boolean; + rawInput: boolean; + betterChat: boolean; + chatHistorySize: number; + showPing: boolean; + hpEnemyCounter: boolean; + }; + swapper: { + enabled: boolean; + path: string; + }; + matchmaker: { + enabled: boolean; + regions: string[]; + gamemodes: string[]; + maps: string[]; + minPlayers: number; + maxPlayers: number; + minRemainingTime: number; + openServerBrowser: boolean; + autoJoin: boolean; + }; + keybinds: { + reload: Keybind; + newMatch: Keybind; + copyGameLink: Keybind; + joinFromClipboard: Keybind; + devTools: Keybind; + matchmaker: Keybind; + matchmakerAccept: Keybind; + matchmakerCancel: Keybind; + fullscreenToggle: Keybind; + }; + userscripts: { + enabled: boolean; + path: string; + }; + ui: { + showExitButton: boolean; + deathscreenAnimation: boolean; + hideMenuPopups: boolean; + menuTimer: boolean; + doublePing: boolean; + cssTheme: string; + loadingTheme: string; + backgroundUrl: string; + showChangelog: boolean; + lastSeenVersion: string; + }; + discord: { + enabled: boolean; + }; + translator: { + enabled: boolean; + targetLanguage: string; + showLanguageTag: boolean; + }; + advanced: { + removeUselessFeatures: boolean; + gpuRasterizing: boolean; + helpfulFlags: boolean; + increaseLimits: boolean; + lowLatency: boolean; + experimentalFlags: boolean; + angleBackend: string; + verboseLogging: boolean; + }; + accounts: SavedAccount[]; + tabWindow: { + width: number; + height: number; + x: number | undefined; + y: number | undefined; + maximized: boolean; + }; +} + +export const DEFAULT_KEYBINDS: AppConfig['keybinds'] = { + reload: { key: 'F5', ctrl: false, shift: false, alt: false }, + newMatch: { key: 'F4', ctrl: false, shift: false, alt: false }, + copyGameLink: { key: 'l', ctrl: true, shift: false, alt: false }, + joinFromClipboard: { key: 'j', ctrl: true, shift: false, alt: false }, + devTools: { key: 'F12', ctrl: false, shift: false, alt: false }, + matchmaker: { key: 'F6', ctrl: false, shift: false, alt: false }, + matchmakerAccept: { key: 'Enter', ctrl: false, shift: false, alt: false }, + matchmakerCancel: { key: 'Escape', ctrl: false, shift: false, alt: false }, + fullscreenToggle: { key: 'F11', ctrl: false, shift: false, alt: false }, +}; + + +export const config = new Store({ + name: 'krunker-civilian-config', + defaults: { + window: { + width: 1600, + height: 900, + x: undefined, + y: undefined, + maximized: false, + fullscreen: false, + }, + performance: { + fpsUnlocked: true, + hardwareAccel: true, + gpuPreference: 'high-performance', + cpuThrottleGame: 1, + cpuThrottleMenu: 1.5, + processPriority: 'Normal', + }, + game: { + lastServer: '', + socialTabBehaviour: 'New Window', + joinAsSpectator: false, + rawInput: true, + betterChat: true, + chatHistorySize: 200, + showPing: true, + hpEnemyCounter: true, + }, + swapper: { + enabled: true, + path: '', + }, + matchmaker: { + enabled: true, + regions: [], + gamemodes: [], + maps: [], + minPlayers: 1, + maxPlayers: 6, + minRemainingTime: 120, + openServerBrowser: true, + autoJoin: false, + }, + keybinds: DEFAULT_KEYBINDS, + userscripts: { + enabled: true, + path: '', + }, + ui: { + showExitButton: true, + deathscreenAnimation: true, + hideMenuPopups: false, + menuTimer: true, + doublePing: true, + cssTheme: 'disabled', + loadingTheme: 'disabled', + backgroundUrl: '', + showChangelog: true, + lastSeenVersion: '', + }, + discord: { + enabled: false, + }, + translator: { + enabled: true, + targetLanguage: 'en', + showLanguageTag: true, + }, + advanced: { + removeUselessFeatures: true, + gpuRasterizing: false, + helpfulFlags: true, + increaseLimits: false, + lowLatency: false, + experimentalFlags: false, + angleBackend: 'default', + verboseLogging: false, + }, + accounts: [], + tabWindow: { + width: 1280, + height: 720, + x: undefined, + y: undefined, + maximized: true, + }, + }, +}); diff --git a/src/preload/chat.ts b/src/preload/chat.ts index 165effe..c9a67a9 100644 --- a/src/preload/chat.ts +++ b/src/preload/chat.ts @@ -16,8 +16,11 @@ let observer: MutationObserver | null = null; let historyMax = 0; let betterChatEnabled = false; let reInsertGuard = false; +let scrollPaused = false; let _con: SavedConsole | null = null; +const SCROLL_BOTTOM_THRESHOLD = 30; // px from bottom to consider "at bottom" + function isChatMessage(node: Node): node is HTMLElement { return node.nodeType === 1 && (node as HTMLElement).id?.startsWith('chatMsg_'); } @@ -83,18 +86,37 @@ function handleMutations(mutations: MutationRecord[]): void { } } - // Auto-scroll unless paused - if (chatList && !chatList.classList.contains('kpc-chat-paused')) { + // Auto-scroll to bottom unless the user has scrolled up + if (chatList && !scrollPaused) { chatList.scrollTop = chatList.scrollHeight; } } +function isNearBottom(el: HTMLElement): boolean { + return el.scrollHeight - el.scrollTop - el.clientHeight <= SCROLL_BOTTOM_THRESHOLD; +} + +function updatePauseState(): void { + if (!chatList) return; + const atBottom = isNearBottom(chatList); + if (scrollPaused && atBottom) { + scrollPaused = false; + chatList.classList.remove('kpc-chat-paused'); + } else if (!scrollPaused && !atBottom) { + scrollPaused = true; + chatList.classList.add('kpc-chat-paused'); + } +} + function tryAttach(): boolean { chatList = document.getElementById('chatList'); if (!chatList) return false; observer = new MutationObserver(handleMutations); observer.observe(chatList, { childList: true }); + + chatList.addEventListener('scroll', updatePauseState, { passive: true }); + _con?.log('[KCC-Chat] Observer attached to #chatList'); return true; }