fix: make weel and touch passive

This commit is contained in:
frozenKelp
2026-06-09 19:10:52 +05:30
parent 0e7ff1a27c
commit 6d33fb7ae1
2 changed files with 50 additions and 33 deletions
+31 -17
View File
@@ -1,4 +1,5 @@
import { platformService } from '$lib/platform-service'
import { settingsState } from '$lib/state/settings.svelte'
import type { Manga } from '$lib/types/manga'
import type { Chapter } from '$lib/types/chapter'
@@ -9,11 +10,8 @@ const APP_BUTTONS = [
const FALLBACK_IMAGE = 'moku_logo'
let sessionStart: number | null = null
function isPublicUrl(url: string | null | undefined): boolean {
return typeof url === 'string' && url.startsWith('https://')
}
let sessionStart: number | null = null
let activeMangaId: number | null = null
function trunc(s: string, max = 128): string {
return s.length <= max ? s : `${s.slice(0, max - 1)}`
@@ -24,6 +22,31 @@ function formatChapter(chapter: Chapter): string {
return `Chapter ${Number.isInteger(n) ? n : n.toFixed(1)}`
}
// Suwayomi always returns the proxy path (/api/v1/manga/{id}/thumbnail), never the raw CDN URL.
// The proxy URL is only useful to Discord when the server is publicly reachable over HTTPS.
// For localhost setups cover art falls back to the app logo until Suwayomi exposes rawThumbnailUrl.
function resolveCoverUrl(manga: Manga): string {
const serverBase = (settingsState.settings.serverUrl ?? '').replace(/\/$/, '')
if (!serverBase.startsWith('https://')) return FALLBACK_IMAGE
const path = manga.thumbnailUrl?.startsWith('/') ? manga.thumbnailUrl : `/api/v1/manga/${manga.id}/thumbnail`
return `${serverBase}${path}`
}
function buildPresence(manga: Manga, chapter: Chapter, coverUrl: string) {
return {
details: trunc(manga.title),
state: `${formatChapter(chapter)} · Reading`,
timestamps: { start: sessionStart ?? Date.now() },
assets: {
largeImage: coverUrl,
largeText: trunc(manga.title),
smallImage: FALLBACK_IMAGE,
smallText: 'Moku',
},
buttons: APP_BUTTONS,
}
}
export async function initRpc(): Promise<void> {
if (!platformService.isSupported('discord-rpc')) return
sessionStart = Date.now()
@@ -36,18 +59,9 @@ export async function destroyRpc(): Promise<void> {
export async function setReading(manga: Manga, chapter: Chapter): Promise<void> {
if (!platformService.isSupported('discord-rpc')) return
await platformService.setDiscordPresence({
details: trunc(manga.title),
state: `${formatChapter(chapter)} · Reading`,
timestamps: { start: sessionStart ?? Date.now() },
assets: {
largeImage: isPublicUrl(manga.thumbnailUrl) ? manga.thumbnailUrl : FALLBACK_IMAGE,
largeText: trunc(manga.title),
smallImage: FALLBACK_IMAGE,
smallText: 'Moku',
},
buttons: APP_BUTTONS,
})
activeMangaId = manga.id
await platformService.setDiscordPresence(buildPresence(manga, chapter, resolveCoverUrl(manga)))
}
export async function setIdle(): Promise<void> {
+19 -16
View File
@@ -158,30 +158,33 @@
if (appState.status !== 'ready') return
// capture phase so events from any component — including modals — reset the timer
const onActivity = () => resetIdleTimer()
document.addEventListener('mousemove', onActivity, true)
document.addEventListener('keydown', onActivity, true)
document.addEventListener('touchstart', onActivity, true)
document.addEventListener('touchmove', onActivity, true) // sustained touch-scroll in reader
document.addEventListener('wheel', onActivity, true) // mouse-wheel / trackpad scroll in reader
document.addEventListener('click', onActivity, true)
document.addEventListener('mousemove', onActivity, true)
document.addEventListener('keydown', onActivity, true)
document.addEventListener('touchstart', onActivity, true)
// passive:true tells the browser it can render scroll frames without waiting for the handler
document.addEventListener('touchmove', onActivity, { capture: true, passive: true })
document.addEventListener('wheel', onActivity, { capture: true, passive: true })
document.addEventListener('click', onActivity, true)
return () => {
document.removeEventListener('mousemove', onActivity, true)
document.removeEventListener('keydown', onActivity, true)
document.removeEventListener('touchstart', onActivity, true)
document.removeEventListener('touchmove', onActivity, true)
document.removeEventListener('wheel', onActivity, true)
document.removeEventListener('click', onActivity, true)
document.removeEventListener('mousemove', onActivity, true)
document.removeEventListener('keydown', onActivity, true)
document.removeEventListener('touchstart', onActivity, true)
document.removeEventListener('touchmove', onActivity, { capture: true })
document.removeEventListener('wheel', onActivity, { capture: true })
document.removeEventListener('click', onActivity, true)
}
})
function resetIdleTimer() {
if (idleTimer) clearTimeout(idleTimer)
appState.idleSplash = false
if (idleTimer) { clearTimeout(idleTimer); idleTimer = null }
if (appState.idleSplash) appState.idleSplash = false
// read the setting live so changes take effect without a restart
const timeoutMs = (settingsState.settings.idleTimeoutMin ?? 5) * 60_000
// 0 means "Never" — skip the timer entirely
const mins = settingsState.settings.idleTimeoutMin ?? 5
if (mins === 0) return
idleTimer = setTimeout(() => {
if (appState.status === 'ready') appState.idleSplash = true
}, timeoutMs)
}, mins * 60_000)
}
function onIdleDismiss() { appState.idleSplash = false; resetIdleTimer() }