mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-14 01:39:56 -05:00
Chore: ModalBlur Component
This commit is contained in:
@@ -1,13 +1,15 @@
|
||||
import { readerState } from "$lib/state/reader.svelte";
|
||||
import { fetchPages } from "./pageLoader";
|
||||
import { cancelQueuedFetches, revokeBlobUrl } from "$lib/core/cache/imageCache";
|
||||
import { clearResolvedUrlCache } from "$lib/core/cache/pageCache";
|
||||
import { clearResolvedUrlCache, clearPageCache } from "$lib/core/cache/pageCache";
|
||||
|
||||
export function scheduleResumeDismiss() {
|
||||
setTimeout(() => { readerState.resumeFading = true; }, 1500);
|
||||
setTimeout(() => { readerState.resumeVisible = false; readerState.resumeFading = false; }, 2500);
|
||||
}
|
||||
|
||||
let prefetchedChapterId: number | null = null;
|
||||
|
||||
export async function loadChapter(
|
||||
id: number,
|
||||
useBlob: boolean,
|
||||
@@ -23,11 +25,16 @@ export async function loadChapter(
|
||||
cancelQueuedFetches();
|
||||
if (useBlob) {
|
||||
clearResolvedUrlCache();
|
||||
// revoke blob URLs for all loaded pages so the GPU can release their textures
|
||||
for (const url of readerState.pageUrls) revokeBlobUrl(url);
|
||||
for (const strip of readerState.stripChapters) {
|
||||
for (const url of strip.urls) revokeBlobUrl(url);
|
||||
}
|
||||
if (prefetchedChapterId !== null && prefetchedChapterId !== id) {
|
||||
const prefetchedUrls = await fetchPages(prefetchedChapterId, false).catch(() => [] as string[]);
|
||||
for (const url of prefetchedUrls) revokeBlobUrl(url);
|
||||
clearPageCache(prefetchedChapterId);
|
||||
}
|
||||
prefetchedChapterId = null;
|
||||
}
|
||||
|
||||
startAtLastPage.current = false;
|
||||
@@ -51,10 +58,13 @@ export async function loadChapter(
|
||||
else if (resumeTo > 1) readerState.pageNumber = Math.min(resumeTo, urls.length || resumeTo);
|
||||
readerState.pageReady = true;
|
||||
readerState.loading = false;
|
||||
if (adjacent.next) fetchPages(adjacent.next.id, useBlob, ctrl.signal).catch(() => {});
|
||||
if (adjacent.next) {
|
||||
prefetchedChapterId = adjacent.next.id;
|
||||
fetchPages(adjacent.next.id, useBlob, ctrl.signal).catch(() => {});
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
if (ctrl.signal.aborted) return;
|
||||
readerState.error = e instanceof Error ? e.message : String(e);
|
||||
readerState.loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,7 @@
|
||||
/* ── Backdrop & Modal Shell ───────────────────────────────────────── */
|
||||
.s-backdrop {
|
||||
position: fixed; inset: 0;
|
||||
background: rgba(0,0,0,0.6);
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
|
||||
z-index: var(--z-settings);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
animation: s-fade-in 0.14s ease both;
|
||||
@@ -29,10 +27,7 @@
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
animation: s-scale-in 0.2s cubic-bezier(0.16,1,0.3,1) both;
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(255,255,255,0.04) inset,
|
||||
0 24px 80px rgba(0,0,0,0.7),
|
||||
0 8px 24px rgba(0,0,0,0.4);
|
||||
box-shadow: 0 0 0 1px var(--border-dim), 0 24px 64px rgba(0,0,0,0.6);
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +41,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
overflow-y: auto;
|
||||
overflow-y: hidden;
|
||||
border-radius: var(--radius-2xl) 0 0 var(--radius-2xl);
|
||||
}
|
||||
|
||||
@@ -140,7 +135,6 @@
|
||||
.s-content-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import ContentSettings from './sections/ContentSettings.svelte'
|
||||
import AboutSettings from './sections/AboutSettings.svelte'
|
||||
import DevtoolsSettings from './sections/DevToolsSettings.svelte'
|
||||
import ModalBlur from '$lib/components/shared/ui/ModalBlur.svelte'
|
||||
|
||||
interface Props { onclose?: () => void; onOpenThemeEditor?: (id?: string | null) => void }
|
||||
let { onclose, onOpenThemeEditor }: Props = $props()
|
||||
@@ -111,6 +112,7 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<ModalBlur />
|
||||
<div class="s-backdrop" role="presentation" tabindex="-1"
|
||||
onclick={(e) => { if (e.target === e.currentTarget) close() }}
|
||||
onkeydown={(e) => { if (e.key === 'Escape') { e.stopPropagation(); close() } }}>
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
} from "$lib/state/series.svelte";
|
||||
import { app } from "$lib/state/app.svelte";
|
||||
import type { Manga, Chapter, Category } from "$lib/types";
|
||||
import ModalBlur from '$lib/components/shared/ui/ModalBlur.svelte'
|
||||
|
||||
|
||||
let manga: Manga | null = $state(null);
|
||||
@@ -353,6 +354,7 @@
|
||||
</script>
|
||||
|
||||
{#if seriesState.previewManga}
|
||||
<ModalBlur blur={4} dim={0.72} />
|
||||
<div
|
||||
class="backdrop"
|
||||
role="button"
|
||||
@@ -679,10 +681,8 @@
|
||||
<style>
|
||||
.backdrop {
|
||||
position: fixed; inset: 0;
|
||||
background: rgba(0,0,0,0.72);
|
||||
z-index: var(--z-settings);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
|
||||
animation: fadeIn 0.12s ease both;
|
||||
}
|
||||
.modal {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<script lang="ts">
|
||||
let {
|
||||
blur = 8,
|
||||
dim = 0.6,
|
||||
zIndex = 'var(--z-settings)',
|
||||
animate = true,
|
||||
}: {
|
||||
blur?: number
|
||||
dim?: number
|
||||
zIndex?: string | number
|
||||
animate?: boolean
|
||||
} = $props()
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="modal-blur"
|
||||
class:animate
|
||||
style="--blur:{blur}px; --dim:{dim}; --z:{zIndex}"
|
||||
></div>
|
||||
|
||||
<style>
|
||||
.modal-blur {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
backdrop-filter: blur(var(--blur));
|
||||
-webkit-backdrop-filter: blur(var(--blur));
|
||||
background: rgba(0, 0, 0, var(--dim));
|
||||
pointer-events: none;
|
||||
z-index: var(--z);
|
||||
}
|
||||
|
||||
.modal-blur.animate {
|
||||
animation: blur-in 0.14s ease both;
|
||||
}
|
||||
|
||||
@keyframes blur-in {
|
||||
from { opacity: 0 }
|
||||
to { opacity: 1 }
|
||||
}
|
||||
</style>
|
||||
@@ -10,6 +10,7 @@
|
||||
import { markManyRead } from "$lib/request-manager/chapters";
|
||||
import type { Tracker, TrackRecord, TrackSearch } from "$lib/types";
|
||||
import type { Chapter } from "$lib/types";
|
||||
import ModalBlur from '$lib/components/shared/ui/ModalBlur.svelte'
|
||||
|
||||
let { mangaId, mangaTitle, onClose }: {
|
||||
mangaId: number;
|
||||
@@ -250,6 +251,7 @@
|
||||
}
|
||||
}} />
|
||||
|
||||
<ModalBlur blur={4} dim={0.68} />
|
||||
<div class="backdrop" role="presentation" onclick={(e) => { if (e.target === e.currentTarget) onClose(); }}>
|
||||
<div class="modal" role="dialog" aria-label="Tracking">
|
||||
|
||||
@@ -497,6 +499,7 @@
|
||||
{#if confirmUnbindId !== null}
|
||||
{@const rec = records.find(r => r.id === confirmUnbindId)}
|
||||
{@const trk = rec ? trackerFor(rec.trackerId) : null}
|
||||
<ModalBlur blur={2} dim={0.45} zIndex="calc(var(--z-settings) + 1)" />
|
||||
<div class="confirm-backdrop" role="button" tabindex="-1" aria-label="Cancel"
|
||||
onclick={() => confirmUnbindId = null}
|
||||
onkeydown={(e) => { if (e.key === "Escape") confirmUnbindId = null; }}>
|
||||
@@ -515,10 +518,9 @@
|
||||
|
||||
<style>
|
||||
.backdrop {
|
||||
position: fixed; inset: 0; background: rgba(0,0,0,0.68);
|
||||
position: fixed; inset: 0;
|
||||
z-index: var(--z-settings);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
|
||||
animation: fadeIn 0.12s ease both;
|
||||
}
|
||||
.modal {
|
||||
@@ -646,7 +648,7 @@
|
||||
.result-row:hover:not(:disabled) .result-action { color: var(--accent-fg); border-color: var(--accent-dim); background: var(--accent-muted); }
|
||||
.result-action-on { color: var(--accent-fg) !important; border-color: var(--accent-dim) !important; background: var(--accent-muted) !important; }
|
||||
|
||||
.confirm-backdrop { position: fixed; inset: 0; z-index: calc(var(--z-settings) + 1); background: rgba(0,0,0,0.45); backdrop-filter: blur(2px); display: flex; align-items: center; justify-content: center; animation: fadeIn 0.1s ease both; }
|
||||
.confirm-backdrop { position: fixed; inset: 0; z-index: calc(var(--z-settings) + 1); display: flex; align-items: center; justify-content: center; animation: fadeIn 0.1s ease both; }
|
||||
.confirm-modal { background: var(--bg-surface); border: 1px solid var(--border-dim); border-radius: var(--radius-xl); padding: var(--sp-5); width: 260px; display: flex; flex-direction: column; gap: var(--sp-3); box-shadow: 0 16px 48px rgba(0,0,0,0.5); animation: scaleIn 0.15s ease both; }
|
||||
.confirm-title { font-size: var(--text-sm); font-weight: var(--weight-medium); color: var(--text-primary); margin: 0; }
|
||||
.confirm-body { font-family: var(--font-ui); font-size: var(--text-xs); color: var(--text-faint); line-height: 1.5; margin: 0; letter-spacing: var(--tracking-wide); }
|
||||
|
||||
Reference in New Issue
Block a user