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