Optimiere LifeWave-Timeline-Performance und entferne toten Code in _src
- TimelineView: Shader-Event-Mapping in computed ausgelagert, deep-Watch entfernt, natives Wheel-Scrolling statt @wheel.prevent - Events-Store: shallowRef statt tiefer Reaktivität, String-Sortierung statt Date-Objekten, getGlowColor memoized, DB-Writes debounced - Edits werden jetzt dedupliziert als 'update' in die SyncQueue geschrieben (vorher gingen Änderungen beim Sync verloren) - useImageCache: In-Flight-Deduping, Cache-Limit mit revokeObjectURL - FloatingLines: Antialiasing aus (Fullscreen-Quad), deep-Watches entfernt - Ungenutzte Seiten/Komponenten entfernt (~6.700 Zeilen, nicht geroutet) - implementation-plan.md: Hinweis zu update-Action im Sync ergänzt
This commit is contained in:
parent
c1514999be
commit
f79dcb9928
20 changed files with 185 additions and 6781 deletions
|
|
@ -4,9 +4,25 @@ import { db } from 'src/db'
|
|||
const THUMB_SIZE = 200
|
||||
|
||||
// In-memory URL cache: avoids repeated IndexedDB reads and blob URL creation
|
||||
// Shared across all component instances
|
||||
// Shared across all component instances. Bounded: oldest entries are evicted
|
||||
// and their blob URLs revoked once the cap is reached.
|
||||
const MEMORY_CACHE_MAX = 300
|
||||
const memoryCache = new Map()
|
||||
|
||||
// In-flight resolutions: parallel requests for the same URL share one promise
|
||||
// (many dots can mount at once referencing the same image)
|
||||
const inFlight = new Map()
|
||||
|
||||
function memoryCacheSet(url, blobUrl) {
|
||||
if (memoryCache.size >= MEMORY_CACHE_MAX) {
|
||||
// Map iterates in insertion order → first entry is the oldest
|
||||
const [oldestUrl, oldestBlobUrl] = memoryCache.entries().next().value
|
||||
URL.revokeObjectURL(oldestBlobUrl)
|
||||
memoryCache.delete(oldestUrl)
|
||||
}
|
||||
memoryCache.set(url, blobUrl)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a thumbnail (THUMB_SIZE x THUMB_SIZE) from a source image blob.
|
||||
* Returns a new Blob (JPEG, quality 0.8).
|
||||
|
|
@ -68,7 +84,7 @@ async function fetchAndCache(imageUrl, eventId) {
|
|||
})
|
||||
|
||||
const blobUrl = URL.createObjectURL(thumbBlob)
|
||||
memoryCache.set(imageUrl, blobUrl)
|
||||
memoryCacheSet(imageUrl, blobUrl)
|
||||
return blobUrl
|
||||
}
|
||||
|
||||
|
|
@ -84,7 +100,7 @@ async function getCachedImage(imageUrl) {
|
|||
const entry = await db.imageCache.get(imageUrl)
|
||||
if (entry?.blob) {
|
||||
const blobUrl = URL.createObjectURL(entry.blob)
|
||||
memoryCache.set(imageUrl, blobUrl)
|
||||
memoryCacheSet(imageUrl, blobUrl)
|
||||
return blobUrl
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
@ -93,6 +109,38 @@ async function getCachedImage(imageUrl) {
|
|||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an image URL to a displayable src (memory → IndexedDB → fetch).
|
||||
* Concurrent calls for the same URL share a single in-flight promise.
|
||||
* Falls back to the original URL if caching fails.
|
||||
*/
|
||||
export function resolveImage(imageUrl, eventId) {
|
||||
if (!imageUrl) return Promise.resolve(null)
|
||||
|
||||
if (memoryCache.has(imageUrl)) {
|
||||
return Promise.resolve(memoryCache.get(imageUrl))
|
||||
}
|
||||
|
||||
if (inFlight.has(imageUrl)) {
|
||||
return inFlight.get(imageUrl)
|
||||
}
|
||||
|
||||
const promise = (async () => {
|
||||
const cached = await getCachedImage(imageUrl)
|
||||
if (cached) return cached
|
||||
try {
|
||||
return await fetchAndCache(imageUrl, eventId)
|
||||
} catch (e) {
|
||||
// Fallback: use original URL directly (works when online)
|
||||
console.warn('Image cache failed, using direct URL:', e)
|
||||
return imageUrl
|
||||
}
|
||||
})().finally(() => inFlight.delete(imageUrl))
|
||||
|
||||
inFlight.set(imageUrl, promise)
|
||||
return promise
|
||||
}
|
||||
|
||||
/**
|
||||
* Composable: resolves an event's image to a displayable src.
|
||||
* - Checks memory cache → IndexedDB cache → fetches & caches thumbnail.
|
||||
|
|
@ -100,42 +148,11 @@ async function getCachedImage(imageUrl) {
|
|||
*/
|
||||
export function useImageCache(imageUrl, eventId) {
|
||||
const resolvedSrc = ref(null)
|
||||
const loading = ref(false)
|
||||
const loading = ref(true)
|
||||
|
||||
async function resolve() {
|
||||
if (!imageUrl) {
|
||||
resolvedSrc.value = null
|
||||
return
|
||||
}
|
||||
|
||||
// 1. Memory cache (instant)
|
||||
if (memoryCache.has(imageUrl)) {
|
||||
resolvedSrc.value = memoryCache.get(imageUrl)
|
||||
return
|
||||
}
|
||||
|
||||
// 2. IndexedDB cache
|
||||
const cached = await getCachedImage(imageUrl)
|
||||
if (cached) {
|
||||
resolvedSrc.value = cached
|
||||
return
|
||||
}
|
||||
|
||||
// 3. Fetch, create thumbnail, cache
|
||||
loading.value = true
|
||||
try {
|
||||
const blobUrl = await fetchAndCache(imageUrl, eventId)
|
||||
resolvedSrc.value = blobUrl
|
||||
} catch (e) {
|
||||
// Fallback: use original URL directly (works when online)
|
||||
console.warn('Image cache failed, using direct URL:', e)
|
||||
resolvedSrc.value = imageUrl
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
resolve()
|
||||
resolveImage(imageUrl, eventId)
|
||||
.then((src) => { resolvedSrc.value = src })
|
||||
.finally(() => { loading.value = false })
|
||||
|
||||
return { resolvedSrc, loading }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue