feat: auto-pause chat on scroll, resume at bottom
This commit is contained in:
+214
-216
@@ -1,216 +1,214 @@
|
|||||||
import Store from 'electron-store';
|
import Store from 'electron-store';
|
||||||
|
|
||||||
export interface Keybind {
|
export interface Keybind {
|
||||||
key: string;
|
key: string;
|
||||||
ctrl: boolean;
|
ctrl: boolean;
|
||||||
shift: boolean;
|
shift: boolean;
|
||||||
alt: boolean;
|
alt: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SavedAccount {
|
export interface SavedAccount {
|
||||||
label: string;
|
label: string;
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AppConfig {
|
export interface AppConfig {
|
||||||
window: {
|
window: {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
x: number | undefined;
|
x: number | undefined;
|
||||||
y: number | undefined;
|
y: number | undefined;
|
||||||
maximized: boolean;
|
maximized: boolean;
|
||||||
fullscreen: boolean;
|
fullscreen: boolean;
|
||||||
};
|
};
|
||||||
performance: {
|
performance: {
|
||||||
fpsUnlocked: boolean;
|
fpsUnlocked: boolean;
|
||||||
hardwareAccel: boolean;
|
hardwareAccel: boolean;
|
||||||
gpuPreference: 'high-performance' | 'low-power' | 'default';
|
gpuPreference: 'high-performance' | 'low-power' | 'default';
|
||||||
cpuThrottleGame: number;
|
cpuThrottleGame: number;
|
||||||
cpuThrottleMenu: number;
|
cpuThrottleMenu: number;
|
||||||
processPriority: string;
|
processPriority: string;
|
||||||
};
|
};
|
||||||
game: {
|
game: {
|
||||||
lastServer: string;
|
lastServer: string;
|
||||||
socialTabBehaviour: 'New Window' | 'Same Window';
|
socialTabBehaviour: 'New Window' | 'Same Window';
|
||||||
joinAsSpectator: boolean;
|
joinAsSpectator: boolean;
|
||||||
rawInput: boolean;
|
rawInput: boolean;
|
||||||
betterChat: boolean;
|
betterChat: boolean;
|
||||||
chatHistorySize: number;
|
chatHistorySize: number;
|
||||||
showPing: boolean;
|
showPing: boolean;
|
||||||
hpEnemyCounter: boolean;
|
hpEnemyCounter: boolean;
|
||||||
};
|
};
|
||||||
swapper: {
|
swapper: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
path: string;
|
path: string;
|
||||||
};
|
};
|
||||||
matchmaker: {
|
matchmaker: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
regions: string[];
|
regions: string[];
|
||||||
gamemodes: string[];
|
gamemodes: string[];
|
||||||
maps: string[];
|
maps: string[];
|
||||||
minPlayers: number;
|
minPlayers: number;
|
||||||
maxPlayers: number;
|
maxPlayers: number;
|
||||||
minRemainingTime: number;
|
minRemainingTime: number;
|
||||||
openServerBrowser: boolean;
|
openServerBrowser: boolean;
|
||||||
autoJoin: boolean;
|
autoJoin: boolean;
|
||||||
};
|
};
|
||||||
keybinds: {
|
keybinds: {
|
||||||
reload: Keybind;
|
reload: Keybind;
|
||||||
newMatch: Keybind;
|
newMatch: Keybind;
|
||||||
copyGameLink: Keybind;
|
copyGameLink: Keybind;
|
||||||
joinFromClipboard: Keybind;
|
joinFromClipboard: Keybind;
|
||||||
devTools: Keybind;
|
devTools: Keybind;
|
||||||
matchmaker: Keybind;
|
matchmaker: Keybind;
|
||||||
matchmakerAccept: Keybind;
|
matchmakerAccept: Keybind;
|
||||||
matchmakerCancel: Keybind;
|
matchmakerCancel: Keybind;
|
||||||
pauseChat: Keybind;
|
fullscreenToggle: Keybind;
|
||||||
fullscreenToggle: Keybind;
|
};
|
||||||
};
|
userscripts: {
|
||||||
userscripts: {
|
enabled: boolean;
|
||||||
enabled: boolean;
|
path: string;
|
||||||
path: string;
|
};
|
||||||
};
|
ui: {
|
||||||
ui: {
|
showExitButton: boolean;
|
||||||
showExitButton: boolean;
|
deathscreenAnimation: boolean;
|
||||||
deathscreenAnimation: boolean;
|
hideMenuPopups: boolean;
|
||||||
hideMenuPopups: boolean;
|
menuTimer: boolean;
|
||||||
menuTimer: boolean;
|
doublePing: boolean;
|
||||||
doublePing: boolean;
|
cssTheme: string;
|
||||||
cssTheme: string;
|
loadingTheme: string;
|
||||||
loadingTheme: string;
|
backgroundUrl: string;
|
||||||
backgroundUrl: string;
|
showChangelog: boolean;
|
||||||
showChangelog: boolean;
|
lastSeenVersion: string;
|
||||||
lastSeenVersion: string;
|
};
|
||||||
};
|
discord: {
|
||||||
discord: {
|
enabled: boolean;
|
||||||
enabled: boolean;
|
};
|
||||||
};
|
translator: {
|
||||||
translator: {
|
enabled: boolean;
|
||||||
enabled: boolean;
|
targetLanguage: string;
|
||||||
targetLanguage: string;
|
showLanguageTag: boolean;
|
||||||
showLanguageTag: boolean;
|
};
|
||||||
};
|
advanced: {
|
||||||
advanced: {
|
removeUselessFeatures: boolean;
|
||||||
removeUselessFeatures: boolean;
|
gpuRasterizing: boolean;
|
||||||
gpuRasterizing: boolean;
|
helpfulFlags: boolean;
|
||||||
helpfulFlags: boolean;
|
increaseLimits: boolean;
|
||||||
increaseLimits: boolean;
|
lowLatency: boolean;
|
||||||
lowLatency: boolean;
|
experimentalFlags: boolean;
|
||||||
experimentalFlags: boolean;
|
angleBackend: string;
|
||||||
angleBackend: string;
|
verboseLogging: boolean;
|
||||||
verboseLogging: boolean;
|
};
|
||||||
};
|
accounts: SavedAccount[];
|
||||||
accounts: SavedAccount[];
|
tabWindow: {
|
||||||
tabWindow: {
|
width: number;
|
||||||
width: number;
|
height: number;
|
||||||
height: number;
|
x: number | undefined;
|
||||||
x: number | undefined;
|
y: number | undefined;
|
||||||
y: number | undefined;
|
maximized: boolean;
|
||||||
maximized: boolean;
|
};
|
||||||
};
|
}
|
||||||
}
|
|
||||||
|
export const DEFAULT_KEYBINDS: AppConfig['keybinds'] = {
|
||||||
export const DEFAULT_KEYBINDS: AppConfig['keybinds'] = {
|
reload: { key: 'F5', ctrl: false, shift: false, alt: false },
|
||||||
reload: { key: 'F5', ctrl: false, shift: false, alt: false },
|
newMatch: { key: 'F4', ctrl: false, shift: false, alt: false },
|
||||||
newMatch: { key: 'F4', ctrl: false, shift: false, alt: false },
|
copyGameLink: { key: 'l', ctrl: true, shift: false, alt: false },
|
||||||
copyGameLink: { key: 'l', ctrl: true, shift: false, alt: false },
|
joinFromClipboard: { key: 'j', ctrl: true, shift: false, alt: false },
|
||||||
joinFromClipboard: { key: 'j', ctrl: true, shift: false, alt: false },
|
devTools: { key: 'F12', ctrl: false, shift: false, alt: false },
|
||||||
devTools: { key: 'F12', ctrl: false, shift: false, alt: false },
|
matchmaker: { key: 'F6', ctrl: false, shift: false, alt: false },
|
||||||
matchmaker: { key: 'F6', ctrl: false, shift: false, alt: false },
|
matchmakerAccept: { key: 'Enter', ctrl: false, shift: false, alt: false },
|
||||||
matchmakerAccept: { key: 'Enter', ctrl: false, shift: false, alt: false },
|
matchmakerCancel: { key: 'Escape', ctrl: false, shift: false, alt: false },
|
||||||
matchmakerCancel: { key: 'Escape', ctrl: false, shift: false, alt: false },
|
fullscreenToggle: { key: 'F11', 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<AppConfig>({
|
||||||
|
name: 'krunker-civilian-config',
|
||||||
export const config = new Store<AppConfig>({
|
defaults: {
|
||||||
name: 'krunker-civilian-config',
|
window: {
|
||||||
defaults: {
|
width: 1600,
|
||||||
window: {
|
height: 900,
|
||||||
width: 1600,
|
x: undefined,
|
||||||
height: 900,
|
y: undefined,
|
||||||
x: undefined,
|
maximized: false,
|
||||||
y: undefined,
|
fullscreen: false,
|
||||||
maximized: false,
|
},
|
||||||
fullscreen: false,
|
performance: {
|
||||||
},
|
fpsUnlocked: true,
|
||||||
performance: {
|
hardwareAccel: true,
|
||||||
fpsUnlocked: true,
|
gpuPreference: 'high-performance',
|
||||||
hardwareAccel: true,
|
cpuThrottleGame: 1,
|
||||||
gpuPreference: 'high-performance',
|
cpuThrottleMenu: 1.5,
|
||||||
cpuThrottleGame: 1,
|
processPriority: 'Normal',
|
||||||
cpuThrottleMenu: 1.5,
|
},
|
||||||
processPriority: 'Normal',
|
game: {
|
||||||
},
|
lastServer: '',
|
||||||
game: {
|
socialTabBehaviour: 'New Window',
|
||||||
lastServer: '',
|
joinAsSpectator: false,
|
||||||
socialTabBehaviour: 'New Window',
|
rawInput: true,
|
||||||
joinAsSpectator: false,
|
betterChat: true,
|
||||||
rawInput: true,
|
chatHistorySize: 200,
|
||||||
betterChat: true,
|
showPing: true,
|
||||||
chatHistorySize: 200,
|
hpEnemyCounter: true,
|
||||||
showPing: true,
|
},
|
||||||
hpEnemyCounter: true,
|
swapper: {
|
||||||
},
|
enabled: true,
|
||||||
swapper: {
|
path: '',
|
||||||
enabled: true,
|
},
|
||||||
path: '',
|
matchmaker: {
|
||||||
},
|
enabled: true,
|
||||||
matchmaker: {
|
regions: [],
|
||||||
enabled: true,
|
gamemodes: [],
|
||||||
regions: [],
|
maps: [],
|
||||||
gamemodes: [],
|
minPlayers: 1,
|
||||||
maps: [],
|
maxPlayers: 6,
|
||||||
minPlayers: 1,
|
minRemainingTime: 120,
|
||||||
maxPlayers: 6,
|
openServerBrowser: true,
|
||||||
minRemainingTime: 120,
|
autoJoin: false,
|
||||||
openServerBrowser: true,
|
},
|
||||||
autoJoin: false,
|
keybinds: DEFAULT_KEYBINDS,
|
||||||
},
|
userscripts: {
|
||||||
keybinds: DEFAULT_KEYBINDS,
|
enabled: true,
|
||||||
userscripts: {
|
path: '',
|
||||||
enabled: true,
|
},
|
||||||
path: '',
|
ui: {
|
||||||
},
|
showExitButton: true,
|
||||||
ui: {
|
deathscreenAnimation: true,
|
||||||
showExitButton: true,
|
hideMenuPopups: false,
|
||||||
deathscreenAnimation: true,
|
menuTimer: true,
|
||||||
hideMenuPopups: false,
|
doublePing: true,
|
||||||
menuTimer: true,
|
cssTheme: 'disabled',
|
||||||
doublePing: true,
|
loadingTheme: 'disabled',
|
||||||
cssTheme: 'disabled',
|
backgroundUrl: '',
|
||||||
loadingTheme: 'disabled',
|
showChangelog: true,
|
||||||
backgroundUrl: '',
|
lastSeenVersion: '',
|
||||||
showChangelog: true,
|
},
|
||||||
lastSeenVersion: '',
|
discord: {
|
||||||
},
|
enabled: false,
|
||||||
discord: {
|
},
|
||||||
enabled: false,
|
translator: {
|
||||||
},
|
enabled: true,
|
||||||
translator: {
|
targetLanguage: 'en',
|
||||||
enabled: true,
|
showLanguageTag: true,
|
||||||
targetLanguage: 'en',
|
},
|
||||||
showLanguageTag: true,
|
advanced: {
|
||||||
},
|
removeUselessFeatures: true,
|
||||||
advanced: {
|
gpuRasterizing: false,
|
||||||
removeUselessFeatures: true,
|
helpfulFlags: true,
|
||||||
gpuRasterizing: false,
|
increaseLimits: false,
|
||||||
helpfulFlags: true,
|
lowLatency: false,
|
||||||
increaseLimits: false,
|
experimentalFlags: false,
|
||||||
lowLatency: false,
|
angleBackend: 'default',
|
||||||
experimentalFlags: false,
|
verboseLogging: false,
|
||||||
angleBackend: 'default',
|
},
|
||||||
verboseLogging: false,
|
accounts: [],
|
||||||
},
|
tabWindow: {
|
||||||
accounts: [],
|
width: 1280,
|
||||||
tabWindow: {
|
height: 720,
|
||||||
width: 1280,
|
x: undefined,
|
||||||
height: 720,
|
y: undefined,
|
||||||
x: undefined,
|
maximized: true,
|
||||||
y: undefined,
|
},
|
||||||
maximized: true,
|
},
|
||||||
},
|
});
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|||||||
+24
-2
@@ -16,8 +16,11 @@ let observer: MutationObserver | null = null;
|
|||||||
let historyMax = 0;
|
let historyMax = 0;
|
||||||
let betterChatEnabled = false;
|
let betterChatEnabled = false;
|
||||||
let reInsertGuard = false;
|
let reInsertGuard = false;
|
||||||
|
let scrollPaused = false;
|
||||||
let _con: SavedConsole | null = null;
|
let _con: SavedConsole | null = null;
|
||||||
|
|
||||||
|
const SCROLL_BOTTOM_THRESHOLD = 30; // px from bottom to consider "at bottom"
|
||||||
|
|
||||||
function isChatMessage(node: Node): node is HTMLElement {
|
function isChatMessage(node: Node): node is HTMLElement {
|
||||||
return node.nodeType === 1 && (node as HTMLElement).id?.startsWith('chatMsg_');
|
return node.nodeType === 1 && (node as HTMLElement).id?.startsWith('chatMsg_');
|
||||||
}
|
}
|
||||||
@@ -83,18 +86,37 @@ function handleMutations(mutations: MutationRecord[]): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-scroll unless paused
|
// Auto-scroll to bottom unless the user has scrolled up
|
||||||
if (chatList && !chatList.classList.contains('kpc-chat-paused')) {
|
if (chatList && !scrollPaused) {
|
||||||
chatList.scrollTop = chatList.scrollHeight;
|
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 {
|
function tryAttach(): boolean {
|
||||||
chatList = document.getElementById('chatList');
|
chatList = document.getElementById('chatList');
|
||||||
if (!chatList) return false;
|
if (!chatList) return false;
|
||||||
|
|
||||||
observer = new MutationObserver(handleMutations);
|
observer = new MutationObserver(handleMutations);
|
||||||
observer.observe(chatList, { childList: true });
|
observer.observe(chatList, { childList: true });
|
||||||
|
|
||||||
|
chatList.addEventListener('scroll', updatePauseState, { passive: true });
|
||||||
|
|
||||||
_con?.log('[KCC-Chat] Observer attached to #chatList');
|
_con?.log('[KCC-Chat] Observer attached to #chatList');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user