mirror of
https://github.com/ThaUnknown/miru.git
synced 2026-04-19 18:02:16 +00:00
perf: disable pulse animation when not necessary
chore: improve activity checking code and stores
This commit is contained in:
parent
37266b88e8
commit
9d98c00dd6
5 changed files with 77 additions and 28 deletions
|
|
@ -1,5 +1,6 @@
|
|||
<script lang='ts'>
|
||||
import { page } from '$app/stores'
|
||||
import { activityState, idleState, isPlaying, lockedState } from '$lib/modules/idle'
|
||||
import { settings } from '$lib/modules/settings'
|
||||
import { sleep } from '$lib/utils'
|
||||
|
||||
|
|
@ -7,9 +8,6 @@
|
|||
export let root: HTMLDivElement
|
||||
|
||||
let visibilityState: DocumentVisibilityState
|
||||
let idleState = 'idle' as 'active' | 'idle'
|
||||
let lockedState = 'unlocked' as 'unlocked' | 'locked'
|
||||
let activityState = document.hasFocus() ? 'active' : 'inactive' as 'active' | 'inactive'
|
||||
|
||||
let isAnimating = false
|
||||
let isSpinning = false
|
||||
|
|
@ -30,7 +28,7 @@
|
|||
}
|
||||
|
||||
async function reset () {
|
||||
if (!isAnimating) return clearTimeout(timeout)
|
||||
if (!isAnimating) return
|
||||
root.style.transform = getComputedStyle(root).transform
|
||||
plate.style.transform = getComputedStyle(plate).transform
|
||||
isSpinning = isFlying = false
|
||||
|
|
@ -39,32 +37,20 @@
|
|||
await sleep(790)
|
||||
isAnimating = isSpinning = isFlying = false
|
||||
}
|
||||
// @ts-expect-error non-standard API
|
||||
const idleDetector = new IdleDetector()
|
||||
idleDetector.addEventListener('change', () => {
|
||||
idleState = idleDetector.userState
|
||||
lockedState = idleDetector.screenState
|
||||
})
|
||||
idleDetector.start({
|
||||
threshold: 60_000
|
||||
})
|
||||
|
||||
function checkIdleState (idleState: 'active' | 'idle', lockedState: 'unlocked' | 'locked', activityState: 'active' | 'inactive', visibilityState: DocumentVisibilityState) {
|
||||
if ($settings.idleAnimation === 'off') return reset()
|
||||
// don't waste resources
|
||||
if (lockedState === 'locked' || visibilityState === 'hidden') return reset()
|
||||
if (idleState === 'active' && activityState === 'active') return reset()
|
||||
if ($page.url.pathname === '/app/player/') return reset()
|
||||
$: active = $lockedState === 'locked' || visibilityState === 'hidden' || ($idleState === 'active' && $activityState === 'active') || $isPlaying
|
||||
|
||||
function checkIdleState (active: boolean, idleAnimation: 'fancy' | 'fast' | 'off') {
|
||||
clearTimeout(timeout)
|
||||
if (idleAnimation === 'off' || active) return reset()
|
||||
|
||||
timeout = setTimeout(start, 20_000)
|
||||
}
|
||||
|
||||
$: checkIdleState(idleState, lockedState, activityState, visibilityState)
|
||||
$: checkIdleState(active, $settings.idleAnimation)
|
||||
</script>
|
||||
|
||||
<svelte:document bind:visibilityState on:mouseleave={() => { if (!document.hasFocus()) activityState = 'inactive' }} on:mouseenter={() => { activityState = 'active' }} />
|
||||
<svelte:window on:focus={() => { activityState = 'active' }} on:blur={() => { activityState = 'inactive' }}
|
||||
on:pointermove={() => { idleState = 'active'; activityState = 'active' }} />
|
||||
<svelte:document bind:visibilityState />
|
||||
|
||||
<div class='preserve-3d absolute w-full h-full overflow-hidden flip backface-hidden backplate bg-black flex-col justify-center pointer-events-none hidden'
|
||||
bind:this={plate}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
import { hideBanner } from '$lib/components/ui/banner'
|
||||
import { server } from '$lib/modules/torrent'
|
||||
import { page } from '$app/stores'
|
||||
import { goto } from '$app/navigation'
|
||||
|
||||
onMount(async () => {
|
||||
await tick()
|
||||
|
|
@ -18,25 +19,29 @@
|
|||
$: active = resolveFilesPoorly($act)
|
||||
|
||||
$: isMiniplayer = $page.route.id !== '/app/player'
|
||||
|
||||
function openPlayer () {
|
||||
goto('/app/player/')
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class='w-full {isMiniplayer ? 'z-[100] max-w-80 absolute bottom-4 right-4 rounded-lg overflow-clip' : 'h-full'}'>
|
||||
<div class='w-full {isMiniplayer ? 'z-[49] max-w-80 absolute bottom-4 left-4 md:left-[unset] md:right-4 rounded-lg overflow-clip' : 'h-full'}'>
|
||||
{#if active}
|
||||
{#await active}
|
||||
<div class='w-full flex justify-center items-center bg-black aspect-video'>
|
||||
<div class='w-full flex justify-center items-center bg-black aspect-video cursor-pointer' on:click={openPlayer}>
|
||||
<div class='border-[3px] rounded-[50%] w-10 h-10 drop-shadow-lg border-transparent border-t-white animate-spin' />
|
||||
</div>
|
||||
{:then mediaInfo}
|
||||
{#if mediaInfo}
|
||||
<Mediahandler {mediaInfo} />
|
||||
{:else}
|
||||
<div class='w-full flex justify-center items-center bg-black aspect-video'>
|
||||
<div class='w-full flex justify-center items-center bg-black aspect-video cursor-pointer' on:click={openPlayer}>
|
||||
<div class='border-[3px] rounded-[50%] w-10 h-10 drop-shadow-lg border-transparent border-t-white animate-spin' />
|
||||
</div>
|
||||
{/if}
|
||||
{/await}
|
||||
{:else}
|
||||
<div class='w-full flex justify-center items-center bg-black aspect-video'>
|
||||
<div class='w-full flex justify-center items-center bg-black aspect-video cursor-pointer' on:click={openPlayer}>
|
||||
<div class='border-[3px] rounded-[50%] w-10 h-10 drop-shadow-lg border-transparent border-t-white animate-spin' />
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
import EpisodesList from '$lib/components/EpisodesList.svelte'
|
||||
import { episodes } from '$lib/modules/anizip'
|
||||
import { page } from '$app/stores'
|
||||
import { isPlaying } from '$lib/modules/idle'
|
||||
|
||||
export let mediaInfo: MediaInfo
|
||||
export let files: TorrentFile[]
|
||||
|
|
@ -64,6 +65,8 @@
|
|||
let paused = true
|
||||
const cast = false
|
||||
|
||||
$: $isPlaying = !paused
|
||||
|
||||
$: buffering = readyState < 3
|
||||
$: immersed = !buffering && !seeking && !paused && !ended && !$pictureInPictureElement
|
||||
|
||||
|
|
|
|||
|
|
@ -10,12 +10,20 @@
|
|||
import native from '$lib/modules/native'
|
||||
import client from '$lib/modules/auth/client'
|
||||
import * as Avatar from '$lib/components/ui/avatar'
|
||||
import { lockedState, idleState, activityState } from '$lib/modules/idle'
|
||||
import { page } from '$app/stores'
|
||||
|
||||
const auth = client.hasAuth
|
||||
|
||||
$: hasAuth = $auth
|
||||
|
||||
let visibilityState: DocumentVisibilityState
|
||||
|
||||
$: active = ($lockedState === 'locked' || visibilityState === 'hidden' || ($idleState === 'active' && $activityState === 'active')) && $page.route.id !== '/app/player'
|
||||
</script>
|
||||
|
||||
<svelte:document bind:visibilityState />
|
||||
|
||||
<BannerImage class='absolute top-0 left-0 w-14 -z-10 hidden md:block' />
|
||||
<img src='/logo_cropped.png' alt='logo' class='mb-3 cursor-pointer h-10 object-contain px-1 hidden md:block' loading='lazy' decoding='async' />
|
||||
<SidebarButton href='/app/home/'>
|
||||
|
|
@ -38,7 +46,7 @@
|
|||
</SidebarButton>
|
||||
<Button variant='ghost' on:click={() => native.openURL('https://github.com/sponsors/ThaUnknown/')} class='px-2 w-full relative mt-auto select:!bg-transparent text-[#fa68b6] select:text-[#fa68b6]'>
|
||||
<Heart size={18} fill='currentColor' class='absolute' />
|
||||
<Heart size={18} fill='currentColor' class='donate' />
|
||||
<Heart size={18} fill='currentColor' class={active ? 'donate' : 'hidden'} />
|
||||
</Button>
|
||||
<SidebarButton href='/app/settings/'>
|
||||
<Settings size={18} />
|
||||
|
|
|
|||
47
src/lib/modules/idle.ts
Normal file
47
src/lib/modules/idle.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import { derived, readable } from 'svelte/store'
|
||||
import { writable } from 'simple-store-svelte'
|
||||
|
||||
import { page } from '$app/stores'
|
||||
|
||||
export const activityState = readable<'active' | 'inactive'>(document.hasFocus() ? 'active' : 'inactive', set => {
|
||||
set(document.hasFocus() ? 'active' : 'inactive')
|
||||
const ctrl = new AbortController()
|
||||
|
||||
window.addEventListener('pointermove', () => set('active'), { signal: ctrl.signal })
|
||||
window.addEventListener('focus', () => set('active'), { signal: ctrl.signal })
|
||||
window.addEventListener('blur', () => set('inactive'), { signal: ctrl.signal })
|
||||
|
||||
document.addEventListener('mouseenter', () => set('active'), { signal: ctrl.signal })
|
||||
document.addEventListener('mouseleave', () => {
|
||||
if (!document.hasFocus()) set('inactive')
|
||||
}, { signal: ctrl.signal })
|
||||
|
||||
return () => ctrl.abort()
|
||||
})
|
||||
|
||||
// @ts-expect-error non-standard API
|
||||
const idleDetector = new IdleDetector()
|
||||
idleDetector.start({
|
||||
threshold: 60_000
|
||||
})
|
||||
|
||||
export const idleState = readable<'active' | 'idle'>(idleDetector.userState, set => {
|
||||
set(idleDetector.userState)
|
||||
const ctrl = new AbortController()
|
||||
|
||||
idleDetector.addEventListener('change', () => set(idleDetector.userState), { signal: ctrl.signal })
|
||||
window.addEventListener('pointermove', () => set('active'), { signal: ctrl.signal })
|
||||
|
||||
return () => ctrl.abort()
|
||||
})
|
||||
|
||||
export const lockedState = readable<'locked' | 'unlocked'>(idleDetector.screenState, set => {
|
||||
set(idleDetector.screenState)
|
||||
const ctrl = new AbortController()
|
||||
|
||||
idleDetector.addEventListener('change', () => set(idleDetector.screenState), { signal: ctrl.signal })
|
||||
|
||||
return () => ctrl.abort()
|
||||
})
|
||||
|
||||
export const isPlaying = writable(false)
|
||||
Loading…
Reference in a new issue