mirror of
https://github.com/dergigi/boris.git
synced 2025-12-19 15:44:20 +01:00
feat: add caching to /me page for faster loading
- Create meCache service to store highlights, bookmarks, and read articles - Seed Me component from cache on load to avoid empty flash - Show small spinner while refreshing if cached data is displayed - Update cache when highlights are deleted - Only show full loading screen if no cached data is available - Improves perceived performance similar to /explore page
This commit is contained in:
@@ -17,6 +17,7 @@ import { BookmarkItem } from './BookmarkItem'
|
|||||||
import IconButton from './IconButton'
|
import IconButton from './IconButton'
|
||||||
import { ViewMode } from './Bookmarks'
|
import { ViewMode } from './Bookmarks'
|
||||||
import { extractUrlsFromContent } from '../services/bookmarkHelpers'
|
import { extractUrlsFromContent } from '../services/bookmarkHelpers'
|
||||||
|
import { getCachedMeData, setCachedMeData, updateCachedHighlights } from '../services/meCache'
|
||||||
import { faBooks } from '../icons/customIcons'
|
import { faBooks } from '../icons/customIcons'
|
||||||
|
|
||||||
interface MeProps {
|
interface MeProps {
|
||||||
@@ -47,6 +48,14 @@ const Me: React.FC<MeProps> = ({ relayPool }) => {
|
|||||||
setLoading(true)
|
setLoading(true)
|
||||||
setError(null)
|
setError(null)
|
||||||
|
|
||||||
|
// Seed from cache if available to avoid empty flash
|
||||||
|
const cached = getCachedMeData(activeAccount.pubkey)
|
||||||
|
if (cached) {
|
||||||
|
setHighlights(cached.highlights)
|
||||||
|
setBookmarks(cached.bookmarks)
|
||||||
|
setReadArticles(cached.readArticles)
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch highlights and read articles
|
// Fetch highlights and read articles
|
||||||
const [userHighlights, userReadArticles] = await Promise.all([
|
const [userHighlights, userReadArticles] = await Promise.all([
|
||||||
fetchHighlights(relayPool, activeAccount.pubkey),
|
fetchHighlights(relayPool, activeAccount.pubkey),
|
||||||
@@ -57,12 +66,19 @@ const Me: React.FC<MeProps> = ({ relayPool }) => {
|
|||||||
setReadArticles(userReadArticles)
|
setReadArticles(userReadArticles)
|
||||||
|
|
||||||
// Fetch bookmarks using callback pattern
|
// Fetch bookmarks using callback pattern
|
||||||
|
let fetchedBookmarks: Bookmark[] = []
|
||||||
try {
|
try {
|
||||||
await fetchBookmarks(relayPool, activeAccount, setBookmarks)
|
await fetchBookmarks(relayPool, activeAccount, (newBookmarks) => {
|
||||||
|
fetchedBookmarks = newBookmarks
|
||||||
|
setBookmarks(newBookmarks)
|
||||||
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('Failed to load bookmarks:', err)
|
console.warn('Failed to load bookmarks:', err)
|
||||||
setBookmarks([])
|
setBookmarks([])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update cache with all fetched data
|
||||||
|
setCachedMeData(activeAccount.pubkey, userHighlights, fetchedBookmarks, userReadArticles)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to load data:', err)
|
console.error('Failed to load data:', err)
|
||||||
setError('Failed to load data. Please try again.')
|
setError('Failed to load data. Please try again.')
|
||||||
@@ -75,7 +91,14 @@ const Me: React.FC<MeProps> = ({ relayPool }) => {
|
|||||||
}, [relayPool, activeAccount])
|
}, [relayPool, activeAccount])
|
||||||
|
|
||||||
const handleHighlightDelete = (highlightId: string) => {
|
const handleHighlightDelete = (highlightId: string) => {
|
||||||
setHighlights(prev => prev.filter(h => h.id !== highlightId))
|
setHighlights(prev => {
|
||||||
|
const updated = prev.filter(h => h.id !== highlightId)
|
||||||
|
// Update cache when highlight is deleted
|
||||||
|
if (activeAccount) {
|
||||||
|
updateCachedHighlights(activeAccount.pubkey, updated)
|
||||||
|
}
|
||||||
|
return updated
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const getPostUrl = (post: BlogPostPreview) => {
|
const getPostUrl = (post: BlogPostPreview) => {
|
||||||
@@ -110,7 +133,10 @@ const Me: React.FC<MeProps> = ({ relayPool }) => {
|
|||||||
.filter(hasContentOrUrl)
|
.filter(hasContentOrUrl)
|
||||||
.sort((a, b) => ((b.added_at || 0) - (a.added_at || 0)) || ((b.created_at || 0) - (a.created_at || 0)))
|
.sort((a, b) => ((b.added_at || 0) - (a.added_at || 0)) || ((b.created_at || 0) - (a.created_at || 0)))
|
||||||
|
|
||||||
if (loading) {
|
// Only show full loading screen if we don't have any data yet
|
||||||
|
const hasData = highlights.length > 0 || bookmarks.length > 0 || readArticles.length > 0
|
||||||
|
|
||||||
|
if (loading && !hasData) {
|
||||||
return (
|
return (
|
||||||
<div className="explore-container">
|
<div className="explore-container">
|
||||||
<div className="explore-loading">
|
<div className="explore-loading">
|
||||||
@@ -228,6 +254,12 @@ const Me: React.FC<MeProps> = ({ relayPool }) => {
|
|||||||
<div className="explore-header">
|
<div className="explore-header">
|
||||||
{activeAccount && <AuthorCard authorPubkey={activeAccount.pubkey} />}
|
{activeAccount && <AuthorCard authorPubkey={activeAccount.pubkey} />}
|
||||||
|
|
||||||
|
{loading && hasData && (
|
||||||
|
<div className="explore-loading" style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', padding: '0.5rem 0' }}>
|
||||||
|
<FontAwesomeIcon icon={faSpinner} spin />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="me-tabs">
|
<div className="me-tabs">
|
||||||
<button
|
<button
|
||||||
className={`me-tab ${activeTab === 'highlights' ? 'active' : ''}`}
|
className={`me-tab ${activeTab === 'highlights' ? 'active' : ''}`}
|
||||||
|
|||||||
54
src/services/meCache.ts
Normal file
54
src/services/meCache.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { Highlight } from '../types/highlights'
|
||||||
|
import { Bookmark } from '../types/bookmarks'
|
||||||
|
import { BlogPostPreview } from './exploreService'
|
||||||
|
|
||||||
|
export interface MeCache {
|
||||||
|
highlights: Highlight[]
|
||||||
|
bookmarks: Bookmark[]
|
||||||
|
readArticles: BlogPostPreview[]
|
||||||
|
timestamp: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const meCache = new Map<string, MeCache>() // key: pubkey
|
||||||
|
|
||||||
|
export function getCachedMeData(pubkey: string): MeCache | null {
|
||||||
|
const entry = meCache.get(pubkey)
|
||||||
|
if (!entry) return null
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setCachedMeData(
|
||||||
|
pubkey: string,
|
||||||
|
highlights: Highlight[],
|
||||||
|
bookmarks: Bookmark[],
|
||||||
|
readArticles: BlogPostPreview[]
|
||||||
|
): void {
|
||||||
|
meCache.set(pubkey, {
|
||||||
|
highlights,
|
||||||
|
bookmarks,
|
||||||
|
readArticles,
|
||||||
|
timestamp: Date.now()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateCachedHighlights(pubkey: string, highlights: Highlight[]): void {
|
||||||
|
const existing = meCache.get(pubkey)
|
||||||
|
if (existing) {
|
||||||
|
meCache.set(pubkey, { ...existing, highlights, timestamp: Date.now() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateCachedBookmarks(pubkey: string, bookmarks: Bookmark[]): void {
|
||||||
|
const existing = meCache.get(pubkey)
|
||||||
|
if (existing) {
|
||||||
|
meCache.set(pubkey, { ...existing, bookmarks, timestamp: Date.now() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateCachedReadArticles(pubkey: string, readArticles: BlogPostPreview[]): void {
|
||||||
|
const existing = meCache.get(pubkey)
|
||||||
|
if (existing) {
|
||||||
|
meCache.set(pubkey, { ...existing, readArticles, timestamp: Date.now() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user