summaryrefslogtreecommitdiffhomepage
path: root/src/lib/components
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-04-10 01:00:45 +0900
committerAdam Malczewski <[email protected]>2026-04-10 01:00:45 +0900
commit5d610bcad55c5d908a4ae046390124b5f5174762 (patch)
tree68cf7025274e31a092a438479aca9e5176507d53 /src/lib/components
parent328b962572e4decb5280541c6d01495af440799d (diff)
downloadflashair-speedsync-5d610bcad55c5d908a4ae046390124b5f5174762.tar.gz
flashair-speedsync-5d610bcad55c5d908a4ae046390124b5f5174762.zip
better debug, change button sorting
Diffstat (limited to 'src/lib/components')
-rw-r--r--src/lib/components/CacheDebug.svelte122
-rw-r--r--src/lib/components/CachedThumbnail.svelte5
-rw-r--r--src/lib/components/ImageList.svelte2
-rw-r--r--src/lib/components/ImagePreview.svelte4
4 files changed, 56 insertions, 77 deletions
diff --git a/src/lib/components/CacheDebug.svelte b/src/lib/components/CacheDebug.svelte
index 09e859d..ea1386d 100644
--- a/src/lib/components/CacheDebug.svelte
+++ b/src/lib/components/CacheDebug.svelte
@@ -1,11 +1,14 @@
<script lang="ts">
import { imageCache } from '../cache';
+ import { autoCacheService } from '../cache';
+ import { fly } from 'svelte/transition';
interface CacheStats {
entries: number;
fullCount: number;
thumbCount: number;
totalBytes: number;
+ maxBytes: number;
idbEntries: number;
idbBytes: number;
idbError: string | undefined;
@@ -14,8 +17,18 @@
}
let stats = $state<CacheStats | undefined>(undefined);
- let refreshing = $state(false);
- let collapsed = $state(false);
+
+ let idbBudgetPct = $derived(
+ stats !== undefined && stats.maxBytes > 0
+ ? (stats.idbBytes / stats.maxBytes * 100)
+ : 0
+ );
+
+ let budgetPct = $derived(
+ stats !== undefined && stats.maxBytes > 0
+ ? (stats.totalBytes / stats.maxBytes * 100)
+ : 0
+ );
function formatBytes(bytes: number): string {
if (bytes === 0) return '0 B';
@@ -26,45 +39,15 @@
return `${val.toFixed(1)} ${sizes[i]}`;
}
- let storageEstimate = $state<{ usage: number; quota: number } | undefined>(undefined);
- let storageError = $state<string | undefined>(undefined);
+ let downloadSpeed = $state(0);
async function refresh() {
- refreshing = true;
try {
stats = await imageCache.getStats();
- } catch { /* ignore */ }
-
- // Try standard Storage API first, then webkit fallback
- storageEstimate = undefined;
- storageError = undefined;
- try {
- if (navigator.storage && navigator.storage.estimate) {
- const est = await navigator.storage.estimate();
- storageEstimate = { usage: est.usage ?? 0, quota: est.quota ?? 0 };
- } else if ('webkitTemporaryStorage' in navigator) {
- // Chrome HTTP fallback
- const wk = (navigator as Record<string, unknown>)['webkitTemporaryStorage'] as
- { queryUsageAndQuota: (ok: (u: number, q: number) => void, err: (e: unknown) => void) => void } | undefined;
- if (wk !== undefined) {
- const result = await new Promise<{ usage: number; quota: number }>((resolve, reject) => {
- wk.queryUsageAndQuota(
- (usage, quota) => resolve({ usage, quota }),
- (err) => reject(err),
- );
- });
- storageEstimate = result;
- } else {
- storageError = 'Storage API not available (HTTP origin)';
- }
- } else {
- storageError = 'Storage API not available';
- }
- } catch (e) {
- storageError = e instanceof Error ? e.message : String(e);
+ } catch {
+ // ignore
}
-
- refreshing = false;
+ downloadSpeed = autoCacheService.downloadSpeed;
}
$effect(() => {
@@ -74,22 +57,33 @@
});
</script>
-<div class="fixed top-2 left-2 z-50 bg-base-100/95 backdrop-blur-sm rounded-box shadow-lg border border-base-300 p-3 text-xs font-mono max-w-80 pointer-events-auto">
- <button
- class="flex items-center justify-between w-full mb-1"
- onclick={() => collapsed = !collapsed}
- >
- <span class="font-bold text-sm">Cache Debug</span>
- <span class="text-base-content/50">{collapsed ? '▶' : '▼'}</span>
- </button>
+<div
+ class="fixed top-2 left-2 z-50 bg-base-100/95 backdrop-blur-sm rounded-box shadow-lg border border-base-300 p-3 text-xs font-mono max-w-80 pointer-events-auto"
+ transition:fly={{ x: -320, duration: 250 }}
+>
+ <div class="font-bold text-sm mb-2">Cache Debug</div>
- {#if !collapsed && stats !== undefined}
+ {#if stats !== undefined}
<div class="space-y-2">
+ <!-- Download Speed -->
+ <div>
+ <span class="font-semibold">DL Speed:</span>
+ {#if downloadSpeed > 0}
+ <span class="text-info">{formatBytes(downloadSpeed)}/s</span>
+ {:else}
+ <span class="text-base-content/50">N/A</span>
+ {/if}
+ </div>
+
<!-- Memory Cache -->
<div>
<div class="font-semibold text-primary">Memory Cache</div>
<div>Entries: <span class="text-info">{stats.entries}</span> ({stats.fullCount} full, {stats.thumbCount} thumb)</div>
- <div>Size: <span class="text-info">{formatBytes(stats.totalBytes)}</span></div>
+ <div>Size: <span class="text-info">{formatBytes(stats.totalBytes)}</span> / {formatBytes(stats.maxBytes)}</div>
+ <div>
+ <progress class="progress progress-primary w-full h-1.5" value={budgetPct} max="100"></progress>
+ <span class="text-[10px]" class:text-error={budgetPct > 90} class:text-warning={budgetPct > 70 && budgetPct <= 90}>{budgetPct.toFixed(1)}% of budget</span>
+ </div>
</div>
<!-- IndexedDB -->
@@ -99,46 +93,30 @@
<div class="text-error">Error: {stats.idbError}</div>
{:else}
<div>Entries: <span class="text-info">{stats.idbEntries}</span></div>
- <div>Size: <span class="text-info">{formatBytes(stats.idbBytes)}</span></div>
+ <div>Size: <span class="text-info">{formatBytes(stats.idbBytes)}</span> / {formatBytes(stats.maxBytes)}</div>
+ <div>
+ <progress class="progress progress-secondary w-full h-1.5" value={idbBudgetPct} max="100"></progress>
+ <span class="text-[10px]" class:text-error={idbBudgetPct > 90} class:text-warning={idbBudgetPct > 70 && idbBudgetPct <= 90}>{idbBudgetPct.toFixed(1)}% of budget</span>
+ </div>
{#if stats.idbWriteErrorCount > 0}
- <div class="text-error mt-1">❌ {stats.idbWriteErrorCount} write errors</div>
+ <div class="text-error mt-1">{stats.idbWriteErrorCount} write errors</div>
{#if stats.idbLastWriteError !== undefined}
<div class="text-error text-[10px] break-all">{stats.idbLastWriteError}</div>
{/if}
{/if}
<div class="mt-1">
{#if stats.idbEntries === stats.entries}
- <span class="text-success">✅ In sync with memory</span>
+ <span class="text-success">In sync with memory</span>
{:else if stats.idbEntries < stats.entries}
- <span class="text-warning">⏳ IDB behind ({stats.entries - stats.idbEntries} pending)</span>
+ <span class="text-warning">IDB behind ({stats.entries - stats.idbEntries} pending)</span>
{:else}
<span class="text-info">IDB has {stats.idbEntries - stats.entries} extra (pre-loaded)</span>
{/if}
</div>
{/if}
</div>
- <!-- Storage Quota -->
- <div class="border-t border-base-300 pt-2">
- <div class="font-semibold text-accent">Storage Quota</div>
- {#if storageEstimate !== undefined}
- <div>Used: <span class="text-info">{formatBytes(storageEstimate.usage)}</span> / {formatBytes(storageEstimate.quota)}</div>
- {#if storageEstimate.quota > 0}
- {@const pct = (storageEstimate.usage / storageEstimate.quota * 100)}
- <div>
- <progress class="progress progress-primary w-full h-2" value={pct} max="100"></progress>
- <span class:text-error={pct > 90} class:text-warning={pct > 70 && pct <= 90}>{pct.toFixed(1)}%</span>
- </div>
- {/if}
- {:else if storageError !== undefined}
- <div class="text-warning">{storageError}</div>
- {:else}
- <div class="text-base-content/50">Querying…</div>
- {/if}
- </div>
- </div>
- {:else if !collapsed}
- <div class="text-base-content/50">
- {#if refreshing}Loading…{:else}No data{/if}
</div>
+ {:else}
+ <div class="text-base-content/50">Loading…</div>
{/if}
</div>
diff --git a/src/lib/components/CachedThumbnail.svelte b/src/lib/components/CachedThumbnail.svelte
index 3a6d452..dada22a 100644
--- a/src/lib/components/CachedThumbnail.svelte
+++ b/src/lib/components/CachedThumbnail.svelte
@@ -6,9 +6,10 @@
interface Props {
path: string;
alt: string;
+ fileDate?: number;
}
- let { path, alt }: Props = $props();
+ let { path, alt, fileDate }: Props = $props();
let blobUrl = $state<string | undefined>(undefined);
let rawBlobUrl: string | undefined;
@@ -56,7 +57,7 @@
try {
const { blob, meta } = await flashair.fetchThumbnail(filePath);
// Store in cache (fire-and-forget)
- void imageCache.put('thumbnail', filePath, blob, meta);
+ void imageCache.put('thumbnail', filePath, blob, meta, fileDate);
const url = URL.createObjectURL(blob);
rawBlobUrl = url;
blobUrl = url;
diff --git a/src/lib/components/ImageList.svelte b/src/lib/components/ImageList.svelte
index 5048e1b..d56c95c 100644
--- a/src/lib/components/ImageList.svelte
+++ b/src/lib/components/ImageList.svelte
@@ -40,7 +40,7 @@
onclick={() => onSelect(file)}
>
{#if thumbUrl}
- <CachedThumbnail path={file.path} alt={file.filename} />
+ <CachedThumbnail path={file.path} alt={file.filename} fileDate={file.date.getTime()} />
{:else}
<div class="w-full aspect-square rounded bg-base-300 flex items-center justify-center">
<span class="text-lg">🖼️</span>
diff --git a/src/lib/components/ImagePreview.svelte b/src/lib/components/ImagePreview.svelte
index 914f9a1..335e231 100644
--- a/src/lib/components/ImagePreview.svelte
+++ b/src/lib/components/ImagePreview.svelte
@@ -320,7 +320,7 @@
// Check staleness after await
if (activePath !== entry.path) return;
// Store in cache (fire-and-forget)
- void imageCache.put('thumbnail', entry.path, blob, meta);
+ void imageCache.put('thumbnail', entry.path, blob, meta, entry.date.getTime());
const blobUrl = URL.createObjectURL(blob);
rawThumbnailUrl = blobUrl;
thumbnailBlobUrl = blobUrl;
@@ -386,7 +386,7 @@
const blob = await res.blob();
if (abort.signal.aborted || activePath !== entry.path) return;
// Store in cache (fire-and-forget)
- void imageCache.put('full', entry.path, blob);
+ void imageCache.put('full', entry.path, blob, undefined, entry.date.getTime());
autoCacheService.markCached(entry.path);
const objectUrl = URL.createObjectURL(blob);
rawObjectUrl = objectUrl;