Browse Source

refine library storage

imwald
Silberengel 1 week ago
parent
commit
7ed700199b
  1. 5
      src/components/CacheBrowser/CacheBrowserDialog.tsx
  2. 92
      src/components/LibraryIndexCacheSettings/index.tsx
  3. 2
      src/components/Note/PublicationCard.tsx
  4. 5
      src/hooks/useLibraryPublications.ts
  5. 17
      src/i18n/locales/de.ts
  6. 17
      src/i18n/locales/en.ts
  7. 19
      src/lib/library-publication-index.ts
  8. 2
      src/pages/secondary/CacheSettingsPage/index.tsx

5
src/components/CacheBrowser/CacheBrowserDialog.tsx

@ -6,6 +6,7 @@ import { Trash2, RefreshCw, Database, WrapText, Search, X, TriangleAlert, Copy, @@ -6,6 +6,7 @@ import { Trash2, RefreshCw, Database, WrapText, Search, X, TriangleAlert, Copy,
import { Input } from '@/components/ui/input'
import { Skeleton } from '@/components/ui/skeleton'
import indexedDb, { isLikelyCachedNostrEvent, StoreNames, type TCachedEventSearchHit } from '@/services/indexed-db.service'
import { clearAllLibraryIndexCaches } from '@/lib/library-publication-index'
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'
import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription } from '@/components/ui/drawer'
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card'
@ -177,7 +178,11 @@ export default function CacheBrowserDialog({ @@ -177,7 +178,11 @@ export default function CacheBrowserDialog({
if (!selectedStore) return
if (!confirm(t('Are you sure you want to delete all items from this store?'))) return
try {
if (selectedStore === StoreNames.LIBRARY_PUBLICATION_INDEX) {
await clearAllLibraryIndexCaches()
} else {
await indexedDb.clearStore(selectedStore)
}
setStoreItems([])
void loadCacheInfo()
toast.success(t('All items deleted successfully'))

92
src/components/LibraryIndexCacheSettings/index.tsx

@ -0,0 +1,92 @@ @@ -0,0 +1,92 @@
import { Button } from '@/components/ui/button'
import { getLibraryIndexCacheFootprint, getLibraryIndexCacheBudget } from '@/lib/library-index-idb-cache'
import { clearAllLibraryIndexCaches } from '@/lib/library-publication-index'
import { isImwaldElectron, isMobileBrowserProfile } from '@/lib/client-platform'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
function formatMb(bytes: number): string {
return (bytes / (1024 * 1024)).toFixed(1)
}
function platformLabel(): string {
if (isImwaldElectron()) return 'desktop-app'
if (isMobileBrowserProfile()) return 'mobile-web'
return 'desktop-web'
}
export default function LibraryIndexCacheSettings() {
const { t } = useTranslation()
const [footprint, setFootprint] = useState<{ count: number; bytes: number } | null>(null)
const [clearing, setClearing] = useState(false)
const budget = useMemo(() => getLibraryIndexCacheBudget(), [])
const refreshFootprint = useCallback(async () => {
try {
setFootprint(await getLibraryIndexCacheFootprint())
} catch {
setFootprint({ count: 0, bytes: 0 })
}
}, [])
useEffect(() => {
void refreshFootprint()
}, [refreshFootprint])
const handleClear = async () => {
if (!confirm(t('libraryIndexCache.clearConfirm'))) return
setClearing(true)
try {
await clearAllLibraryIndexCaches()
await refreshFootprint()
toast.success(t('libraryIndexCache.clearedToast'))
} catch {
toast.error(t('libraryIndexCache.clearFailed'))
} finally {
setClearing(false)
}
}
const defaultsHint = useMemo(() => {
const p = platformLabel()
if (p === 'mobile-web') {
return t('libraryIndexCache.defaultsMobile', {
entries: budget.maxEntries,
mb: Math.round(budget.maxBytes / (1024 * 1024))
})
}
if (p === 'desktop-app') {
return t('libraryIndexCache.defaultsElectron', {
entries: budget.maxEntries,
mb: Math.round(budget.maxBytes / (1024 * 1024))
})
}
return t('libraryIndexCache.defaultsDesktopWeb', {
entries: budget.maxEntries,
mb: Math.round(budget.maxBytes / (1024 * 1024))
})
}, [budget.maxBytes, budget.maxEntries, t])
return (
<div className="mt-8 space-y-4 border-t border-border pt-6">
<h3 className="text-base font-medium">{t('libraryIndexCache.sectionTitle')}</h3>
<p className="text-muted-foreground text-sm">{t('libraryIndexCache.sectionBlurb')}</p>
<p className="text-muted-foreground text-xs">{defaultsHint}</p>
<p className="text-muted-foreground text-xs">
{t('libraryIndexCache.footprintSummary', {
count: footprint?.count ?? 0,
mb: formatMb(footprint?.bytes ?? 0),
maxEntries: budget.maxEntries,
maxMb: Math.round(budget.maxBytes / (1024 * 1024))
})}
</p>
<Button type="button" variant="secondary" disabled={clearing} onClick={() => void handleClear()}>
{clearing ? t('libraryIndexCache.clearing') : t('libraryIndexCache.clear')}
</Button>
</div>
)
}

2
src/components/Note/PublicationCard.tsx

@ -9,6 +9,7 @@ import { Event, kinds } from 'nostr-tools' @@ -9,6 +9,7 @@ import { Event, kinds } from 'nostr-tools'
import { useMemo } from 'react'
import Image from '../Image'
import { extractBookMetadata } from '@/lib/bookstr-parser'
import { persistLibraryPublicationForReading } from '@/lib/library-publication-index'
import { ExtendedKind } from '@/constants'
export default function PublicationCard({
@ -36,6 +37,7 @@ export default function PublicationCard({ @@ -36,6 +37,7 @@ export default function PublicationCard({
const handleCardClick = (e: React.MouseEvent) => {
e.stopPropagation()
if (disableNavigation) return
persistLibraryPublicationForReading(event)
navigateToNote(toNote(event), event)
}

5
src/hooks/useLibraryPublications.ts

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import {
clearLibraryPublicationIndexCache,
clearAllLibraryIndexCaches,
filterLibraryPublicationsBySearch,
filterLibraryPublicationsByUser,
buildLibraryRelayUrls,
@ -81,8 +81,7 @@ export function useLibraryPublications(isActive: boolean) { @@ -81,8 +81,7 @@ export function useLibraryPublications(isActive: boolean) {
}, [isActive, load])
const refresh = useCallback(() => {
clearLibraryPublicationIndexCache()
void load(true)
void clearAllLibraryIndexCaches().then(() => load(true))
}, [load])
const filteredEntries = useMemo(() => {

17
src/i18n/locales/de.ts

@ -1658,6 +1658,23 @@ export default { @@ -1658,6 +1658,23 @@ export default {
'Library badge label': 'Label',
'Library badge comment': 'Kommentar',
'Library badge highlight': 'Markierung',
'libraryIndexCache.sectionTitle': 'Bibliotheks-Publikationsindex',
'libraryIndexCache.sectionBlurb':
'Zwischengespeicherte Kind-30040-Index-Events für den Bibliotheks-Tab. Beim Leeren wird nur der Entdeckungslisten-Cache entfernt — geöffnete Publikationen bleiben im Lese-Cache.',
'libraryIndexCache.defaultsMobile':
'Standard mobil: bis zu {{entries}} Indizes, ~{{mb}} MB.',
'libraryIndexCache.defaultsElectron':
'Standard Desktop-App: bis zu {{entries}} Indizes, ~{{mb}} MB.',
'libraryIndexCache.defaultsDesktopWeb':
'Standard Desktop-Web: bis zu {{entries}} Indizes, ~{{mb}} MB.',
'libraryIndexCache.footprintSummary':
'{{count}} / {{maxEntries}} Indizes (~{{mb}} / {{maxMb}} MB).',
'libraryIndexCache.clear': 'Bibliotheksindex-Cache leeren',
'libraryIndexCache.clearing': 'Wird geleert…',
'libraryIndexCache.clearConfirm':
'Bibliotheksindex-Cache leeren? Beim nächsten Besuch werden Indizes erneut von Relays geladen. Geöffnete Publikationen bleiben im Lese-Cache.',
'libraryIndexCache.clearedToast': 'Bibliotheksindex-Cache geleert.',
'libraryIndexCache.clearFailed': 'Bibliotheksindex-Cache konnte nicht geleert werden.',
'Search page clear': 'Leeren',
'Search page clear description':
'Suchfeld leeren, Vorschläge schließen und Ergebnisse entfernen, um neu zu suchen.',

17
src/i18n/locales/en.ts

@ -1681,6 +1681,23 @@ export default { @@ -1681,6 +1681,23 @@ export default {
'Library badge label': 'Label',
'Library badge comment': 'Comment',
'Library badge highlight': 'Highlight',
'libraryIndexCache.sectionTitle': 'Library publication index',
'libraryIndexCache.sectionBlurb':
'Cached kind-30040 index events used to populate the Library tab. Clearing this only removes the discovery list cache—not publications you have opened for reading.',
'libraryIndexCache.defaultsMobile':
'Default on mobile web: up to {{entries}} indexes, ~{{mb}} MB.',
'libraryIndexCache.defaultsElectron':
'Default in the desktop app: up to {{entries}} indexes, ~{{mb}} MB.',
'libraryIndexCache.defaultsDesktopWeb':
'Default on desktop web: up to {{entries}} indexes, ~{{mb}} MB.',
'libraryIndexCache.footprintSummary':
'Using {{count}} / {{maxEntries}} indexes (~{{mb}} / {{maxMb}} MB).',
'libraryIndexCache.clear': 'Clear library index cache',
'libraryIndexCache.clearing': 'Clearing…',
'libraryIndexCache.clearConfirm':
'Clear the Library index cache? The Library tab will reload indexes from relays on next visit. Opened publications stay in your publication reading cache.',
'libraryIndexCache.clearedToast': 'Library index cache cleared.',
'libraryIndexCache.clearFailed': 'Failed to clear library index cache.',
'Search page clear': 'Clear',
'Search page clear description':
'Clear the search field, close suggestions, and remove results so you can start a new search.',

19
src/lib/library-publication-index.ts

@ -12,9 +12,12 @@ import { @@ -12,9 +12,12 @@ import {
} from '@/lib/publication-index'
import { buildComprehensiveRelayList } from '@/lib/relay-list-builder'
import {
clearLibraryIndexIdbCache,
loadLibraryIndexCacheEvents,
persistLibraryIndexCacheEvents
} from '@/lib/library-index-idb-cache'
import client from '@/services/client.service'
import indexedDb from '@/services/indexed-db.service'
import {
canonicalRelaySessionKey,
httpIndexBasesForRelayQuery,
@ -599,3 +602,19 @@ export async function loadLibraryPublicationIndex( @@ -599,3 +602,19 @@ export async function loadLibraryPublicationIndex(
export function clearLibraryPublicationIndexCache(): void {
sessionCache = null
}
/** Clears Library tab session + IDB index cache only (publication reading cache is unchanged). */
export async function clearAllLibraryIndexCaches(): Promise<void> {
sessionCache = null
await clearLibraryIndexIdbCache()
}
/**
* When opening a publication from Library, seed session cache and the publication events store
* so offline re-read works even if the index lived only in the Library LRU store.
*/
export function persistLibraryPublicationForReading(event: Event): void {
if (event.kind !== ExtendedKind.PUBLICATION) return
client.addEventToCache(event)
void indexedDb.putReplaceableEvent(event).catch(() => {})
}

2
src/pages/secondary/CacheSettingsPage/index.tsx

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
import CacheEventImportSettings from '@/components/CacheEventImportSettings'
import InBrowserCacheSetting from '@/components/InBrowserCacheSetting'
import EventArchiveCacheSettings from '@/components/EventArchiveCacheSettings'
import LibraryIndexCacheSettings from '@/components/LibraryIndexCacheSettings'
import PrivateKeyRecoverySetting from '@/components/PrivateKeyRecoverySetting'
import { RefreshButton } from '@/components/RefreshButton'
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
@ -65,6 +66,7 @@ const CacheSettingsPage = forwardRef<TPageRef, { index?: number; hideTitlebar?: @@ -65,6 +66,7 @@ const CacheSettingsPage = forwardRef<TPageRef, { index?: number; hideTitlebar?:
<InBrowserCacheSetting />
<CacheEventImportSettings />
<EventArchiveCacheSettings />
<LibraryIndexCacheSettings />
</div>
</SecondaryPageLayout>
)

Loading…
Cancel
Save