Add study data client adapter

This commit is contained in:
2026-07-15 16:13:51 -05:00
parent 7f917b9560
commit de383596c7
4 changed files with 132 additions and 37 deletions
+102 -36
View File
@@ -1,6 +1,7 @@
import { invoke } from '@tauri-apps/api/core'
import JSZip from 'jszip'
import { buildVerseSearchIndex } from './searchIndex'
import type { StudyDataClient, TranslationNotesByVerse } from './studyDataClient'
import type {
AudioBibleContext,
AudioBibleSegment,
@@ -352,9 +353,7 @@ function allBrowserTags() {
return [...unique.values()].sort((a, b) => a.label.localeCompare(b.label))
}
export async function getTranslations(): Promise<TranslationResource[]> {
if (inTauri()) return []
async function getBrowserTranslations(): Promise<TranslationResource[]> {
const catalog = await loadBrowserCatalog()
return catalog.resources
.filter((resource) => resource.resource_type === 'translation')
@@ -369,22 +368,18 @@ export async function getTranslations(): Promise<TranslationResource[]> {
}))
}
export async function getLibraryState(translationId = defaultTranslationId): Promise<LibraryState> {
if (inTauri()) {
return invoke<LibraryState>('get_library_state')
}
async function getBrowserLibraryState(translationId = defaultTranslationId): Promise<LibraryState> {
return {
verses: await loadBrowserVersesForTranslation(translationId),
tags: allBrowserTags(),
}
}
export async function getTranslationNotesForVerses(
async function getBrowserTranslationNotesForVerses(
translationId: string,
verses: Verse[],
): Promise<Map<string, { footnotes: Footnote[]; studyNotes: Footnote[] }>> {
if (inTauri() || verses.length === 0) return new Map()
): Promise<TranslationNotesByVerse> {
if (verses.length === 0) return new Map()
const chapterKeys = [
...new Set(
@@ -411,25 +406,17 @@ export async function getTranslationNotesForVerses(
return notesByVerse
}
export async function searchVerses(query: string, translationId = defaultTranslationId): Promise<Verse[]> {
if (inTauri()) {
return invoke<Verse[]>('search_verses', { query })
}
async function searchBrowserVerses(query: string, translationId = defaultTranslationId): Promise<Verse[]> {
const searchIndex = await loadBrowserSearchIndex(translationId)
return searchIndex.search(query)
}
export async function addTagToVerse(
async function addBrowserTagToVerse(
verseId: number,
label: string,
color: string,
translationId = defaultTranslationId,
) {
if (inTauri()) {
return invoke<Verse>('add_tag_to_verse', { verseId, label, color })
}
const tagsByVerse = readBrowserTags()
const current = tagsByVerse[verseId] ?? []
const existing = current.find((tag) => tag.label.toLowerCase() === label.toLowerCase())
@@ -443,9 +430,7 @@ export async function addTagToVerse(
return verse
}
export async function getCommentaryForVerse(verse: Verse): Promise<CommentaryEntry[]> {
if (inTauri()) return []
async function getBrowserCommentaryForVerse(verse: Verse): Promise<CommentaryEntry[]> {
const commentary = await loadBrowserCommentary()
const chapterEntries = commentary.get(commentaryKey(verse.book, verse.chapter)) ?? []
return chapterEntries.filter(
@@ -453,9 +438,7 @@ export async function getCommentaryForVerse(verse: Verse): Promise<CommentaryEnt
)
}
export async function getDictionaryEntriesForTerms(terms: string[]): Promise<DictionaryEntry[]> {
if (inTauri()) return []
async function getBrowserDictionaryEntriesForTerms(terms: string[]): Promise<DictionaryEntry[]> {
const dictionary = await loadBrowserDictionary()
const seen = new Set<string>()
const entries: DictionaryEntry[] = []
@@ -472,14 +455,10 @@ export async function getDictionaryEntriesForTerms(terms: string[]): Promise<Dic
return entries
}
export async function getMapContextForVerses(
async function getBrowserMapContextForVerses(
selectedVerse: Verse,
pageVerses: Verse[],
): Promise<MapContext> {
if (inTauri()) {
return { selectedPlaces: [], pagePlaces: [], relatedRoutes: [] }
}
const versePlaces = await loadBrowserVersePlaces()
const selectedVerseId = browserVerseKey(selectedVerse)
const pageVerseIds = new Set(pageVerses.map((verse) => browserVerseKey(verse)).filter(Boolean))
@@ -496,12 +475,10 @@ export async function getMapContextForVerses(
}
}
export async function getAudioContextForVerse(
async function getBrowserAudioContextForVerse(
translationId: string,
verse: Verse,
): Promise<AudioBibleContext | null> {
if (inTauri()) return null
const catalog = await loadBrowserCatalog()
const resource = catalog.resources.find(
(item) =>
@@ -547,7 +524,7 @@ export async function getAudioContextForVerse(
}
}
export async function getAudioObjectUrl(segment: AudioBibleSegment): Promise<string> {
async function getBrowserAudioObjectUrl(segment: AudioBibleSegment): Promise<string> {
const cacheKey = `${segment.audioZipUrl}#${segment.audioEntryPath}`
if (!browserAudioObjectUrlCache.has(cacheKey)) {
browserAudioObjectUrlCache.set(
@@ -566,6 +543,95 @@ export async function getAudioObjectUrl(segment: AudioBibleSegment): Promise<str
return browserAudioObjectUrlCache.get(cacheKey)!
}
const emptyMapContext: MapContext = {
selectedPlaces: [],
pagePlaces: [],
relatedRoutes: [],
}
const browserStudyDataClient: StudyDataClient = {
getTranslations: getBrowserTranslations,
getLibraryState: getBrowserLibraryState,
getTranslationNotesForVerses: getBrowserTranslationNotesForVerses,
searchVerses: searchBrowserVerses,
addTagToVerse: addBrowserTagToVerse,
getCommentaryForVerse: getBrowserCommentaryForVerse,
getDictionaryEntriesForTerms: getBrowserDictionaryEntriesForTerms,
getMapContextForVerses: getBrowserMapContextForVerses,
getAudioContextForVerse: getBrowserAudioContextForVerse,
getAudioObjectUrl: getBrowserAudioObjectUrl,
}
const tauriStudyDataClient: StudyDataClient = {
getTranslations: async () => [],
getLibraryState: () => invoke<LibraryState>('get_library_state'),
getTranslationNotesForVerses: async () => new Map(),
searchVerses: (query) => invoke<Verse[]>('search_verses', { query }),
addTagToVerse: (verseId, label, color) => invoke<Verse>('add_tag_to_verse', { verseId, label, color }),
getCommentaryForVerse: async () => [],
getDictionaryEntriesForTerms: async () => [],
getMapContextForVerses: async () => emptyMapContext,
getAudioContextForVerse: async () => null,
getAudioObjectUrl: async () => {
throw new Error('Audio object URLs are not available in the Tauri data adapter yet.')
},
}
export function getStudyDataClient(): StudyDataClient {
return inTauri() ? tauriStudyDataClient : browserStudyDataClient
}
export function getTranslations(): Promise<TranslationResource[]> {
return getStudyDataClient().getTranslations()
}
export function getLibraryState(translationId = defaultTranslationId): Promise<LibraryState> {
return getStudyDataClient().getLibraryState(translationId)
}
export function getTranslationNotesForVerses(
translationId: string,
verses: Verse[],
): Promise<TranslationNotesByVerse> {
return getStudyDataClient().getTranslationNotesForVerses(translationId, verses)
}
export function searchVerses(query: string, translationId = defaultTranslationId): Promise<Verse[]> {
return getStudyDataClient().searchVerses(query, translationId)
}
export function addTagToVerse(
verseId: number,
label: string,
color: string,
translationId = defaultTranslationId,
) {
return getStudyDataClient().addTagToVerse(verseId, label, color, translationId)
}
export function getCommentaryForVerse(verse: Verse): Promise<CommentaryEntry[]> {
return getStudyDataClient().getCommentaryForVerse(verse)
}
export function getDictionaryEntriesForTerms(terms: string[]): Promise<DictionaryEntry[]> {
return getStudyDataClient().getDictionaryEntriesForTerms(terms)
}
export function getMapContextForVerses(selectedVerse: Verse, pageVerses: Verse[]): Promise<MapContext> {
return getStudyDataClient().getMapContextForVerses(selectedVerse, pageVerses)
}
export function getAudioContextForVerse(
translationId: string,
verse: Verse,
): Promise<AudioBibleContext | null> {
return getStudyDataClient().getAudioContextForVerse(translationId, verse)
}
export function getAudioObjectUrl(segment: AudioBibleSegment): Promise<string> {
return getStudyDataClient().getAudioObjectUrl(segment)
}
async function loadBrowserCatalog(): Promise<ImportedCatalog> {
browserCatalogCache ??= fetchJson<ImportedCatalog>(`${libreBibleDevRoot}/catalog.json`)
return browserCatalogCache