lyrics-transcriber 0.44.0__py3-none-any.whl → 0.46.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lyrics_transcriber/frontend/.yarn/releases/{yarn-4.6.0.cjs → yarn-4.7.0.cjs} +292 -291
- lyrics_transcriber/frontend/.yarnrc.yml +1 -1
- lyrics_transcriber/frontend/dist/assets/{index-DVoI6Z16.js → index-BXOpmKq-.js} +2715 -2030
- lyrics_transcriber/frontend/dist/assets/index-BXOpmKq-.js.map +1 -0
- lyrics_transcriber/frontend/dist/index.html +1 -1
- lyrics_transcriber/frontend/package.json +1 -1
- lyrics_transcriber/frontend/src/App.tsx +1 -1
- lyrics_transcriber/frontend/src/components/EditActionBar.tsx +68 -0
- lyrics_transcriber/frontend/src/components/EditModal.tsx +376 -303
- lyrics_transcriber/frontend/src/components/EditTimelineSection.tsx +373 -0
- lyrics_transcriber/frontend/src/components/EditWordList.tsx +308 -0
- lyrics_transcriber/frontend/src/components/Header.tsx +8 -23
- lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +460 -62
- lyrics_transcriber/frontend/src/components/ReferenceView.tsx +3 -1
- lyrics_transcriber/frontend/src/components/TranscriptionView.tsx +7 -7
- lyrics_transcriber/frontend/src/components/WordDivider.tsx +4 -3
- lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +4 -2
- lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +26 -3
- lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +1 -1
- lyrics_transcriber/frontend/src/components/shared/types.ts +2 -0
- lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +68 -46
- lyrics_transcriber/frontend/tsconfig.tsbuildinfo +1 -1
- {lyrics_transcriber-0.44.0.dist-info → lyrics_transcriber-0.46.0.dist-info}/METADATA +1 -1
- {lyrics_transcriber-0.44.0.dist-info → lyrics_transcriber-0.46.0.dist-info}/RECORD +27 -25
- lyrics_transcriber/frontend/dist/assets/index-DVoI6Z16.js.map +0 -1
- lyrics_transcriber/frontend/src/components/GlobalSyncEditor.tsx +0 -675
- {lyrics_transcriber-0.44.0.dist-info → lyrics_transcriber-0.46.0.dist-info}/LICENSE +0 -0
- {lyrics_transcriber-0.44.0.dist-info → lyrics_transcriber-0.46.0.dist-info}/WHEEL +0 -0
- {lyrics_transcriber-0.44.0.dist-info → lyrics_transcriber-0.46.0.dist-info}/entry_points.txt +0 -0
@@ -1,11 +1,13 @@
|
|
1
|
-
import { useState, useEffect, useCallback } from 'react'
|
1
|
+
import { useState, useEffect, useCallback, useMemo, memo } from 'react'
|
2
2
|
import {
|
3
3
|
AnchorSequence,
|
4
4
|
CorrectionData,
|
5
5
|
GapSequence,
|
6
6
|
HighlightInfo,
|
7
7
|
InteractionMode,
|
8
|
-
LyricsSegment
|
8
|
+
LyricsSegment,
|
9
|
+
ReferenceSource,
|
10
|
+
WordCorrection
|
9
11
|
} from '../types'
|
10
12
|
import { Box, Button, Grid, useMediaQuery, useTheme } from '@mui/material'
|
11
13
|
import { ApiClient } from '../api'
|
@@ -29,7 +31,6 @@ import { getWordsFromIds } from './shared/utils/wordUtils'
|
|
29
31
|
import AddLyricsModal from './AddLyricsModal'
|
30
32
|
import { RestoreFromTrash, OndemandVideo } from '@mui/icons-material'
|
31
33
|
import FindReplaceModal from './FindReplaceModal'
|
32
|
-
import GlobalSyncEditor from './GlobalSyncEditor'
|
33
34
|
|
34
35
|
// Add type for window augmentation at the top of the file
|
35
36
|
declare global {
|
@@ -39,6 +40,7 @@ declare global {
|
|
39
40
|
}
|
40
41
|
}
|
41
42
|
|
43
|
+
const debugLog = false;
|
42
44
|
export interface LyricsAnalyzerProps {
|
43
45
|
data: CorrectionData
|
44
46
|
onFileLoad: () => void
|
@@ -64,6 +66,161 @@ export type ModalContent = {
|
|
64
66
|
}
|
65
67
|
}
|
66
68
|
|
69
|
+
// Define types for the memoized components
|
70
|
+
interface MemoizedTranscriptionViewProps {
|
71
|
+
data: CorrectionData
|
72
|
+
mode: InteractionMode
|
73
|
+
onElementClick: (content: ModalContent) => void
|
74
|
+
onWordClick: (info: WordClickInfo) => void
|
75
|
+
flashingType: FlashType
|
76
|
+
flashingHandler: string | null
|
77
|
+
highlightInfo: HighlightInfo | null
|
78
|
+
onPlaySegment?: (time: number) => void
|
79
|
+
currentTime: number
|
80
|
+
anchors: AnchorSequence[]
|
81
|
+
disableHighlighting: boolean
|
82
|
+
}
|
83
|
+
|
84
|
+
// Create a memoized TranscriptionView component
|
85
|
+
const MemoizedTranscriptionView = memo(function MemoizedTranscriptionView({
|
86
|
+
data,
|
87
|
+
mode,
|
88
|
+
onElementClick,
|
89
|
+
onWordClick,
|
90
|
+
flashingType,
|
91
|
+
flashingHandler,
|
92
|
+
highlightInfo,
|
93
|
+
onPlaySegment,
|
94
|
+
currentTime,
|
95
|
+
anchors,
|
96
|
+
disableHighlighting
|
97
|
+
}: MemoizedTranscriptionViewProps) {
|
98
|
+
return (
|
99
|
+
<TranscriptionView
|
100
|
+
data={data}
|
101
|
+
mode={mode}
|
102
|
+
onElementClick={onElementClick}
|
103
|
+
onWordClick={onWordClick}
|
104
|
+
flashingType={flashingType}
|
105
|
+
flashingHandler={flashingHandler}
|
106
|
+
highlightInfo={highlightInfo}
|
107
|
+
onPlaySegment={onPlaySegment}
|
108
|
+
currentTime={disableHighlighting ? undefined : currentTime}
|
109
|
+
anchors={anchors}
|
110
|
+
/>
|
111
|
+
);
|
112
|
+
});
|
113
|
+
|
114
|
+
interface MemoizedReferenceViewProps {
|
115
|
+
referenceSources: Record<string, ReferenceSource>
|
116
|
+
anchors: AnchorSequence[]
|
117
|
+
gaps: GapSequence[]
|
118
|
+
mode: InteractionMode
|
119
|
+
onElementClick: (content: ModalContent) => void
|
120
|
+
onWordClick: (info: WordClickInfo) => void
|
121
|
+
flashingType: FlashType
|
122
|
+
highlightInfo: HighlightInfo | null
|
123
|
+
currentSource: string
|
124
|
+
onSourceChange: (source: string) => void
|
125
|
+
corrected_segments: LyricsSegment[]
|
126
|
+
corrections: WordCorrection[]
|
127
|
+
onAddLyrics?: () => void
|
128
|
+
}
|
129
|
+
|
130
|
+
// Create a memoized ReferenceView component
|
131
|
+
const MemoizedReferenceView = memo(function MemoizedReferenceView({
|
132
|
+
referenceSources,
|
133
|
+
anchors,
|
134
|
+
gaps,
|
135
|
+
mode,
|
136
|
+
onElementClick,
|
137
|
+
onWordClick,
|
138
|
+
flashingType,
|
139
|
+
highlightInfo,
|
140
|
+
currentSource,
|
141
|
+
onSourceChange,
|
142
|
+
corrected_segments,
|
143
|
+
corrections,
|
144
|
+
onAddLyrics
|
145
|
+
}: MemoizedReferenceViewProps) {
|
146
|
+
return (
|
147
|
+
<ReferenceView
|
148
|
+
referenceSources={referenceSources}
|
149
|
+
anchors={anchors}
|
150
|
+
gaps={gaps}
|
151
|
+
mode={mode}
|
152
|
+
onElementClick={onElementClick}
|
153
|
+
onWordClick={onWordClick}
|
154
|
+
flashingType={flashingType}
|
155
|
+
highlightInfo={highlightInfo}
|
156
|
+
currentSource={currentSource}
|
157
|
+
onSourceChange={onSourceChange}
|
158
|
+
corrected_segments={corrected_segments}
|
159
|
+
corrections={corrections}
|
160
|
+
onAddLyrics={onAddLyrics}
|
161
|
+
/>
|
162
|
+
);
|
163
|
+
});
|
164
|
+
|
165
|
+
interface MemoizedHeaderProps {
|
166
|
+
isReadOnly: boolean
|
167
|
+
onFileLoad: () => void
|
168
|
+
data: CorrectionData
|
169
|
+
onMetricClick: {
|
170
|
+
anchor: () => void
|
171
|
+
corrected: () => void
|
172
|
+
uncorrected: () => void
|
173
|
+
}
|
174
|
+
effectiveMode: InteractionMode
|
175
|
+
onModeChange: (mode: InteractionMode) => void
|
176
|
+
apiClient: ApiClient | null
|
177
|
+
audioHash: string
|
178
|
+
onTimeUpdate: (time: number) => void
|
179
|
+
onHandlerToggle: (handler: string, enabled: boolean) => void
|
180
|
+
isUpdatingHandlers: boolean
|
181
|
+
onHandlerClick?: (handler: string) => void
|
182
|
+
onAddLyrics?: () => void
|
183
|
+
onFindReplace?: () => void
|
184
|
+
onEditAll?: () => void
|
185
|
+
}
|
186
|
+
|
187
|
+
// Create a memoized Header component
|
188
|
+
const MemoizedHeader = memo(function MemoizedHeader({
|
189
|
+
isReadOnly,
|
190
|
+
onFileLoad,
|
191
|
+
data,
|
192
|
+
onMetricClick,
|
193
|
+
effectiveMode,
|
194
|
+
onModeChange,
|
195
|
+
apiClient,
|
196
|
+
audioHash,
|
197
|
+
onTimeUpdate,
|
198
|
+
onHandlerToggle,
|
199
|
+
isUpdatingHandlers,
|
200
|
+
onHandlerClick,
|
201
|
+
onFindReplace,
|
202
|
+
onEditAll
|
203
|
+
}: MemoizedHeaderProps) {
|
204
|
+
return (
|
205
|
+
<Header
|
206
|
+
isReadOnly={isReadOnly}
|
207
|
+
onFileLoad={onFileLoad}
|
208
|
+
data={data}
|
209
|
+
onMetricClick={onMetricClick}
|
210
|
+
effectiveMode={effectiveMode}
|
211
|
+
onModeChange={onModeChange}
|
212
|
+
apiClient={apiClient}
|
213
|
+
audioHash={audioHash}
|
214
|
+
onTimeUpdate={onTimeUpdate}
|
215
|
+
onHandlerToggle={onHandlerToggle}
|
216
|
+
isUpdatingHandlers={isUpdatingHandlers}
|
217
|
+
onHandlerClick={onHandlerClick}
|
218
|
+
onFindReplace={onFindReplace}
|
219
|
+
onEditAll={onEditAll}
|
220
|
+
/>
|
221
|
+
);
|
222
|
+
});
|
223
|
+
|
67
224
|
export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly, audioHash }: LyricsAnalyzerProps) {
|
68
225
|
const [modalContent, setModalContent] = useState<ModalContent | null>(null)
|
69
226
|
const [flashingType, setFlashingType] = useState<FlashType>(null)
|
@@ -85,6 +242,11 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
85
242
|
index: number
|
86
243
|
originalSegment: LyricsSegment
|
87
244
|
} | null>(null)
|
245
|
+
const [isEditAllModalOpen, setIsEditAllModalOpen] = useState(false)
|
246
|
+
const [globalEditSegment, setGlobalEditSegment] = useState<LyricsSegment | null>(null)
|
247
|
+
const [originalGlobalSegment, setOriginalGlobalSegment] = useState<LyricsSegment | null>(null)
|
248
|
+
const [originalTranscribedGlobalSegment, setOriginalTranscribedGlobalSegment] = useState<LyricsSegment | null>(null)
|
249
|
+
const [isLoadingGlobalEdit, setIsLoadingGlobalEdit] = useState(false)
|
88
250
|
const [isReviewModalOpen, setIsReviewModalOpen] = useState(false)
|
89
251
|
const [currentAudioTime, setCurrentAudioTime] = useState(0)
|
90
252
|
const [isUpdatingHandlers, setIsUpdatingHandlers] = useState(false)
|
@@ -93,24 +255,25 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
93
255
|
const [isAddLyricsModalOpen, setIsAddLyricsModalOpen] = useState(false)
|
94
256
|
const [isAnyModalOpen, setIsAnyModalOpen] = useState(false)
|
95
257
|
const [isFindReplaceModalOpen, setIsFindReplaceModalOpen] = useState(false)
|
96
|
-
const [isGlobalSyncEditorOpen, setIsGlobalSyncEditorOpen] = useState(false)
|
97
258
|
const theme = useTheme()
|
98
259
|
const isMobile = useMediaQuery(theme.breakpoints.down('md'))
|
99
260
|
|
100
261
|
// Update debug logging to use new ID-based structure
|
101
262
|
useEffect(() => {
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
263
|
+
if (debugLog) {
|
264
|
+
console.log('LyricsAnalyzer Initial Data:', {
|
265
|
+
hasData: !!initialData,
|
266
|
+
segmentsCount: initialData?.corrected_segments?.length ?? 0,
|
267
|
+
anchorsCount: initialData?.anchor_sequences?.length ?? 0,
|
268
|
+
gapsCount: initialData?.gap_sequences?.length ?? 0,
|
269
|
+
firstAnchor: initialData?.anchor_sequences?.[0] && {
|
270
|
+
transcribedWordIds: initialData.anchor_sequences[0].transcribed_word_ids,
|
271
|
+
referenceWordIds: initialData.anchor_sequences[0].reference_word_ids
|
272
|
+
},
|
273
|
+
firstSegment: initialData?.corrected_segments?.[0],
|
274
|
+
referenceSources: Object.keys(initialData?.reference_lyrics ?? {})
|
275
|
+
});
|
276
|
+
}
|
114
277
|
}, [initialData]);
|
115
278
|
|
116
279
|
// Load saved data
|
@@ -132,17 +295,21 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
132
295
|
useEffect(() => {
|
133
296
|
const { currentModalHandler } = getModalState()
|
134
297
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
298
|
+
if (debugLog) {
|
299
|
+
console.log('LyricsAnalyzer - Setting up keyboard effect', {
|
300
|
+
isAnyModalOpen,
|
301
|
+
hasSpacebarHandler: !!currentModalHandler
|
302
|
+
})
|
303
|
+
}
|
139
304
|
|
140
305
|
const { handleKeyDown, handleKeyUp } = setupKeyboardHandlers({
|
141
306
|
setIsShiftPressed,
|
142
307
|
})
|
143
308
|
|
144
309
|
// Always add keyboard listeners
|
145
|
-
|
310
|
+
if (debugLog) {
|
311
|
+
console.log('LyricsAnalyzer - Adding keyboard event listeners')
|
312
|
+
}
|
146
313
|
window.addEventListener('keydown', handleKeyDown)
|
147
314
|
window.addEventListener('keyup', handleKeyUp)
|
148
315
|
|
@@ -153,7 +320,9 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
153
320
|
|
154
321
|
// Cleanup function
|
155
322
|
return () => {
|
156
|
-
|
323
|
+
if (debugLog) {
|
324
|
+
console.log('LyricsAnalyzer - Cleanup effect running')
|
325
|
+
}
|
157
326
|
window.removeEventListener('keydown', handleKeyDown)
|
158
327
|
window.removeEventListener('keyup', handleKeyUp)
|
159
328
|
document.body.style.userSelect = ''
|
@@ -167,10 +336,11 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
167
336
|
editModalSegment ||
|
168
337
|
isReviewModalOpen ||
|
169
338
|
isAddLyricsModalOpen ||
|
170
|
-
isFindReplaceModalOpen
|
339
|
+
isFindReplaceModalOpen ||
|
340
|
+
isEditAllModalOpen
|
171
341
|
)
|
172
342
|
setIsAnyModalOpen(modalOpen)
|
173
|
-
}, [modalContent, editModalSegment, isReviewModalOpen, isAddLyricsModalOpen, isFindReplaceModalOpen])
|
343
|
+
}, [modalContent, editModalSegment, isReviewModalOpen, isAddLyricsModalOpen, isFindReplaceModalOpen, isEditAllModalOpen])
|
174
344
|
|
175
345
|
// Calculate effective mode based on modifier key states
|
176
346
|
const effectiveMode = isShiftPressed ? 'highlight' : interactionMode
|
@@ -194,7 +364,9 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
194
364
|
}, [])
|
195
365
|
|
196
366
|
const handleWordClick = useCallback((info: WordClickInfo) => {
|
197
|
-
|
367
|
+
if (debugLog) {
|
368
|
+
console.log('LyricsAnalyzer handleWordClick:', { info });
|
369
|
+
}
|
198
370
|
|
199
371
|
if (effectiveMode === 'highlight') {
|
200
372
|
// Find if this word is part of a correction
|
@@ -342,7 +514,9 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
342
514
|
if (!apiClient) return
|
343
515
|
|
344
516
|
try {
|
345
|
-
|
517
|
+
if (debugLog) {
|
518
|
+
console.log('Submitting changes to server')
|
519
|
+
}
|
346
520
|
await apiClient.submitCorrections(data)
|
347
521
|
|
348
522
|
setIsReviewComplete(true)
|
@@ -431,15 +605,20 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
431
605
|
}, [apiClient, data.metadata.enabled_handlers, handleFlash])
|
432
606
|
|
433
607
|
const handleHandlerClick = useCallback((handler: string) => {
|
434
|
-
|
608
|
+
if (debugLog) {
|
609
|
+
console.log('Handler clicked:', handler);
|
610
|
+
}
|
435
611
|
setFlashingHandler(handler);
|
436
612
|
setFlashingType('handler');
|
437
|
-
|
438
|
-
|
439
|
-
|
613
|
+
if (debugLog) {
|
614
|
+
console.log('Set flashingHandler to:', handler);
|
615
|
+
console.log('Set flashingType to: handler');
|
616
|
+
}
|
440
617
|
// Clear the flash after a short delay
|
441
618
|
setTimeout(() => {
|
442
|
-
|
619
|
+
if (debugLog) {
|
620
|
+
console.log('Clearing flash state');
|
621
|
+
}
|
443
622
|
setFlashingHandler(null);
|
444
623
|
setFlashingType(null);
|
445
624
|
}, 1500);
|
@@ -447,9 +626,11 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
447
626
|
|
448
627
|
// Wrap setModalSpacebarHandler in useCallback
|
449
628
|
const handleSetModalSpacebarHandler = useCallback((handler: (() => (e: KeyboardEvent) => void) | undefined) => {
|
450
|
-
|
451
|
-
|
452
|
-
|
629
|
+
if (debugLog) {
|
630
|
+
console.log('LyricsAnalyzer - Setting modal handler:', {
|
631
|
+
hasHandler: !!handler
|
632
|
+
})
|
633
|
+
}
|
453
634
|
// Update the global modal handler
|
454
635
|
setModalHandler(handler ? handler() : undefined, !!handler)
|
455
636
|
}, [])
|
@@ -472,6 +653,221 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
472
653
|
setData(newData)
|
473
654
|
}
|
474
655
|
|
656
|
+
// Add handler for Edit All functionality
|
657
|
+
const handleEditAll = useCallback(() => {
|
658
|
+
console.log('EditAll - Starting process');
|
659
|
+
|
660
|
+
// Create empty placeholder segments to prevent the modal from closing
|
661
|
+
const placeholderSegment: LyricsSegment = {
|
662
|
+
id: 'loading-placeholder',
|
663
|
+
words: [],
|
664
|
+
text: '',
|
665
|
+
start_time: 0,
|
666
|
+
end_time: 1
|
667
|
+
};
|
668
|
+
|
669
|
+
// Set placeholder segments first
|
670
|
+
setGlobalEditSegment(placeholderSegment);
|
671
|
+
setOriginalGlobalSegment(placeholderSegment);
|
672
|
+
|
673
|
+
// Show loading state
|
674
|
+
setIsLoadingGlobalEdit(true);
|
675
|
+
console.log('EditAll - Set loading state to true');
|
676
|
+
|
677
|
+
// Open the modal with placeholder data
|
678
|
+
setIsEditAllModalOpen(true);
|
679
|
+
console.log('EditAll - Set modal open to true');
|
680
|
+
|
681
|
+
// Use requestAnimationFrame to ensure the modal with loading state is rendered
|
682
|
+
// before doing the expensive operation
|
683
|
+
requestAnimationFrame(() => {
|
684
|
+
console.log('EditAll - Inside requestAnimationFrame');
|
685
|
+
|
686
|
+
// Use setTimeout to allow the modal to render before doing the expensive operation
|
687
|
+
setTimeout(() => {
|
688
|
+
console.log('EditAll - Inside setTimeout, starting data processing');
|
689
|
+
|
690
|
+
try {
|
691
|
+
console.time('EditAll - Data processing');
|
692
|
+
|
693
|
+
// Create a combined segment with all words from all segments
|
694
|
+
const allWords = data.corrected_segments.flatMap(segment => segment.words)
|
695
|
+
console.log(`EditAll - Collected ${allWords.length} words from all segments`);
|
696
|
+
|
697
|
+
// Sort words by start time to maintain chronological order
|
698
|
+
const sortedWords = [...allWords].sort((a, b) => {
|
699
|
+
const aTime = a.start_time ?? 0
|
700
|
+
const bTime = b.start_time ?? 0
|
701
|
+
return aTime - bTime
|
702
|
+
})
|
703
|
+
console.log('EditAll - Sorted words by start time');
|
704
|
+
|
705
|
+
// Create a global segment containing all words
|
706
|
+
const globalSegment: LyricsSegment = {
|
707
|
+
id: 'global-edit',
|
708
|
+
words: sortedWords,
|
709
|
+
text: sortedWords.map(w => w.text).join(' '),
|
710
|
+
start_time: sortedWords[0]?.start_time ?? null,
|
711
|
+
end_time: sortedWords[sortedWords.length - 1]?.end_time ?? null
|
712
|
+
}
|
713
|
+
console.log('EditAll - Created global segment');
|
714
|
+
|
715
|
+
// Store the original global segment for reset functionality
|
716
|
+
setGlobalEditSegment(globalSegment)
|
717
|
+
console.log('EditAll - Set global edit segment');
|
718
|
+
|
719
|
+
setOriginalGlobalSegment(JSON.parse(JSON.stringify(globalSegment)))
|
720
|
+
console.log('EditAll - Set original global segment');
|
721
|
+
|
722
|
+
// Create the original transcribed global segment for Un-Correct functionality
|
723
|
+
if (originalData.original_segments) {
|
724
|
+
console.log('EditAll - Processing original segments for Un-Correct functionality');
|
725
|
+
|
726
|
+
// Get all words from original segments
|
727
|
+
const originalWords = originalData.original_segments.flatMap((segment: LyricsSegment) => segment.words)
|
728
|
+
console.log(`EditAll - Collected ${originalWords.length} words from original segments`);
|
729
|
+
|
730
|
+
// Sort words by start time
|
731
|
+
const sortedOriginalWords = [...originalWords].sort((a, b) => {
|
732
|
+
const aTime = a.start_time ?? 0
|
733
|
+
const bTime = b.start_time ?? 0
|
734
|
+
return aTime - bTime
|
735
|
+
})
|
736
|
+
console.log('EditAll - Sorted original words by start time');
|
737
|
+
|
738
|
+
// Create the original transcribed global segment
|
739
|
+
const originalTranscribedGlobal: LyricsSegment = {
|
740
|
+
id: 'original-transcribed-global',
|
741
|
+
words: sortedOriginalWords,
|
742
|
+
text: sortedOriginalWords.map(w => w.text).join(' '),
|
743
|
+
start_time: sortedOriginalWords[0]?.start_time ?? null,
|
744
|
+
end_time: sortedOriginalWords[sortedOriginalWords.length - 1]?.end_time ?? null
|
745
|
+
}
|
746
|
+
console.log('EditAll - Created original transcribed global segment');
|
747
|
+
|
748
|
+
setOriginalTranscribedGlobalSegment(originalTranscribedGlobal)
|
749
|
+
console.log('EditAll - Set original transcribed global segment');
|
750
|
+
} else {
|
751
|
+
setOriginalTranscribedGlobalSegment(null)
|
752
|
+
console.log('EditAll - No original segments found, set original transcribed global segment to null');
|
753
|
+
}
|
754
|
+
|
755
|
+
console.timeEnd('EditAll - Data processing');
|
756
|
+
} catch (error) {
|
757
|
+
console.error('Error preparing global edit data:', error);
|
758
|
+
} finally {
|
759
|
+
// Clear loading state
|
760
|
+
console.log('EditAll - Finished processing, setting loading state to false');
|
761
|
+
setIsLoadingGlobalEdit(false);
|
762
|
+
}
|
763
|
+
}, 100); // Small delay to allow the modal to render
|
764
|
+
});
|
765
|
+
}, [data.corrected_segments, originalData.original_segments])
|
766
|
+
|
767
|
+
// Handle saving the global edit
|
768
|
+
const handleSaveGlobalEdit = useCallback((updatedSegment: LyricsSegment) => {
|
769
|
+
console.log('Global Edit - Saving with new approach:', {
|
770
|
+
updatedSegmentId: updatedSegment.id,
|
771
|
+
wordCount: updatedSegment.words.length,
|
772
|
+
originalSegmentCount: data.corrected_segments.length,
|
773
|
+
originalTotalWordCount: data.corrected_segments.reduce((count, segment) => count + segment.words.length, 0)
|
774
|
+
})
|
775
|
+
|
776
|
+
// Get the updated words from the global segment
|
777
|
+
const updatedWords = updatedSegment.words
|
778
|
+
|
779
|
+
// Create a new array of segments with the same structure as the original
|
780
|
+
const updatedSegments = []
|
781
|
+
let wordIndex = 0
|
782
|
+
|
783
|
+
// Distribute words to segments based on the original segment sizes
|
784
|
+
for (const segment of data.corrected_segments) {
|
785
|
+
const originalWordCount = segment.words.length
|
786
|
+
|
787
|
+
// Get the words for this segment from the updated global segment
|
788
|
+
const segmentWords = []
|
789
|
+
const endIndex = Math.min(wordIndex + originalWordCount, updatedWords.length)
|
790
|
+
|
791
|
+
for (let i = wordIndex; i < endIndex; i++) {
|
792
|
+
segmentWords.push(updatedWords[i])
|
793
|
+
}
|
794
|
+
|
795
|
+
// Update the word index for the next segment
|
796
|
+
wordIndex = endIndex
|
797
|
+
|
798
|
+
// If we have words for this segment, create an updated segment
|
799
|
+
if (segmentWords.length > 0) {
|
800
|
+
// Recalculate segment start and end times
|
801
|
+
const validStartTimes = segmentWords.map(w => w.start_time).filter((t): t is number => t !== null)
|
802
|
+
const validEndTimes = segmentWords.map(w => w.end_time).filter((t): t is number => t !== null)
|
803
|
+
|
804
|
+
const segmentStartTime = validStartTimes.length > 0 ? Math.min(...validStartTimes) : null
|
805
|
+
const segmentEndTime = validEndTimes.length > 0 ? Math.max(...validEndTimes) : null
|
806
|
+
|
807
|
+
// Create the updated segment
|
808
|
+
updatedSegments.push({
|
809
|
+
...segment,
|
810
|
+
words: segmentWords,
|
811
|
+
text: segmentWords.map(w => w.text).join(' '),
|
812
|
+
start_time: segmentStartTime,
|
813
|
+
end_time: segmentEndTime
|
814
|
+
})
|
815
|
+
}
|
816
|
+
}
|
817
|
+
|
818
|
+
// If there are any remaining words, add them to the last segment
|
819
|
+
if (wordIndex < updatedWords.length) {
|
820
|
+
const remainingWords = updatedWords.slice(wordIndex)
|
821
|
+
const lastSegment = updatedSegments[updatedSegments.length - 1]
|
822
|
+
|
823
|
+
// Combine the remaining words with the last segment
|
824
|
+
const combinedWords = [...lastSegment.words, ...remainingWords]
|
825
|
+
|
826
|
+
// Recalculate segment start and end times
|
827
|
+
const validStartTimes = combinedWords.map(w => w.start_time).filter((t): t is number => t !== null)
|
828
|
+
const validEndTimes = combinedWords.map(w => w.end_time).filter((t): t is number => t !== null)
|
829
|
+
|
830
|
+
const segmentStartTime = validStartTimes.length > 0 ? Math.min(...validStartTimes) : null
|
831
|
+
const segmentEndTime = validEndTimes.length > 0 ? Math.max(...validEndTimes) : null
|
832
|
+
|
833
|
+
// Update the last segment
|
834
|
+
updatedSegments[updatedSegments.length - 1] = {
|
835
|
+
...lastSegment,
|
836
|
+
words: combinedWords,
|
837
|
+
text: combinedWords.map(w => w.text).join(' '),
|
838
|
+
start_time: segmentStartTime,
|
839
|
+
end_time: segmentEndTime
|
840
|
+
}
|
841
|
+
}
|
842
|
+
|
843
|
+
console.log('Global Edit - Updated Segments with new approach:', {
|
844
|
+
segmentCount: updatedSegments.length,
|
845
|
+
firstSegmentWordCount: updatedSegments[0]?.words.length,
|
846
|
+
totalWordCount: updatedSegments.reduce((count, segment) => count + segment.words.length, 0),
|
847
|
+
originalTotalWordCount: data.corrected_segments.reduce((count, segment) => count + segment.words.length, 0)
|
848
|
+
})
|
849
|
+
|
850
|
+
// Update the data with the new segments
|
851
|
+
setData({
|
852
|
+
...data,
|
853
|
+
corrected_segments: updatedSegments
|
854
|
+
})
|
855
|
+
|
856
|
+
// Close the modal
|
857
|
+
setIsEditAllModalOpen(false)
|
858
|
+
setGlobalEditSegment(null)
|
859
|
+
}, [data])
|
860
|
+
|
861
|
+
// Memoize the metric click handlers
|
862
|
+
const metricClickHandlers = useMemo(() => ({
|
863
|
+
anchor: () => handleFlash('anchor'),
|
864
|
+
corrected: () => handleFlash('corrected'),
|
865
|
+
uncorrected: () => handleFlash('uncorrected')
|
866
|
+
}), [handleFlash]);
|
867
|
+
|
868
|
+
// Determine if any modal is open to disable highlighting
|
869
|
+
const isAnyModalOpenMemo = useMemo(() => isAnyModalOpen, [isAnyModalOpen]);
|
870
|
+
|
475
871
|
return (
|
476
872
|
<Box sx={{
|
477
873
|
p: 1,
|
@@ -479,15 +875,11 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
479
875
|
maxWidth: '100%',
|
480
876
|
overflowX: 'hidden'
|
481
877
|
}}>
|
482
|
-
<
|
878
|
+
<MemoizedHeader
|
483
879
|
isReadOnly={isReadOnly}
|
484
880
|
onFileLoad={onFileLoad}
|
485
881
|
data={data}
|
486
|
-
onMetricClick={
|
487
|
-
anchor: () => handleFlash('anchor'),
|
488
|
-
corrected: () => handleFlash('corrected'),
|
489
|
-
uncorrected: () => handleFlash('uncorrected')
|
490
|
-
}}
|
882
|
+
onMetricClick={metricClickHandlers}
|
491
883
|
effectiveMode={effectiveMode}
|
492
884
|
onModeChange={setInteractionMode}
|
493
885
|
apiClient={apiClient}
|
@@ -498,12 +890,12 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
498
890
|
onHandlerClick={handleHandlerClick}
|
499
891
|
onAddLyrics={() => setIsAddLyricsModalOpen(true)}
|
500
892
|
onFindReplace={() => setIsFindReplaceModalOpen(true)}
|
501
|
-
|
893
|
+
onEditAll={handleEditAll}
|
502
894
|
/>
|
503
895
|
|
504
|
-
<Grid container
|
896
|
+
<Grid container direction={isMobile ? 'column' : 'row'}>
|
505
897
|
<Grid item xs={12} md={6}>
|
506
|
-
<
|
898
|
+
<MemoizedTranscriptionView
|
507
899
|
data={data}
|
508
900
|
mode={effectiveMode}
|
509
901
|
onElementClick={setModalContent}
|
@@ -514,6 +906,7 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
514
906
|
onPlaySegment={handlePlaySegment}
|
515
907
|
currentTime={currentAudioTime}
|
516
908
|
anchors={data.anchor_sequences}
|
909
|
+
disableHighlighting={isAnyModalOpenMemo}
|
517
910
|
/>
|
518
911
|
{!isReadOnly && apiClient && (
|
519
912
|
<Box sx={{
|
@@ -543,7 +936,7 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
543
936
|
)}
|
544
937
|
</Grid>
|
545
938
|
<Grid item xs={12} md={6}>
|
546
|
-
<
|
939
|
+
<MemoizedReferenceView
|
547
940
|
referenceSources={data.reference_lyrics}
|
548
941
|
anchors={data.anchor_sequences}
|
549
942
|
gaps={data.gap_sequences}
|
@@ -556,10 +949,32 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
556
949
|
onSourceChange={setCurrentSource}
|
557
950
|
corrected_segments={data.corrected_segments}
|
558
951
|
corrections={data.corrections}
|
952
|
+
onAddLyrics={() => setIsAddLyricsModalOpen(true)}
|
559
953
|
/>
|
560
954
|
</Grid>
|
561
955
|
</Grid>
|
562
956
|
|
957
|
+
<EditModal
|
958
|
+
open={isEditAllModalOpen}
|
959
|
+
onClose={() => {
|
960
|
+
setIsEditAllModalOpen(false)
|
961
|
+
setGlobalEditSegment(null)
|
962
|
+
setOriginalGlobalSegment(null)
|
963
|
+
setOriginalTranscribedGlobalSegment(null)
|
964
|
+
handleSetModalSpacebarHandler(undefined)
|
965
|
+
}}
|
966
|
+
segment={globalEditSegment}
|
967
|
+
segmentIndex={null}
|
968
|
+
originalSegment={originalGlobalSegment}
|
969
|
+
onSave={handleSaveGlobalEdit}
|
970
|
+
onPlaySegment={handlePlaySegment}
|
971
|
+
currentTime={currentAudioTime}
|
972
|
+
setModalSpacebarHandler={handleSetModalSpacebarHandler}
|
973
|
+
originalTranscribedSegment={originalTranscribedGlobalSegment}
|
974
|
+
isGlobal={true}
|
975
|
+
isLoading={isLoadingGlobalEdit}
|
976
|
+
/>
|
977
|
+
|
563
978
|
<EditModal
|
564
979
|
open={Boolean(editModalSegment)}
|
565
980
|
onClose={() => {
|
@@ -581,7 +996,7 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
581
996
|
editModalSegment?.segment && editModalSegment?.index !== null
|
582
997
|
? originalData.original_segments.find(
|
583
998
|
(s: LyricsSegment) => s.id === editModalSegment.segment.id
|
584
|
-
|
999
|
+
) || null
|
585
1000
|
: null
|
586
1001
|
}
|
587
1002
|
/>
|
@@ -609,23 +1024,6 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
609
1024
|
onReplace={handleFindReplace}
|
610
1025
|
data={data}
|
611
1026
|
/>
|
612
|
-
|
613
|
-
{/* Add GlobalSyncEditor modal */}
|
614
|
-
{!isReadOnly && (
|
615
|
-
<GlobalSyncEditor
|
616
|
-
open={isGlobalSyncEditorOpen}
|
617
|
-
onClose={() => setIsGlobalSyncEditorOpen(false)}
|
618
|
-
segments={data.corrected_segments || []}
|
619
|
-
onSave={(updatedSegments) => {
|
620
|
-
const newData = { ...data, corrected_segments: updatedSegments };
|
621
|
-
setData(newData);
|
622
|
-
saveData(newData, initialData);
|
623
|
-
}}
|
624
|
-
onPlaySegment={handlePlaySegment}
|
625
|
-
currentTime={currentAudioTime}
|
626
|
-
setModalSpacebarHandler={handleSetModalSpacebarHandler}
|
627
|
-
/>
|
628
|
-
)}
|
629
1027
|
</Box>
|
630
1028
|
)
|
631
1029
|
}
|