Skip to content

Commit

Permalink
Made clicking anchors and gaps more useful
Browse files Browse the repository at this point in the history
  • Loading branch information
beveradb committed Jan 23, 2025
1 parent e8875f3 commit cd84908
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 33 deletions.
81 changes: 74 additions & 7 deletions lyrics-analyzer/src/components/LyricsAnalyzer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@ import UploadFileIcon from '@mui/icons-material/UploadFile'
import { Box, Button, Grid, Typography, useMediaQuery, useTheme } from '@mui/material'
import { useCallback, useState } from 'react'
import { ApiClient } from '../api'
import { CorrectionData, LyricsData } from '../types'
import { CorrectionData, LyricsData, HighlightInfo } from '../types'
import CorrectionMetrics from './CorrectionMetrics'
import DetailsModal from './DetailsModal'
import ReferenceView from './ReferenceView'
import TranscriptionView from './TranscriptionView'

interface WordClickInfo {
wordIndex: number
type: 'anchor' | 'gap' | 'other'
anchor?: LyricsData['anchor_sequences'][0]
gap?: LyricsData['gap_sequences'][0]
}

interface LyricsAnalyzerProps {
data: CorrectionData
onFileLoad: () => void
Expand All @@ -30,30 +37,86 @@ export type ModalContent = {
}
}

export type FlashType = 'anchor' | 'corrected' | 'uncorrected' | null
export type FlashType = 'anchor' | 'corrected' | 'uncorrected' | 'word' | null

