mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-13 09:19:56 -05:00
Fix: Local-Source Popular Query + App-Pin Flow
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte'
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||
import logoUrl from '$lib/assets/moku-icon-splash.svg'
|
||||
import { onMount } from 'svelte'
|
||||
import logoUrl from '$lib/assets/moku-icon-splash.svg'
|
||||
import { platformService } from '$lib/platform-service'
|
||||
|
||||
const isTauri = platformService.platform === 'tauri'
|
||||
|
||||
interface Props {
|
||||
mode?: 'loading' | 'idle' | 'locked'
|
||||
@@ -78,6 +80,7 @@
|
||||
|
||||
$effect(() => {
|
||||
if (mode === 'loading' && !failed && !notConfigured && !ringFull) {
|
||||
if (!isTauri) return // no ring animation on web; probe outcome drives exit
|
||||
animStart = null
|
||||
animPhase = 1
|
||||
animFrame = requestAnimationFrame(animateRing)
|
||||
@@ -278,28 +281,45 @@
|
||||
}
|
||||
|
||||
function mountCanvas(el: HTMLCanvasElement) {
|
||||
const win = getCurrentWindow()
|
||||
const ctx = el.getContext('2d')!
|
||||
let live: RenderState | null = null
|
||||
let lastLogW = 0, lastLogH = 0, lastScale = 0, buildGen = 0
|
||||
|
||||
async function syncSize() {
|
||||
function applySize(logW: number, logH: number, scale: number) {
|
||||
const gen = ++buildGen
|
||||
const [phys, scale] = await Promise.all([win.innerSize(), win.scaleFactor()])
|
||||
if (gen !== buildGen) return
|
||||
const logW = phys.width / scale, logH = phys.height / scale
|
||||
if (logW <= 0 || logH <= 0) return
|
||||
if (logW === lastLogW && logH === lastLogH && scale === lastScale) return
|
||||
lastLogW = logW; lastLogH = logH; lastScale = scale
|
||||
const built = buildCards(logW, logH)
|
||||
const stamps = built.cards.map(c => buildStamp(c, scale))
|
||||
const vig = buildVignette(logW, logH, scale)
|
||||
el.width = phys.width; el.height = phys.height
|
||||
live = { cards: built.cards, trigs: built.trigs, stamps, vignette: vig, CW: phys.width, CH: phys.height, scale }
|
||||
el.width = Math.round(logW * scale)
|
||||
el.height = Math.round(logH * scale)
|
||||
if (gen === buildGen) live = { cards: built.cards, trigs: built.trigs, stamps, vignette: vig, CW: el.width, CH: el.height, scale }
|
||||
}
|
||||
|
||||
const ro = new ResizeObserver(() => syncSize())
|
||||
ro.observe(el)
|
||||
syncSize()
|
||||
let extraCleanup: (() => void) | undefined
|
||||
|
||||
if (isTauri) {
|
||||
let tauriRo: ResizeObserver | undefined
|
||||
let tauriUnlisten: (() => void) | undefined
|
||||
import('@tauri-apps/api/window').then(({ getCurrentWindow }) => {
|
||||
const win = getCurrentWindow()
|
||||
const doSync = () => Promise.all([win.innerSize(), win.scaleFactor()])
|
||||
.then(([phys, scale]) => applySize(phys.width / scale, phys.height / scale, scale))
|
||||
doSync()
|
||||
tauriRo = new ResizeObserver(() => doSync())
|
||||
tauriRo.observe(el)
|
||||
win.onFocusChanged(() => doSync()).then(u => { tauriUnlisten = u })
|
||||
})
|
||||
extraCleanup = () => { tauriRo?.disconnect(); tauriUnlisten?.() }
|
||||
} else {
|
||||
const syncWeb = () => applySize(el.clientWidth, el.clientHeight, window.devicePixelRatio || 1)
|
||||
const ro = new ResizeObserver(() => syncWeb())
|
||||
ro.observe(el)
|
||||
requestAnimationFrame(() => syncWeb())
|
||||
extraCleanup = () => ro.disconnect()
|
||||
}
|
||||
|
||||
let raf = 0, t0 = -1, paused = false
|
||||
|
||||
@@ -307,9 +327,11 @@
|
||||
if (paused) { raf = 0; return }
|
||||
raf = requestAnimationFrame(frame)
|
||||
if (!live) return
|
||||
const { cards, trigs, stamps, vignette, CW, CH, scale } = live
|
||||
if (CW <= 0 || CH <= 0 || vignette.width <= 0 || vignette.height <= 0) return
|
||||
if (stamps.some(s => s.width <= 0 || s.height <= 0)) return
|
||||
if (t0 < 0) t0 = now
|
||||
if (showFps) tickFps(now)
|
||||
const { cards, trigs, stamps, vignette, CW, CH, scale } = live
|
||||
drawFrame(ctx, (now - t0) / 1000, CW, CH, scale, cards, trigs, stamps, vignette)
|
||||
}
|
||||
|
||||
@@ -318,14 +340,11 @@
|
||||
function onVis() { document.hidden ? pause() : resume() }
|
||||
|
||||
document.addEventListener('visibilitychange', onVis)
|
||||
const unlistenFocus = win.onFocusChanged(({ payload: focused }) => { focused ? resume() : pause() })
|
||||
|
||||
raf = requestAnimationFrame(frame)
|
||||
return () => {
|
||||
cancelAnimationFrame(raf)
|
||||
ro.disconnect()
|
||||
extraCleanup?.()
|
||||
document.removeEventListener('visibilitychange', onVis)
|
||||
unlistenFocus.then(f => f())
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { ArrowLeft, MagnifyingGlass, GearSix, Swap, Funnel, Check } from "phosphor-svelte";
|
||||
import { ArrowLeft, MagnifyingGlass, GearSix, Swap, Funnel, Check, CircleNotch } from "phosphor-svelte";
|
||||
import Thumbnail from "$lib/components/shared/manga/Thumbnail.svelte";
|
||||
import { resolvedCover } from "$lib/core/cover/coverResolver";
|
||||
import { getAdapter } from "$lib/request-manager";
|
||||
@@ -25,9 +25,22 @@
|
||||
|
||||
let { pkgName, extensionName, iconUrl, cols, cropCovers, statsAlways, anims, sources, onBack, onSettings }: Props = $props();
|
||||
|
||||
const isLocal = pkgName === '__local__';
|
||||
|
||||
// ── Library mode state ──────────────────────────────────────────────
|
||||
let groups: SourceLibrary[] = $state([]);
|
||||
let sourceNodes: SourceNode[] = $state([]);
|
||||
|
||||
// ── Local/browse mode state ──────────────────────────────────────────
|
||||
let localItems: any[] = $state([]);
|
||||
let localPage: number = $state(1);
|
||||
let localHasNext: boolean = $state(false);
|
||||
let localLoadingMore: boolean = $state(false);
|
||||
|
||||
// ── Shared state ─────────────────────────────────────────────────────
|
||||
let loading = $state(true);
|
||||
let search = $state("");
|
||||
let searchInput = $state("");
|
||||
|
||||
type ContentFilter = "unread" | "downloaded";
|
||||
let activeFilters = $state<Partial<Record<ContentFilter, boolean>>>({});
|
||||
@@ -37,35 +50,80 @@
|
||||
|
||||
let migrateTarget: { sourceId: string; sourceName: string; iconUrl: string; manga: LibraryManga[] } | null = $state(null);
|
||||
|
||||
const allManga = $derived(groups.flatMap(g => g.manga));
|
||||
// ── Derived filtered lists ────────────────────────────────────────────
|
||||
const allManga = $derived(isLocal ? localItems : groups.flatMap(g => g.manga));
|
||||
|
||||
const filtered = $derived((() => {
|
||||
let items = allManga;
|
||||
const q = search.trim().toLowerCase();
|
||||
if (q) items = items.filter(m => m.title.toLowerCase().includes(q));
|
||||
if (activeFilters.unread) items = items.filter(m => m.unreadCount > 0);
|
||||
if (activeFilters.downloaded) items = items.filter(m => m.downloadCount > 0);
|
||||
if (q && !isLocal) items = items.filter((m: any) => m.title.toLowerCase().includes(q));
|
||||
if (!isLocal) {
|
||||
if (activeFilters.unread) items = items.filter((m: any) => m.unreadCount > 0);
|
||||
if (activeFilters.downloaded) items = items.filter((m: any) => m.downloadCount > 0);
|
||||
}
|
||||
return items;
|
||||
})());
|
||||
|
||||
let sourceNodes: SourceNode[] = $state([]);
|
||||
|
||||
$effect(() => { load(); });
|
||||
|
||||
async function load() {
|
||||
loading = true;
|
||||
try {
|
||||
const [libData, srcData] = await Promise.all([
|
||||
getAdapter().getMangaList({}).then(r => ({ mangas: { nodes: r.items as any } })),
|
||||
getAdapter().getSources().then(nodes => ({ sources: { nodes } })),
|
||||
]);
|
||||
sourceNodes = srcData.sources.nodes;
|
||||
groups = libraryByExtension(libData.mangas.nodes, srcData.sources.nodes, pkgName);
|
||||
if (isLocal) {
|
||||
localPage = 1;
|
||||
localItems = [];
|
||||
const result = await getAdapter().browseSource('0', 1);
|
||||
localItems = result.items;
|
||||
localHasNext = result.hasNextPage;
|
||||
localPage = 1;
|
||||
} else {
|
||||
const [libData, srcData] = await Promise.all([
|
||||
getAdapter().getMangaList({}).then(r => ({ mangas: { nodes: r.items as any } })),
|
||||
getAdapter().getSources().then(nodes => ({ sources: { nodes } })),
|
||||
]);
|
||||
sourceNodes = srcData.sources.nodes;
|
||||
groups = libraryByExtension(libData.mangas.nodes, srcData.sources.nodes, pkgName);
|
||||
}
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadMoreLocal() {
|
||||
if (localLoadingMore || !localHasNext) return;
|
||||
localLoadingMore = true;
|
||||
try {
|
||||
const next = localPage + 1;
|
||||
const result = await getAdapter().browseSource('0', next);
|
||||
localItems = [...localItems, ...result.items];
|
||||
localHasNext = result.hasNextPage;
|
||||
localPage = next;
|
||||
} finally {
|
||||
localLoadingMore = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function searchLocal() {
|
||||
const q = searchInput.trim();
|
||||
if (!q) { load(); return; }
|
||||
loading = true;
|
||||
try {
|
||||
const result = await getAdapter().searchSource('0', q, 1);
|
||||
localItems = result.items;
|
||||
localHasNext = result.hasNextPage;
|
||||
localPage = 1;
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
search = q;
|
||||
}
|
||||
|
||||
function onSearchKeydown(e: KeyboardEvent) {
|
||||
if (!isLocal) return;
|
||||
if (e.key === 'Enter') searchLocal();
|
||||
if (e.key === 'Escape') { searchInput = ''; search = ''; load(); }
|
||||
}
|
||||
|
||||
function toggleFilter(f: ContentFilter) {
|
||||
activeFilters = { ...activeFilters, [f]: !activeFilters[f] };
|
||||
}
|
||||
@@ -108,58 +166,72 @@
|
||||
<Thumbnail src={iconUrl} alt={extensionName} class="header-icon" onerror={(e) => ((e.target as HTMLImageElement).style.display = "none")} />
|
||||
{/if}
|
||||
<div class="title-block">
|
||||
<span class="eyebrow">In Library</span>
|
||||
<span class="eyebrow">{isLocal ? 'Local Source' : 'In Library'}</span>
|
||||
<span class="title">{extensionName}</span>
|
||||
</div>
|
||||
{#if !loading}
|
||||
<span class="count-badge">{filtered.length}{filtered.length !== allManga.length ? ` / ${allManga.length}` : ""}</span>
|
||||
<span class="count-badge">
|
||||
{isLocal ? allManga.length + (localHasNext ? '+' : '') : `${filtered.length}${filtered.length !== allManga.length ? ` / ${allManga.length}` : ''}`}
|
||||
</span>
|
||||
{/if}
|
||||
<div class="header-right">
|
||||
<div class="search-wrap">
|
||||
<MagnifyingGlass size={12} class="search-icon" weight="light" />
|
||||
<input class="search" placeholder="Search" bind:value={search} autocomplete="off" />
|
||||
</div>
|
||||
|
||||
<div class="filter-wrap">
|
||||
<button
|
||||
class="filter-btn"
|
||||
class:filter-btn-active={hasActiveFilters}
|
||||
title="Filter"
|
||||
onclick={() => filterOpen = !filterOpen}
|
||||
>
|
||||
<Funnel size={13} weight={hasActiveFilters ? "fill" : "bold"} />
|
||||
</button>
|
||||
{#if filterOpen}
|
||||
<div class="filter-panel" role="menu">
|
||||
<div class="filter-panel-header">
|
||||
<span class="panel-heading">Filter</span>
|
||||
{#if hasActiveFilters}
|
||||
<button class="panel-clear-btn" onclick={clearFilters}>Clear all</button>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="panel-divider"></div>
|
||||
<p class="panel-label">Content</p>
|
||||
{#each CONTENT_FILTERS as [f, label]}
|
||||
<button
|
||||
class="panel-item"
|
||||
class:panel-item-active={activeFilters[f]}
|
||||
role="menuitem"
|
||||
onclick={() => toggleFilter(f)}
|
||||
>
|
||||
<span class="panel-check" class:panel-check-on={activeFilters[f]}>
|
||||
{#if activeFilters[f]}<Check size={9} weight="bold" />{/if}
|
||||
</span>
|
||||
{label}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{#if isLocal}
|
||||
<input
|
||||
class="search"
|
||||
placeholder="Search…"
|
||||
bind:value={searchInput}
|
||||
autocomplete="off"
|
||||
onkeydown={onSearchKeydown}
|
||||
/>
|
||||
{:else}
|
||||
<input class="search" placeholder="Search" bind:value={search} autocomplete="off" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if sources.length > 0}
|
||||
<button class="settings-btn" onclick={onSettings} title="Extension settings">
|
||||
<GearSix size={14} weight="bold" />
|
||||
</button>
|
||||
{#if !isLocal}
|
||||
<div class="filter-wrap">
|
||||
<button
|
||||
class="filter-btn"
|
||||
class:filter-btn-active={hasActiveFilters}
|
||||
title="Filter"
|
||||
onclick={() => filterOpen = !filterOpen}
|
||||
>
|
||||
<Funnel size={13} weight={hasActiveFilters ? "fill" : "bold"} />
|
||||
</button>
|
||||
{#if filterOpen}
|
||||
<div class="filter-panel" role="menu">
|
||||
<div class="filter-panel-header">
|
||||
<span class="panel-heading">Filter</span>
|
||||
{#if hasActiveFilters}
|
||||
<button class="panel-clear-btn" onclick={clearFilters}>Clear all</button>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="panel-divider"></div>
|
||||
<p class="panel-label">Content</p>
|
||||
{#each CONTENT_FILTERS as [f, label]}
|
||||
<button
|
||||
class="panel-item"
|
||||
class:panel-item-active={activeFilters[f]}
|
||||
role="menuitem"
|
||||
onclick={() => toggleFilter(f)}
|
||||
>
|
||||
<span class="panel-check" class:panel-check-on={activeFilters[f]}>
|
||||
{#if activeFilters[f]}<Check size={9} weight="bold" />{/if}
|
||||
</span>
|
||||
{label}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if sources.length > 0}
|
||||
<button class="settings-btn" onclick={onSettings} title="Extension settings">
|
||||
<GearSix size={14} weight="bold" />
|
||||
</button>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -176,10 +248,14 @@
|
||||
</div>
|
||||
{:else if filtered.length === 0}
|
||||
<div class="empty">
|
||||
{allManga.length === 0 ? "Nothing from this extension is in your library." : "No matches."}
|
||||
{isLocal
|
||||
? 'No manga found in local source. Add manga folders to your local source directory.'
|
||||
: allManga.length === 0
|
||||
? 'Nothing from this extension is in your library.'
|
||||
: 'No matches.'}
|
||||
</div>
|
||||
{:else}
|
||||
{#if groups.length > 1}
|
||||
{#if !isLocal && groups.length > 1}
|
||||
<div class="source-groups">
|
||||
{#each groups as group}
|
||||
<div class="source-group-header">
|
||||
@@ -192,7 +268,7 @@
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else if groups.length === 1}
|
||||
{:else if !isLocal && groups.length === 1}
|
||||
<div class="single-source-bar">
|
||||
<span class="source-group-name">{groups[0].displayName}</span>
|
||||
<button class="migrate-btn" onclick={() => openMigrate(groups[0])} title="Migrate this source">
|
||||
@@ -214,23 +290,38 @@
|
||||
style="object-fit:{cropCovers ? 'cover' : 'contain'}"
|
||||
draggable="false"
|
||||
/>
|
||||
<div class="card-info-overlay" class:anim={anims} class:instant={!anims} class:always={statsAlways}>
|
||||
<div class="overlay-badges">
|
||||
{#if isCompleted}
|
||||
<span class="badge badge-done">✓ Done</span>
|
||||
{:else if m.unreadCount}
|
||||
<span class="badge badge-unread">{m.unreadCount} new</span>
|
||||
{/if}
|
||||
{#if m.downloadCount}
|
||||
<span class="badge badge-dl">↓ {m.downloadCount}</span>
|
||||
{/if}
|
||||
{#if !isLocal}
|
||||
<div class="card-info-overlay" class:anim={anims} class:instant={!anims} class:always={statsAlways}>
|
||||
<div class="overlay-badges">
|
||||
{#if isCompleted}
|
||||
<span class="badge badge-done">✓ Done</span>
|
||||
{:else if m.unreadCount}
|
||||
<span class="badge badge-unread">{m.unreadCount} new</span>
|
||||
{/if}
|
||||
{#if m.downloadCount}
|
||||
<span class="badge badge-dl">↓ {m.downloadCount}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<p class="card-title">{m.title}</p>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if isLocal && localHasNext}
|
||||
<div class="load-more">
|
||||
<button class="load-more-btn" onclick={loadMoreLocal} disabled={localLoadingMore}>
|
||||
{#if localLoadingMore}
|
||||
<CircleNotch size={13} weight="light" class="anim-spin" />
|
||||
Loading…
|
||||
{:else}
|
||||
Load more
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -330,7 +421,12 @@
|
||||
.cover-skeleton { aspect-ratio: 2/3; border-radius: var(--radius-md); }
|
||||
.title-skeleton { height: 12px; margin-top: var(--sp-2); width: 80%; border-radius: var(--radius-sm); }
|
||||
|
||||
.empty { display: flex; align-items: center; justify-content: center; height: 60%; color: var(--text-muted); font-size: var(--text-sm); }
|
||||
.empty { display: flex; align-items: center; justify-content: center; height: 60%; color: var(--text-muted); font-size: var(--text-sm); text-align: center; padding: 0 var(--sp-6); }
|
||||
|
||||
.load-more { display: flex; justify-content: center; padding: var(--sp-4) 0; }
|
||||
.load-more-btn { display: flex; align-items: center; gap: var(--sp-2); font-family: var(--font-ui); font-size: var(--text-xs); letter-spacing: var(--tracking-wide); padding: 7px 20px; border-radius: var(--radius-md); background: var(--bg-raised); color: var(--text-muted); border: 1px solid var(--border-dim); cursor: pointer; transition: color var(--t-base), border-color var(--t-base), background var(--t-base); }
|
||||
.load-more-btn:hover:not(:disabled) { color: var(--text-primary); border-color: var(--border-strong); }
|
||||
.load-more-btn:disabled { opacity: 0.5; cursor: default; }
|
||||
|
||||
@keyframes fadeIn { from { opacity: 0 } to { opacity: 1 } }
|
||||
</style>
|
||||
@@ -28,7 +28,7 @@
|
||||
}
|
||||
|
||||
let extensions: Extension[] = $state([]);
|
||||
let localMangaCount = $state(0);
|
||||
let localMangaCount = $state<string>("0");
|
||||
let loading = $state(true);
|
||||
let refreshing = $state(false);
|
||||
let filter = $state<Filter>("installed");
|
||||
@@ -84,8 +84,10 @@
|
||||
}
|
||||
|
||||
async function loadLocalManga() {
|
||||
const d = await Promise.resolve(null);
|
||||
|
||||
try {
|
||||
const r = await getAdapter().browseSource('0', 1)
|
||||
localMangaCount = r.hasNextPage ? r.items.length + '+' : String(r.items.length)
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async function fetchFromRepo() {
|
||||
@@ -338,7 +340,7 @@
|
||||
{:else}
|
||||
<div class="list">
|
||||
{#if showLocal}
|
||||
<div class="local-row">
|
||||
<div class="local-row" style="cursor:pointer" onclick={() => libraryTarget = { pkgName: '__local__', extensionName: 'Local Source', iconUrl: '' }}>
|
||||
<div class="local-icon"><HardDrives size={18} weight="bold" /></div>
|
||||
<div class="info">
|
||||
<span class="name">Local Source</span>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
import { settingsState, updateSettings } from '$lib/state/settings.svelte'
|
||||
import { platformService } from '$lib/platform-service'
|
||||
|
||||
const isTauri = platformService.platform === 'tauri'
|
||||
|
||||
interface Props {
|
||||
selectOpen: string | null
|
||||
closingSelect: string | null
|
||||
@@ -67,6 +69,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if isTauri}
|
||||
<label class="s-row">
|
||||
<div class="s-row-info"><span class="s-label">Auto-start server</span><span class="s-desc">Launch tachidesk-server when Moku opens</span></div>
|
||||
<button role="switch" aria-checked={settingsState.settings.autoStartServer} aria-label="Auto-start server"
|
||||
@@ -84,6 +87,7 @@
|
||||
<span class="s-toggle-thumb"></span>
|
||||
</button>
|
||||
</label>
|
||||
{/if}
|
||||
|
||||
{#if serverAdvancedOpen}
|
||||
<div class="srv-adv-panel">
|
||||
|
||||
@@ -441,7 +441,7 @@ export class SuwayomiAdapter implements ServerAdapter {
|
||||
async browseSource(sourceId: string, page: number): Promise<PaginatedResult<Manga>> {
|
||||
const data = await this.gql<{
|
||||
fetchSourceManga: { mangas: Record<string, unknown>[]; hasNextPage: boolean }
|
||||
}>(FETCH_SOURCE_MANGA, { source: sourceId, type: 'LATEST', page })
|
||||
}>(FETCH_SOURCE_MANGA, { source: sourceId, type: sourceId === '0' ? 'POPULAR' : 'LATEST', page })
|
||||
return {
|
||||
items: data.fetchSourceManga.mangas.map(mapManga),
|
||||
hasNextPage: data.fetchSourceManga.hasNextPage,
|
||||
|
||||
@@ -87,7 +87,15 @@ export function startProbe(
|
||||
boot.skipped = false
|
||||
boot.serverProbeOk = false
|
||||
appState.status = 'booting'
|
||||
let tries = 0
|
||||
|
||||
if (appState.platform === 'web') {
|
||||
boot.failed = true
|
||||
appState.status = 'error'
|
||||
startBackgroundProbe(gen, authMode, user, pass)
|
||||
return
|
||||
}
|
||||
|
||||
let tries = 0
|
||||
|
||||
async function probe() {
|
||||
if (gen !== probeGeneration) return
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { onMount } from 'svelte'
|
||||
import { page } from '$app/stores'
|
||||
import { appState, app } from '$lib/state/app.svelte'
|
||||
import { boot } from '$lib/state/boot.svelte'
|
||||
import { notifications } from '$lib/state/notifications.svelte'
|
||||
import { settingsState, loadSettingsIntoState, updateSettings } from '$lib/state/settings.svelte'
|
||||
import { applyTheme, mountSystemThemeSync } from '$lib/core/theme'
|
||||
@@ -147,6 +148,7 @@
|
||||
mode={appState.status === 'locked' ? 'locked' : 'loading'}
|
||||
{ringFull}
|
||||
failed={appState.status === 'error'}
|
||||
notConfigured={boot.notConfigured}
|
||||
pinLen={settingsState.settings.appLockPin?.length ?? 0}
|
||||
pinCorrect={settingsState.settings.appLockPin ?? ''}
|
||||
onReady={onSplashReady}
|
||||
|
||||
Reference in New Issue
Block a user