ultisuite-client/lib/api/contact-list-cache.ts
R3D347HR4Y 07d57f13a8
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
Add Contact Avatar Features and Improve UI Components
- Introduced new ContactAvatar and ContactAvatarPicker components for enhanced avatar management in contact views.
- Updated ContactDetailView and ContactFormView to utilize the new avatar components, improving user experience when adding or editing contacts.
- Enhanced ContactHoverCard and ContactRow components to display avatars, providing a more visually appealing interface.
- Added loading and error states in ContactsListView for better user feedback during data fetching.
- Implemented a new ContactsLoadState component to handle loading and error scenarios in the contacts list.
- Updated package.json to include @formkit/auto-animate for improved UI animations.
2026-06-06 20:26:51 +02:00

100 lines
2.7 KiB
TypeScript

import { apiContactToFullContact } from '@/lib/api/adapters'
import type { ApiContact } from '@/lib/api/types'
import type { FullContact } from '@/lib/contacts/types'
type BookListCache = {
apiContacts: ApiContact[]
fullContacts: FullContact[]
}
const parseCache = new Map<string, FullContact>()
const parseSigCache = new Map<string, string>()
const listCacheByBook = new Map<string, BookListCache>()
function contactCacheKey(api: ApiContact): string {
return api.uid || api.path || `${api.full_name}:${api.email ?? ''}`
}
function contactSignature(api: ApiContact): string {
return [
api.uid,
api.path,
api.etag,
api.full_name,
api.email,
api.raw_vcard?.length ?? 0,
].join('|')
}
/** Parse vCard une seule fois par contact tant que la signature API est stable. */
export function apiContactToFullContactCached(api: ApiContact): FullContact {
const key = contactCacheKey(api)
const sig = contactSignature(api)
if (parseSigCache.get(key) === sig) {
const hit = parseCache.get(key)
if (hit) return hit
}
const full = apiContactToFullContact(api)
parseCache.set(key, full)
parseSigCache.set(key, sig)
return full
}
function rebuildFullList(bookId: string, apiContacts: ApiContact[]): FullContact[] {
const fullContacts = apiContacts.map(apiContactToFullContactCached)
listCacheByBook.set(bookId, { apiContacts, fullContacts })
return fullContacts
}
/**
* Reconstruit la liste complète seulement si nécessaire.
* Sur ajout en fin de liste (cas courant), réutilise les contacts déjà parsés.
*/
export function mapApiContactsToFullContacts(
bookId: string,
apiContacts: ApiContact[] | undefined,
): FullContact[] {
if (!apiContacts) return []
const prev = listCacheByBook.get(bookId)
if (prev && prev.apiContacts === apiContacts) {
return prev.fullContacts
}
if (prev && apiContacts.length === prev.apiContacts.length + 1) {
let prefixMatch = true
for (let i = 0; i < prev.apiContacts.length; i++) {
if (apiContacts[i] !== prev.apiContacts[i]) {
prefixMatch = false
break
}
}
if (prefixMatch) {
const appended = apiContactToFullContactCached(apiContacts[apiContacts.length - 1]!)
const fullContacts = [...prev.fullContacts, appended]
listCacheByBook.set(bookId, { apiContacts, fullContacts })
return fullContacts
}
}
if (
prev &&
apiContacts.length === prev.apiContacts.length &&
apiContacts.every((c, i) => c === prev.apiContacts[i])
) {
return prev.fullContacts
}
return rebuildFullList(bookId, apiContacts)
}
export function invalidateContactListCache(bookId?: string) {
if (bookId) {
listCacheByBook.delete(bookId)
return
}
listCacheByBook.clear()
parseCache.clear()
parseSigCache.clear()
}