export default function LyricsAnalyzer({ data, onFileLoad, apiClient, isReadOnly }: LyricsAnalyzerProps) {
const [modalContent, setModalContent] = useState<ModalContent | null>(null)
const [flashingType, setFlashingType] = useState<FlashType>(null)
const [highlightInfo, setHighlightInfo] = useState<HighlightInfo | null>(null)
const theme = useTheme()
const isMobile = useMediaQuery(theme.breakpoints.down('md'))

const handleFlash = useCallback((type: FlashType) => {
// Clear any existing flash animation
const handleFlash = useCallback((type: FlashType, info?: HighlightInfo) => {
setFlashingType(null)
setHighlightInfo(null)

// Force a new render cycle before starting the animation
requestAnimationFrame(() => {
requestAnimationFrame(() => {
setFlashingType(type)
// Reset after animation completes
if (info) {
setHighlightInfo(info)
}
setTimeout(() => {
setFlashingType(null)
}, 1200) // Adjusted to match new animation duration (0.4s × 3)
setHighlightInfo(null)
}, 1200)
})
})
}, [])

const handleWordClick = useCallback((info: WordClickInfo) => {
console.group('Word Click Debug Info')

// Log the clicked word info
console.log(`Clicked word index: ${info.wordIndex}`)
console.log(`Word type: ${info.type}`)

if (info.type === 'anchor' && info.anchor) {
// Log detailed anchor info
console.log(`Anchor text: "${info.anchor.text}"`)
console.log(`Anchor position in transcription: ${info.anchor.transcription_position}`)
console.log(`Anchor length: ${info.anchor.length}`)
console.log('Reference positions:', {
genius: info.anchor.reference_positions.genius,
spotify: info.anchor.reference_positions.spotify
})

// Log what we're sending to handleFlash
const highlightInfo = {
type: 'anchor' as const,
transcriptionIndex: info.anchor.transcription_position,
transcriptionLength: info.anchor.length,
referenceIndices: info.anchor.reference_positions,
referenceLength: info.anchor.length
}
console.log('Sending highlight info:', {
type: highlightInfo.type,
transIndex: highlightInfo.transcriptionIndex,
transLength: highlightInfo.transcriptionLength,
refIndices: {
genius: highlightInfo.referenceIndices.genius,
spotify: highlightInfo.referenceIndices.spotify
},
refLength: highlightInfo.referenceLength
})

handleFlash('word', highlightInfo)
} else if (info.type === 'gap' && info.gap) {
// Show modal for gaps on single click
setModalContent({
type: 'gap',
data: {
...info.gap,
position: info.wordIndex,
word: info.gap.text
}
})
} else {
console.log('Word is not part of an anchor sequence or gap')
}
console.groupEnd()
}, [handleFlash, setModalContent])

return (
<Box>
{isReadOnly && (
Expand Down Expand Up @@ -130,7 +193,9 @@ export default function LyricsAnalyzer({ data, onFileLoad, apiClient, isReadOnly
<TranscriptionView
data={data}
onElementClick={setModalContent}
onWordClick={handleWordClick}
flashingType={flashingType}
highlightInfo={highlightInfo}
/>
</Grid>
<Grid item xs={12} md={6}>
Expand All @@ -139,8 +204,10 @@ export default function LyricsAnalyzer({ data, onFileLoad, apiClient, isReadOnly
anchors={data.anchor_sequences}
gaps={data.gap_sequences}
onElementClick={setModalContent}
onWordClick={handleWordClick}
flashingType={flashingType}
corrected_segments={data.corrected_segments}
highlightInfo={highlightInfo}
/>
</Grid>
</Grid>
Expand Down
80 changes: 66 additions & 14 deletions lyrics-analyzer/src/components/ReferenceView.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,39 @@
import { useState } from 'react'
import { Paper, Typography, Box, Button, Tooltip } from '@mui/material'
import { LyricsData, LyricsSegment } from '../types'
import { HighlightInfo, LyricsData, LyricsSegment } from '../types'
import { FlashType, ModalContent } from './LyricsAnalyzer'
import { COLORS } from './constants'
import { HighlightedWord } from './styles'

interface WordClickInfo {
wordIndex: number
type: 'anchor' | 'gap' | 'other'
anchor?: LyricsData['anchor_sequences'][0]
gap?: LyricsData['gap_sequences'][0]
}

interface ReferenceViewProps {
referenceTexts: Record<string, string>
anchors: LyricsData['anchor_sequences']
gaps: LyricsData['gap_sequences']
onElementClick: (content: ModalContent) => void
onWordClick?: (info: WordClickInfo) => void
flashingType: FlashType
corrected_segments: LyricsSegment[]
highlightedWordIndex?: number
highlightInfo: HighlightInfo | null
}

export default function ReferenceView({
referenceTexts,
anchors,
gaps,
onElementClick,
onWordClick,
flashingType,
corrected_segments,
highlightedWordIndex,
highlightInfo,
}: ReferenceViewProps) {
const [currentSource, setCurrentSource] = useState<'genius' | 'spotify'>('genius')

Expand Down Expand Up @@ -130,26 +143,65 @@ export default function ReferenceView({
return thisWordIndex >= position && thisWordIndex < position + correction.length
})

const shouldFlash = Boolean(
(flashingType === 'anchor' && anchor) ||
(flashingType === 'corrected' && correctedGap)
)
const shouldHighlight = (() => {
if (flashingType === 'anchor' && anchor) {
return true
}

if (flashingType === 'word' && highlightInfo?.type === 'anchor' && anchor) {
// Check if this word is part of the highlighted anchor sequence
const refPos = anchor.reference_positions[currentSource]
const highlightPos = highlightInfo.referenceIndices[currentSource]

console.log('Checking word highlight:', {
wordIndex: thisWordIndex,
refPos,
highlightPos,
anchorLength: anchor.length,
isInRange: thisWordIndex >= refPos && thisWordIndex < refPos + anchor.length
})

return refPos === highlightPos &&
anchor.length === highlightInfo.referenceLength &&
thisWordIndex >= refPos &&
thisWordIndex < refPos + anchor.length
}

return false
})()

elements.push(
<HighlightedWord
key={`${word}-${index}-${shouldFlash}`}
shouldFlash={shouldFlash}
key={`${word}-${index}-${shouldHighlight}`}
shouldFlash={shouldHighlight}
style={{
backgroundColor: anchor
? COLORS.anchor
: correctedGap
? COLORS.corrected
: 'transparent',
backgroundColor: flashingType === 'word' && highlightedWordIndex === thisWordIndex
? COLORS.highlighted
: anchor
? COLORS.anchor
: correctedGap
? COLORS.corrected
: 'transparent',
padding: (anchor || correctedGap) ? '2px 4px' : '0',
borderRadius: '3px',
cursor: (anchor || correctedGap) ? 'pointer' : 'default',
cursor: 'pointer',
}}
onClick={(e) => {
if (e.detail === 1) {
setTimeout(() => {
if (!e.defaultPrevented) {
onWordClick?.({
wordIndex: thisWordIndex,
type: anchor ? 'anchor' : correctedGap ? 'gap' : 'other',
anchor,
gap: correctedGap
})
}
}, 200)
}
}}
onClick={() => {
onDoubleClick={(e) => {
e.preventDefault() // Prevent single-click from firing
if (anchor) {
onElementClick({
type: 'anchor',
Expand Down
51 changes: 40 additions & 11 deletions lyrics-analyzer/src/components/TranscriptionView.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
import { Paper, Typography } from '@mui/material'
import { LyricsData } from '../types'
import { LyricsData, HighlightInfo } from '../types'
import { FlashType, ModalContent } from './LyricsAnalyzer'
import { COLORS } from './constants'
import { HighlightedWord } from './styles'

interface WordClickInfo {
wordIndex: number
type: 'anchor' | 'gap' | 'other'
anchor?: LyricsData['anchor_sequences'][0]
gap?: LyricsData['gap_sequences'][0]
}

interface TranscriptionViewProps {
data: LyricsData
onElementClick: (content: ModalContent) => void
onWordClick?: (info: WordClickInfo) => void
flashingType: FlashType
highlightInfo: HighlightInfo | null
}

export default function TranscriptionView({ data, onElementClick, flashingType }: TranscriptionViewProps) {
export default function TranscriptionView({
data,
onElementClick,
onWordClick,
flashingType,
highlightInfo
}: TranscriptionViewProps) {
const renderHighlightedText = () => {
const normalizedText = data.corrected_text.replace(/\n\n+/g, '\n')
const words = normalizedText.split(/(\s+)/)
Expand All @@ -30,14 +45,11 @@ export default function TranscriptionView({ data, onElementClick, flashingType }
})

return words.map((word, index) => {
// Skip whitespace without incrementing wordIndex
if (/^\s+$/.test(word)) {
return word
}

const currentWordIndex = wordIndex

// Find the corresponding gap or anchor in the original text
const anchor = data.anchor_sequences.find(a => {
const start = a.transcription_position
const end = start + a.length
Expand All @@ -50,13 +62,17 @@ export default function TranscriptionView({ data, onElementClick, flashingType }
return currentWordIndex >= start && currentWordIndex < end
})

// Get correction info for current position
const hasCorrections = gap ? gap.corrections.length > 0 : false

const shouldFlash = Boolean(
(flashingType === 'anchor' && anchor) ||
(flashingType === 'corrected' && hasCorrections) ||
(flashingType === 'uncorrected' && gap && !hasCorrections)
(flashingType === 'uncorrected' && gap && !hasCorrections) ||
(flashingType === 'word' && highlightInfo?.type === 'anchor' && anchor && (
anchor.transcription_position === highlightInfo.transcriptionIndex &&
currentWordIndex >= anchor.transcription_position &&
currentWordIndex < anchor.transcription_position + anchor.length
))
)

const wordElement = (
Expand All @@ -73,9 +89,24 @@ export default function TranscriptionView({ data, onElementClick, flashingType }
: 'transparent',
padding: anchor || gap ? '2px 4px' : '0',
borderRadius: '3px',
cursor: anchor || gap ? 'pointer' : 'default',
cursor: 'pointer',
}}
onClick={() => {
onClick={(e) => {
if (e.detail === 1) {
setTimeout(() => {
if (!e.defaultPrevented) {
onWordClick?.({
wordIndex: currentWordIndex,
type: anchor ? 'anchor' : gap ? 'gap' : 'other',
anchor,
gap
})
}
}, 200)
}
}}
onDoubleClick={(e) => {
e.preventDefault() // Prevent single-click from firing
if (anchor) {
onElementClick({
type: 'anchor',
Expand All @@ -100,9 +131,7 @@ export default function TranscriptionView({ data, onElementClick, flashingType }
</HighlightedWord>
)

// Increment word index only for non-whitespace
wordIndex++

return wordElement
})
}
Expand Down
3 changes: 2 additions & 1 deletion lyrics-analyzer/src/components/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const COLORS = {
anchor: '#e3f2fd', // Pale blue
corrected: '#e8f5e9', // Pale green
uncorrectedGap: '#fff3e0', // Pale orange
highlighted: '#ffeb3b', // or any color you prefer for highlighting
} as const

export const flashAnimation = keyframes`
Expand All @@ -13,6 +14,6 @@ export const flashAnimation = keyframes`
}
50% {
opacity: 0.6;
background-color: ${COLORS.anchor};
background-color: ${COLORS.highlighted};
}
`
11 changes: 11 additions & 0 deletions lyrics-analyzer/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,14 @@ export interface CorrectionData {
correction_ratio: number
}
}

export interface HighlightInfo {
transcriptionIndex?: number
transcriptionLength?: number
referenceIndices: {
genius?: number
spotify?: number
}
referenceLength?: number
type: 'single' | 'gap' | 'anchor'
}

0 comments on commit cd84908

Please sign in to comment.