feat: integrate highlight management into reader app
- Added HighlightSyncManager and highlight state management to BibleReaderApp - Implemented highlight handlers: add, update color, remove, and sync - Connected highlight state from BibleReaderApp to VersDetailsPanel - Updated VersDetailsPanel to pass highlight props to HighlightsTab - Added auto-sync initialization with 30-second interval - Prepared for Phase 2.1B API integration 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +1,15 @@
|
||||
'use client'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { useLocale } from 'next-intl'
|
||||
import { Box, Typography, Button } from '@mui/material'
|
||||
import { BibleChapter, BibleVerse } from '@/types'
|
||||
import { BibleChapter, BibleVerse, BibleHighlight, HighlightColor } from '@/types'
|
||||
import { getCachedChapter, cacheChapter } from '@/lib/cache-manager'
|
||||
import { SearchNavigator } from './search-navigator'
|
||||
import { ReadingView } from './reading-view'
|
||||
import { VersDetailsPanel } from './verse-details-panel'
|
||||
import { ReadingSettings } from './reading-settings'
|
||||
import { HighlightSyncManager } from '@/lib/highlight-sync-manager'
|
||||
import { addHighlight, updateHighlight, getHighlightsByVerse, deleteHighlight, getAllHighlights } from '@/lib/highlight-manager'
|
||||
|
||||
interface BookInfo {
|
||||
id: string // UUID
|
||||
@@ -31,6 +33,8 @@ export function BibleReaderApp() {
|
||||
const [versionId, setVersionId] = useState<string>('')
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [booksLoading, setBooksLoading] = useState(true)
|
||||
const [highlights, setHighlights] = useState<Map<string, BibleHighlight>>(new Map())
|
||||
const syncManager = useRef<HighlightSyncManager | null>(null)
|
||||
|
||||
// Load books on mount or when locale changes
|
||||
useEffect(() => {
|
||||
@@ -44,6 +48,24 @@ export function BibleReaderApp() {
|
||||
}
|
||||
}, [bookId, chapter, booksLoading, books.length])
|
||||
|
||||
// Initialize sync manager on mount
|
||||
useEffect(() => {
|
||||
syncManager.current = new HighlightSyncManager()
|
||||
syncManager.current.init()
|
||||
syncManager.current.startAutoSync(30000, () => {
|
||||
performSync()
|
||||
})
|
||||
|
||||
return () => {
|
||||
syncManager.current?.stopAutoSync()
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Load all highlights on mount
|
||||
useEffect(() => {
|
||||
loadAllHighlights()
|
||||
}, [])
|
||||
|
||||
async function loadBooks() {
|
||||
setBooksLoading(true)
|
||||
setError(null)
|
||||
@@ -168,6 +190,95 @@ export function BibleReaderApp() {
|
||||
console.log(`Note for verse ${selectedVerse.id}:`, note)
|
||||
}
|
||||
|
||||
async function loadAllHighlights() {
|
||||
try {
|
||||
const highlightList = await getAllHighlights()
|
||||
const map = new Map(highlightList.map(h => [h.verseId, h]))
|
||||
setHighlights(map)
|
||||
} catch (error) {
|
||||
console.error('Failed to load highlights:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleHighlightVerse(color: HighlightColor = 'yellow') {
|
||||
if (!selectedVerse) return
|
||||
|
||||
const highlight: BibleHighlight = {
|
||||
id: `h-${selectedVerse.id}-${Date.now()}`,
|
||||
verseId: selectedVerse.id,
|
||||
color,
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
syncStatus: 'pending'
|
||||
}
|
||||
|
||||
try {
|
||||
await addHighlight(highlight)
|
||||
const newMap = new Map(highlights)
|
||||
newMap.set(selectedVerse.id, highlight)
|
||||
setHighlights(newMap)
|
||||
} catch (error) {
|
||||
console.error('Failed to highlight verse:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleChangeHighlightColor(color: HighlightColor) {
|
||||
if (!selectedVerse) return
|
||||
|
||||
const existing = highlights.get(selectedVerse.id)
|
||||
if (existing) {
|
||||
const updated = {
|
||||
...existing,
|
||||
color,
|
||||
updatedAt: Date.now(),
|
||||
syncStatus: 'pending' as const
|
||||
}
|
||||
try {
|
||||
await updateHighlight(updated)
|
||||
const newMap = new Map(highlights)
|
||||
newMap.set(selectedVerse.id, updated)
|
||||
setHighlights(newMap)
|
||||
} catch (error) {
|
||||
console.error('Failed to update highlight color:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRemoveHighlight() {
|
||||
if (!selectedVerse) return
|
||||
|
||||
try {
|
||||
// Find and delete all highlights for this verse
|
||||
const existing = highlights.get(selectedVerse.id)
|
||||
if (existing) {
|
||||
await deleteHighlight(existing.id)
|
||||
const newMap = new Map(highlights)
|
||||
newMap.delete(selectedVerse.id)
|
||||
setHighlights(newMap)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to remove highlight:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async function performSync() {
|
||||
if (!syncManager.current) return
|
||||
|
||||
try {
|
||||
const pending = await syncManager.current.getPendingSyncItems()
|
||||
if (pending.length === 0) return
|
||||
|
||||
await syncManager.current.markSyncing(pending.map(h => h.id))
|
||||
|
||||
// TODO: POST to /api/highlights/bulk in Phase 2.1B
|
||||
|
||||
await syncManager.current.markSynced(pending.map(h => h.id))
|
||||
} catch (error) {
|
||||
console.error('Sync failed:', error)
|
||||
// Mark items with error status
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', height: 'auto', overflow: 'hidden' }}>
|
||||
{/* Header with search */}
|
||||
@@ -238,6 +349,11 @@ export function BibleReaderApp() {
|
||||
isBookmarked={selectedVerse ? bookmarks.has(selectedVerse.id) : false}
|
||||
onToggleBookmark={handleToggleBookmark}
|
||||
onAddNote={handleAddNote}
|
||||
isHighlighted={highlights.has(selectedVerse?.id || '')}
|
||||
currentHighlightColor={highlights.get(selectedVerse?.id || '')?.color}
|
||||
onHighlightVerse={handleHighlightVerse}
|
||||
onChangeHighlightColor={handleChangeHighlightColor}
|
||||
onRemoveHighlight={handleRemoveHighlight}
|
||||
/>
|
||||
|
||||
{/* Settings panel */}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Box, Paper, Typography, Tabs, Tab, IconButton, useMediaQuery, useTheme, TextField, Button } from '@mui/material'
|
||||
import { Close, Bookmark, BookmarkBorder } from '@mui/icons-material'
|
||||
import { BibleVerse } from '@/types'
|
||||
import { BibleVerse, HighlightColor } from '@/types'
|
||||
import { HighlightsTab } from './highlights-tab'
|
||||
|
||||
interface VersDetailsPanelProps {
|
||||
@@ -12,6 +12,11 @@ interface VersDetailsPanelProps {
|
||||
isBookmarked: boolean
|
||||
onToggleBookmark: () => void
|
||||
onAddNote: (note: string) => void
|
||||
isHighlighted?: boolean
|
||||
currentHighlightColor?: HighlightColor | null
|
||||
onHighlightVerse?: (color: HighlightColor) => void
|
||||
onChangeHighlightColor?: (color: HighlightColor) => void
|
||||
onRemoveHighlight?: () => void
|
||||
}
|
||||
|
||||
export function VersDetailsPanel({
|
||||
@@ -21,6 +26,11 @@ export function VersDetailsPanel({
|
||||
isBookmarked,
|
||||
onToggleBookmark,
|
||||
onAddNote,
|
||||
isHighlighted,
|
||||
currentHighlightColor,
|
||||
onHighlightVerse,
|
||||
onChangeHighlightColor,
|
||||
onRemoveHighlight,
|
||||
}: VersDetailsPanelProps) {
|
||||
const theme = useTheme()
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'))
|
||||
@@ -121,10 +131,16 @@ export function VersDetailsPanel({
|
||||
{tabValue === 1 && (
|
||||
<HighlightsTab
|
||||
verse={verse}
|
||||
isHighlighted={false} // TODO: get from state
|
||||
currentColor={null} // TODO: get from state
|
||||
onToggleHighlight={() => {}} // TODO: implement
|
||||
onColorChange={() => {}} // TODO: implement
|
||||
isHighlighted={isHighlighted || false}
|
||||
currentColor={currentHighlightColor || null}
|
||||
onToggleHighlight={() => {
|
||||
if (isHighlighted) {
|
||||
onRemoveHighlight?.()
|
||||
} else {
|
||||
onHighlightVerse?.('yellow')
|
||||
}
|
||||
}}
|
||||
onColorChange={(color) => onChangeHighlightColor?.(color)}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user