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
@@ -5,25 +5,148 @@ import {
|
|
5
5
|
DialogActions,
|
6
6
|
IconButton,
|
7
7
|
Box,
|
8
|
-
|
9
|
-
Button,
|
8
|
+
CircularProgress,
|
10
9
|
Typography
|
11
10
|
} from '@mui/material'
|
12
11
|
import CloseIcon from '@mui/icons-material/Close'
|
13
|
-
import DeleteIcon from '@mui/icons-material/Delete'
|
14
|
-
import SplitIcon from '@mui/icons-material/CallSplit'
|
15
|
-
import RestoreIcon from '@mui/icons-material/RestoreFromTrash'
|
16
|
-
import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh'
|
17
12
|
import PlayCircleOutlineIcon from '@mui/icons-material/PlayCircleOutline'
|
18
|
-
import CancelIcon from '@mui/icons-material/Cancel'
|
19
13
|
import StopIcon from '@mui/icons-material/Stop'
|
20
|
-
import HistoryIcon from '@mui/icons-material/History'
|
21
14
|
import { LyricsSegment, Word } from '../types'
|
22
|
-
import { useState, useEffect, useCallback } from 'react'
|
23
|
-
import TimelineEditor from './TimelineEditor'
|
15
|
+
import { useState, useEffect, useCallback, useMemo, memo } from 'react'
|
24
16
|
import { nanoid } from 'nanoid'
|
25
|
-
import WordDivider from './WordDivider'
|
26
17
|
import useManualSync from '../hooks/useManualSync'
|
18
|
+
import EditTimelineSection from './EditTimelineSection'
|
19
|
+
import EditWordList from './EditWordList'
|
20
|
+
import EditActionBar from './EditActionBar'
|
21
|
+
|
22
|
+
// Extract TimelineSection into a separate memoized component
|
23
|
+
interface TimelineSectionProps {
|
24
|
+
words: Word[]
|
25
|
+
timeRange: { start: number, end: number }
|
26
|
+
originalSegment: LyricsSegment
|
27
|
+
editedSegment: LyricsSegment
|
28
|
+
currentTime: number
|
29
|
+
isManualSyncing: boolean
|
30
|
+
syncWordIndex: number
|
31
|
+
isSpacebarPressed: boolean
|
32
|
+
onWordUpdate: (index: number, updates: Partial<Word>) => void
|
33
|
+
onPlaySegment?: (startTime: number) => void
|
34
|
+
startManualSync: () => void
|
35
|
+
isGlobal: boolean
|
36
|
+
}
|
37
|
+
|
38
|
+
const MemoizedTimelineSection = memo(function TimelineSection({
|
39
|
+
words,
|
40
|
+
timeRange,
|
41
|
+
originalSegment,
|
42
|
+
editedSegment,
|
43
|
+
currentTime,
|
44
|
+
isManualSyncing,
|
45
|
+
syncWordIndex,
|
46
|
+
isSpacebarPressed,
|
47
|
+
onWordUpdate,
|
48
|
+
onPlaySegment,
|
49
|
+
startManualSync,
|
50
|
+
isGlobal
|
51
|
+
}: TimelineSectionProps) {
|
52
|
+
return (
|
53
|
+
<EditTimelineSection
|
54
|
+
words={words}
|
55
|
+
startTime={timeRange.start}
|
56
|
+
endTime={timeRange.end}
|
57
|
+
originalStartTime={originalSegment.start_time}
|
58
|
+
originalEndTime={originalSegment.end_time}
|
59
|
+
currentStartTime={editedSegment.start_time}
|
60
|
+
currentEndTime={editedSegment.end_time}
|
61
|
+
currentTime={currentTime}
|
62
|
+
isManualSyncing={isManualSyncing}
|
63
|
+
syncWordIndex={syncWordIndex}
|
64
|
+
isSpacebarPressed={isSpacebarPressed}
|
65
|
+
onWordUpdate={onWordUpdate}
|
66
|
+
onPlaySegment={onPlaySegment}
|
67
|
+
startManualSync={startManualSync}
|
68
|
+
isGlobal={isGlobal}
|
69
|
+
/>
|
70
|
+
)
|
71
|
+
})
|
72
|
+
|
73
|
+
// Extract WordList into a separate memoized component
|
74
|
+
interface WordListProps {
|
75
|
+
words: Word[]
|
76
|
+
onWordUpdate: (index: number, updates: Partial<Word>) => void
|
77
|
+
onSplitWord: (index: number) => void
|
78
|
+
onMergeWords: (index: number) => void
|
79
|
+
onAddWord: (index?: number) => void
|
80
|
+
onRemoveWord: (index: number) => void
|
81
|
+
onSplitSegment?: (wordIndex: number) => void
|
82
|
+
onAddSegment?: (beforeIndex: number) => void
|
83
|
+
onMergeSegment?: (mergeWithNext: boolean) => void
|
84
|
+
isGlobal: boolean
|
85
|
+
}
|
86
|
+
|
87
|
+
const MemoizedWordList = memo(function WordList({
|
88
|
+
words,
|
89
|
+
onWordUpdate,
|
90
|
+
onSplitWord,
|
91
|
+
onMergeWords,
|
92
|
+
onAddWord,
|
93
|
+
onRemoveWord,
|
94
|
+
onSplitSegment,
|
95
|
+
onAddSegment,
|
96
|
+
onMergeSegment,
|
97
|
+
isGlobal
|
98
|
+
}: WordListProps) {
|
99
|
+
return (
|
100
|
+
<EditWordList
|
101
|
+
words={words}
|
102
|
+
onWordUpdate={onWordUpdate}
|
103
|
+
onSplitWord={onSplitWord}
|
104
|
+
onMergeWords={onMergeWords}
|
105
|
+
onAddWord={onAddWord}
|
106
|
+
onRemoveWord={onRemoveWord}
|
107
|
+
onSplitSegment={onSplitSegment}
|
108
|
+
onAddSegment={onAddSegment}
|
109
|
+
onMergeSegment={onMergeSegment}
|
110
|
+
isGlobal={isGlobal}
|
111
|
+
/>
|
112
|
+
)
|
113
|
+
})
|
114
|
+
|
115
|
+
// Extract ActionBar into a separate memoized component
|
116
|
+
interface ActionBarProps {
|
117
|
+
onReset: () => void
|
118
|
+
onRevertToOriginal?: () => void
|
119
|
+
onDelete?: () => void
|
120
|
+
onClose: () => void
|
121
|
+
onSave: () => void
|
122
|
+
editedSegment: LyricsSegment | null
|
123
|
+
originalTranscribedSegment?: LyricsSegment | null
|
124
|
+
isGlobal: boolean
|
125
|
+
}
|
126
|
+
|
127
|
+
const MemoizedActionBar = memo(function ActionBar({
|
128
|
+
onReset,
|
129
|
+
onRevertToOriginal,
|
130
|
+
onDelete,
|
131
|
+
onClose,
|
132
|
+
onSave,
|
133
|
+
editedSegment,
|
134
|
+
originalTranscribedSegment,
|
135
|
+
isGlobal
|
136
|
+
}: ActionBarProps) {
|
137
|
+
return (
|
138
|
+
<EditActionBar
|
139
|
+
onReset={onReset}
|
140
|
+
onRevertToOriginal={onRevertToOriginal}
|
141
|
+
onDelete={onDelete}
|
142
|
+
onClose={onClose}
|
143
|
+
onSave={onSave}
|
144
|
+
editedSegment={editedSegment}
|
145
|
+
originalTranscribedSegment={originalTranscribedSegment}
|
146
|
+
isGlobal={isGlobal}
|
147
|
+
/>
|
148
|
+
)
|
149
|
+
})
|
27
150
|
|
28
151
|
interface EditModalProps {
|
29
152
|
open: boolean
|
@@ -40,6 +163,8 @@ interface EditModalProps {
|
|
40
163
|
onMergeSegment?: (segmentIndex: number, mergeWithNext: boolean) => void
|
41
164
|
setModalSpacebarHandler: (handler: (() => (e: KeyboardEvent) => void) | undefined) => void
|
42
165
|
originalTranscribedSegment?: LyricsSegment | null
|
166
|
+
isGlobal?: boolean
|
167
|
+
isLoading?: boolean
|
43
168
|
}
|
44
169
|
|
45
170
|
export default function EditModal({
|
@@ -56,10 +181,21 @@ export default function EditModal({
|
|
56
181
|
onSplitSegment,
|
57
182
|
onMergeSegment,
|
58
183
|
setModalSpacebarHandler,
|
59
|
-
originalTranscribedSegment
|
184
|
+
originalTranscribedSegment,
|
185
|
+
isGlobal = false,
|
186
|
+
isLoading = false
|
60
187
|
}: EditModalProps) {
|
188
|
+
console.log('EditModal - Render', {
|
189
|
+
open,
|
190
|
+
isGlobal,
|
191
|
+
isLoading,
|
192
|
+
hasSegment: !!segment,
|
193
|
+
segmentIndex,
|
194
|
+
hasOriginalSegment: !!originalSegment,
|
195
|
+
hasOriginalTranscribedSegment: !!originalTranscribedSegment
|
196
|
+
});
|
197
|
+
|
61
198
|
const [editedSegment, setEditedSegment] = useState<LyricsSegment | null>(segment)
|
62
|
-
const [replacementText, setReplacementText] = useState('')
|
63
199
|
const [isPlaying, setIsPlaying] = useState(false)
|
64
200
|
|
65
201
|
// Define updateSegment first since the hook depends on it
|
@@ -81,7 +217,7 @@ export default function EditModal({
|
|
81
217
|
})
|
82
218
|
}, [editedSegment])
|
83
219
|
|
84
|
-
// Use the
|
220
|
+
// Use the manual sync hook
|
85
221
|
const {
|
86
222
|
isManualSyncing,
|
87
223
|
syncWordIndex,
|
@@ -97,6 +233,7 @@ export default function EditModal({
|
|
97
233
|
})
|
98
234
|
|
99
235
|
const handleClose = useCallback(() => {
|
236
|
+
console.log('EditModal - handleClose called');
|
100
237
|
cleanupManualSync()
|
101
238
|
onClose()
|
102
239
|
}, [onClose, cleanupManualSync])
|
@@ -109,16 +246,17 @@ export default function EditModal({
|
|
109
246
|
console.log('EditModal - Setting up modal spacebar handler', {
|
110
247
|
hasPlaySegment: !!onPlaySegment,
|
111
248
|
editedSegmentId: editedSegment?.id,
|
112
|
-
handlerFunction: spacebarHandler.toString().slice(0, 100)
|
249
|
+
handlerFunction: spacebarHandler.toString().slice(0, 100),
|
250
|
+
isLoading
|
113
251
|
})
|
114
|
-
|
252
|
+
|
115
253
|
// Create a function that will be called by the global event listeners
|
116
254
|
const handleKeyEvent = (e: KeyboardEvent) => {
|
117
255
|
if (e.code === 'Space') {
|
118
256
|
spacebarHandler(e)
|
119
257
|
}
|
120
258
|
}
|
121
|
-
|
259
|
+
|
122
260
|
setModalSpacebarHandler(() => handleKeyEvent)
|
123
261
|
|
124
262
|
// Only cleanup when the effect is re-run or the modal is closed
|
@@ -134,7 +272,8 @@ export default function EditModal({
|
|
134
272
|
handleSpacebar,
|
135
273
|
setModalSpacebarHandler,
|
136
274
|
editedSegment?.id,
|
137
|
-
onPlaySegment
|
275
|
+
onPlaySegment,
|
276
|
+
isLoading
|
138
277
|
])
|
139
278
|
|
140
279
|
// Update isPlaying when currentTime changes
|
@@ -150,6 +289,11 @@ export default function EditModal({
|
|
150
289
|
|
151
290
|
// All useEffect hooks
|
152
291
|
useEffect(() => {
|
292
|
+
console.log('EditModal - segment changed', {
|
293
|
+
hasSegment: !!segment,
|
294
|
+
segmentId: segment?.id,
|
295
|
+
wordCount: segment?.words.length
|
296
|
+
});
|
153
297
|
setEditedSegment(segment)
|
154
298
|
}, [segment])
|
155
299
|
|
@@ -168,29 +312,26 @@ export default function EditModal({
|
|
168
312
|
}, [isManualSyncing, editedSegment, currentTime, cleanupManualSync])
|
169
313
|
|
170
314
|
// Add a function to get safe time values
|
171
|
-
const getSafeTimeRange = (segment: LyricsSegment | null) => {
|
315
|
+
const getSafeTimeRange = useCallback((segment: LyricsSegment | null) => {
|
172
316
|
if (!segment) return { start: 0, end: 1 }; // Default 1-second range
|
173
317
|
const start = segment.start_time ?? 0;
|
174
318
|
const end = segment.end_time ?? (start + 1);
|
175
319
|
return { start, end };
|
176
|
-
}
|
320
|
+
}, [])
|
177
321
|
|
178
|
-
//
|
179
|
-
|
180
|
-
|
181
|
-
// Get safe time values for TimelineEditor
|
182
|
-
const timeRange = getSafeTimeRange(editedSegment)
|
183
|
-
|
184
|
-
const handleWordChange = (index: number, updates: Partial<Word>) => {
|
322
|
+
// Define all handler functions with useCallback before the early return
|
323
|
+
const handleWordChange = useCallback((index: number, updates: Partial<Word>) => {
|
324
|
+
if (!editedSegment) return;
|
185
325
|
const newWords = [...editedSegment.words]
|
186
326
|
newWords[index] = {
|
187
327
|
...newWords[index],
|
188
328
|
...updates
|
189
329
|
}
|
190
330
|
updateSegment(newWords)
|
191
|
-
}
|
331
|
+
}, [editedSegment, updateSegment])
|
192
332
|
|
193
|
-
const handleAddWord = (index?: number) => {
|
333
|
+
const handleAddWord = useCallback((index?: number) => {
|
334
|
+
if (!editedSegment) return;
|
194
335
|
const newWords = [...editedSegment.words]
|
195
336
|
let newWord: Word
|
196
337
|
|
@@ -228,9 +369,10 @@ export default function EditModal({
|
|
228
369
|
}
|
229
370
|
|
230
371
|
updateSegment(newWords)
|
231
|
-
}
|
372
|
+
}, [editedSegment, updateSegment])
|
232
373
|
|
233
|
-
const handleSplitWord = (index: number) => {
|
374
|
+
const handleSplitWord = useCallback((index: number) => {
|
375
|
+
if (!editedSegment) return;
|
234
376
|
const word = editedSegment.words[index]
|
235
377
|
const startTime = word.start_time ?? 0
|
236
378
|
const endTime = word.end_time ?? startTime + 0.5
|
@@ -264,9 +406,10 @@ export default function EditModal({
|
|
264
406
|
allWords.splice(index, 1, ...newWords)
|
265
407
|
|
266
408
|
updateSegment(allWords)
|
267
|
-
}
|
409
|
+
}, [editedSegment, updateSegment])
|
268
410
|
|
269
|
-
const handleMergeWords = (index: number) => {
|
411
|
+
const handleMergeWords = useCallback((index: number) => {
|
412
|
+
if (!editedSegment) return;
|
270
413
|
if (index >= editedSegment.words.length - 1) return
|
271
414
|
|
272
415
|
const word1 = editedSegment.words[index]
|
@@ -282,103 +425,79 @@ export default function EditModal({
|
|
282
425
|
})
|
283
426
|
|
284
427
|
updateSegment(newWords)
|
285
|
-
}
|
428
|
+
}, [editedSegment, updateSegment])
|
286
429
|
|
287
|
-
const handleRemoveWord = (index: number) => {
|
430
|
+
const handleRemoveWord = useCallback((index: number) => {
|
431
|
+
if (!editedSegment) return;
|
288
432
|
const newWords = editedSegment.words.filter((_, i) => i !== index)
|
289
433
|
updateSegment(newWords)
|
290
|
-
}
|
291
|
-
|
292
|
-
const handleReset = () => {
|
293
|
-
setEditedSegment(JSON.parse(JSON.stringify(originalSegment)))
|
294
|
-
}
|
434
|
+
}, [editedSegment, updateSegment])
|
295
435
|
|
296
|
-
const
|
297
|
-
if (
|
298
|
-
setEditedSegment(JSON.parse(JSON.stringify(originalTranscribedSegment)))
|
299
|
-
}
|
300
|
-
}
|
436
|
+
const handleReset = useCallback(() => {
|
437
|
+
if (!originalSegment) return
|
301
438
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
editedText: editedSegment.text,
|
308
|
-
wordCount: editedSegment.words.length,
|
309
|
-
timeRange: `${editedSegment.start_time?.toFixed(4) ?? 'N/A'} - ${editedSegment.end_time?.toFixed(4) ?? 'N/A'}`
|
310
|
-
})
|
311
|
-
onSave(editedSegment)
|
312
|
-
onClose()
|
313
|
-
}
|
314
|
-
}
|
315
|
-
|
316
|
-
const handleReplaceAllWords = () => {
|
317
|
-
if (!editedSegment) return
|
439
|
+
console.log('EditModal - Resetting to original:', {
|
440
|
+
isGlobal,
|
441
|
+
originalSegmentId: originalSegment.id,
|
442
|
+
originalWordCount: originalSegment.words.length
|
443
|
+
})
|
318
444
|
|
319
|
-
|
320
|
-
|
321
|
-
const endTime = editedSegment.end_time ?? (startTime + newWords.length) // Default to 1 second per word
|
322
|
-
const segmentDuration = endTime - startTime
|
445
|
+
setEditedSegment(JSON.parse(JSON.stringify(originalSegment)))
|
446
|
+
}, [originalSegment, isGlobal])
|
323
447
|
|
324
|
-
|
448
|
+
const handleRevertToOriginal = useCallback(() => {
|
449
|
+
if (!originalTranscribedSegment) return
|
325
450
|
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
start_time: word.start_time,
|
332
|
-
end_time: word.end_time,
|
333
|
-
confidence: 1.0
|
334
|
-
}))
|
335
|
-
} else {
|
336
|
-
// If word count differs, distribute time evenly and generate new IDs
|
337
|
-
const avgWordDuration = segmentDuration / newWords.length
|
338
|
-
updatedWords = newWords.map((text, index) => ({
|
339
|
-
id: nanoid(), // Generate new ID
|
340
|
-
text,
|
341
|
-
start_time: startTime + (index * avgWordDuration),
|
342
|
-
end_time: startTime + ((index + 1) * avgWordDuration),
|
343
|
-
confidence: 1.0
|
344
|
-
}))
|
345
|
-
}
|
346
|
-
|
347
|
-
updateSegment(updatedWords)
|
348
|
-
setReplacementText('') // Clear the input after replacing
|
349
|
-
}
|
451
|
+
console.log('EditModal - Reverting to original transcribed:', {
|
452
|
+
isGlobal,
|
453
|
+
originalTranscribedSegmentId: originalTranscribedSegment.id,
|
454
|
+
originalTranscribedWordCount: originalTranscribedSegment.words.length
|
455
|
+
})
|
350
456
|
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
457
|
+
setEditedSegment(JSON.parse(JSON.stringify(originalTranscribedSegment)))
|
458
|
+
}, [originalTranscribedSegment, isGlobal])
|
459
|
+
|
460
|
+
const handleSave = useCallback(() => {
|
461
|
+
if (!editedSegment || !segment) return;
|
462
|
+
|
463
|
+
console.log('EditModal - Saving segment:', {
|
464
|
+
isGlobal,
|
465
|
+
segmentIndex,
|
466
|
+
originalText: segment?.text,
|
467
|
+
editedText: editedSegment.text,
|
468
|
+
wordCount: editedSegment.words.length,
|
469
|
+
firstWord: editedSegment.words[0],
|
470
|
+
lastWord: editedSegment.words[editedSegment.words.length - 1],
|
471
|
+
timeRange: `${editedSegment.start_time?.toFixed(4) ?? 'N/A'} - ${editedSegment.end_time?.toFixed(4) ?? 'N/A'}`
|
472
|
+
})
|
473
|
+
onSave(editedSegment)
|
474
|
+
onClose()
|
475
|
+
}, [editedSegment, isGlobal, segmentIndex, segment, onSave, onClose])
|
357
476
|
|
358
|
-
const handleDelete = () => {
|
477
|
+
const handleDelete = useCallback(() => {
|
359
478
|
if (segmentIndex !== null) {
|
360
479
|
onDelete?.(segmentIndex)
|
361
480
|
onClose()
|
362
481
|
}
|
363
|
-
}
|
482
|
+
}, [segmentIndex, onDelete, onClose])
|
364
483
|
|
365
|
-
const handleSplitSegment = (wordIndex: number) => {
|
484
|
+
const handleSplitSegment = useCallback((wordIndex: number) => {
|
366
485
|
if (segmentIndex !== null && editedSegment) {
|
367
486
|
handleSave() // Save current changes first
|
368
487
|
onSplitSegment?.(segmentIndex, wordIndex)
|
369
488
|
}
|
370
|
-
}
|
489
|
+
}, [segmentIndex, editedSegment, handleSave, onSplitSegment])
|
371
490
|
|
372
|
-
const handleMergeSegment = (mergeWithNext: boolean) => {
|
491
|
+
const handleMergeSegment = useCallback((mergeWithNext: boolean) => {
|
373
492
|
if (segmentIndex !== null && editedSegment) {
|
374
493
|
handleSave() // Save current changes first
|
375
494
|
onMergeSegment?.(segmentIndex, mergeWithNext)
|
376
495
|
onClose()
|
377
496
|
}
|
378
|
-
}
|
497
|
+
}, [segmentIndex, editedSegment, handleSave, onMergeSegment, onClose])
|
379
498
|
|
380
499
|
// Handle play/stop button click
|
381
|
-
const handlePlayButtonClick = () => {
|
500
|
+
const handlePlayButtonClick = useCallback(() => {
|
382
501
|
if (!segment?.start_time || !onPlaySegment) return
|
383
502
|
|
384
503
|
if (isPlaying) {
|
@@ -390,25 +509,37 @@ export default function EditModal({
|
|
390
509
|
// Start playback
|
391
510
|
onPlaySegment(segment.start_time)
|
392
511
|
}
|
393
|
-
}
|
394
|
-
|
395
|
-
return
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
512
|
+
}, [segment?.start_time, onPlaySegment, isPlaying])
|
513
|
+
|
514
|
+
// Calculate timeRange before the early return
|
515
|
+
const timeRange = useMemo(() => {
|
516
|
+
if (!editedSegment) return { start: 0, end: 1 };
|
517
|
+
return getSafeTimeRange(editedSegment);
|
518
|
+
}, [getSafeTimeRange, editedSegment]);
|
519
|
+
|
520
|
+
// Memoize the dialog title to prevent re-renders
|
521
|
+
const dialogTitle = useMemo(() => {
|
522
|
+
console.log('EditModal - Rendering dialog title', { isLoading, isGlobal });
|
523
|
+
|
524
|
+
if (isLoading) {
|
525
|
+
return (
|
526
|
+
<DialogTitle sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
527
|
+
<Box sx={{ flex: 1, display: 'flex', alignItems: 'center', gap: 1 }}>
|
528
|
+
Loading {isGlobal ? 'All Words' : `Segment ${segmentIndex}`}...
|
529
|
+
</Box>
|
530
|
+
<IconButton onClick={onClose} sx={{ ml: 'auto' }}>
|
531
|
+
<CloseIcon />
|
532
|
+
</IconButton>
|
533
|
+
</DialogTitle>
|
534
|
+
);
|
535
|
+
}
|
536
|
+
|
537
|
+
if (!segment) return null;
|
538
|
+
|
539
|
+
return (
|
409
540
|
<DialogTitle sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
410
541
|
<Box sx={{ flex: 1, display: 'flex', alignItems: 'center', gap: 1 }}>
|
411
|
-
Edit Segment {segmentIndex}
|
542
|
+
Edit {isGlobal ? 'All Words' : `Segment ${segmentIndex}`}
|
412
543
|
{segment?.start_time !== null && onPlaySegment && (
|
413
544
|
<IconButton
|
414
545
|
size="small"
|
@@ -427,202 +558,144 @@ export default function EditModal({
|
|
427
558
|
<CloseIcon />
|
428
559
|
</IconButton>
|
429
560
|
</DialogTitle>
|
561
|
+
);
|
562
|
+
}, [isGlobal, segmentIndex, segment, onPlaySegment, handlePlayButtonClick, isPlaying, onClose, isLoading])
|
563
|
+
|
564
|
+
// Early return after all hooks and function definitions
|
565
|
+
if (!isLoading && (!segment || !editedSegment || !originalSegment)) {
|
566
|
+
console.log('EditModal - Early return: missing required data', {
|
567
|
+
hasSegment: !!segment,
|
568
|
+
hasEditedSegment: !!editedSegment,
|
569
|
+
hasOriginalSegment: !!originalSegment,
|
570
|
+
isLoading
|
571
|
+
});
|
572
|
+
return null;
|
573
|
+
}
|
574
|
+
if (!isLoading && !isGlobal && segmentIndex === null) {
|
575
|
+
console.log('EditModal - Early return: non-global mode with null segmentIndex');
|
576
|
+
return null;
|
577
|
+
}
|
578
|
+
|
579
|
+
console.log('EditModal - Rendering dialog content', {
|
580
|
+
isLoading,
|
581
|
+
hasEditedSegment: !!editedSegment,
|
582
|
+
hasOriginalSegment: !!originalSegment
|
583
|
+
});
|
584
|
+
|
585
|
+
return (
|
586
|
+
<Dialog
|
587
|
+
open={open}
|
588
|
+
onClose={handleClose}
|
589
|
+
maxWidth="md"
|
590
|
+
fullWidth
|
591
|
+
onKeyDown={(e) => {
|
592
|
+
if (e.key === 'Enter' && !e.shiftKey && !isLoading) {
|
593
|
+
e.preventDefault()
|
594
|
+
handleSave()
|
595
|
+
}
|
596
|
+
}}
|
597
|
+
PaperProps={{
|
598
|
+
sx: {
|
599
|
+
height: '90vh',
|
600
|
+
margin: '5vh 0'
|
601
|
+
}
|
602
|
+
}}
|
603
|
+
>
|
604
|
+
{dialogTitle}
|
605
|
+
|
430
606
|
<DialogContent
|
431
607
|
dividers
|
432
608
|
sx={{
|
433
609
|
display: 'flex',
|
434
610
|
flexDirection: 'column',
|
435
|
-
flexGrow: 1,
|
436
|
-
overflow: 'hidden'
|
611
|
+
flexGrow: 1,
|
612
|
+
overflow: 'hidden',
|
613
|
+
position: 'relative'
|
437
614
|
}}
|
438
615
|
>
|
439
|
-
|
440
|
-
<
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
<
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
onClick={startManualSync}
|
461
|
-
disabled={!onPlaySegment}
|
462
|
-
startIcon={isManualSyncing ? <CancelIcon /> : <PlayCircleOutlineIcon />}
|
463
|
-
color={isManualSyncing ? "error" : "primary"}
|
464
|
-
>
|
465
|
-
{isManualSyncing ? "Cancel Sync" : "Manual Sync"}
|
466
|
-
</Button>
|
467
|
-
{isManualSyncing && (
|
468
|
-
<Box>
|
469
|
-
<Typography variant="body2">
|
470
|
-
Word {syncWordIndex + 1} of {editedSegment?.words.length}: <strong>{editedSegment?.words[syncWordIndex]?.text || ''}</strong>
|
471
|
-
</Typography>
|
472
|
-
<Typography variant="caption" color="text.secondary">
|
473
|
-
{isSpacebarPressed ?
|
474
|
-
"Holding spacebar... Release when word ends" :
|
475
|
-
"Press spacebar when word starts (tap for short words, hold for long words)"}
|
476
|
-
</Typography>
|
477
|
-
</Box>
|
478
|
-
)}
|
616
|
+
{isLoading && (
|
617
|
+
<Box sx={{
|
618
|
+
display: 'flex',
|
619
|
+
flexDirection: 'column',
|
620
|
+
alignItems: 'center',
|
621
|
+
justifyContent: 'center',
|
622
|
+
height: '100%',
|
623
|
+
width: '100%',
|
624
|
+
position: 'absolute',
|
625
|
+
top: 0,
|
626
|
+
left: 0,
|
627
|
+
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
628
|
+
zIndex: 10
|
629
|
+
}}>
|
630
|
+
<CircularProgress size={60} thickness={4} />
|
631
|
+
<Typography variant="h6" sx={{ mt: 2, fontWeight: 'bold' }}>
|
632
|
+
Loading {isGlobal ? 'all words' : 'segment'}...
|
633
|
+
</Typography>
|
634
|
+
<Typography variant="body2" sx={{ mt: 1, maxWidth: '80%', textAlign: 'center' }}>
|
635
|
+
{isGlobal ? 'This may take a few seconds for songs with many words.' : 'Please wait...'}
|
636
|
+
</Typography>
|
479
637
|
</Box>
|
480
|
-
|
481
|
-
|
482
|
-
<Box sx={{
|
483
|
-
display: 'flex',
|
484
|
-
flexDirection: 'column',
|
485
|
-
gap: 0.5,
|
486
|
-
mb: 3,
|
487
|
-
pt: 1,
|
488
|
-
flexGrow: 1,
|
489
|
-
overflowY: 'auto',
|
490
|
-
'&::-webkit-scrollbar': {
|
491
|
-
display: 'none' // Hide scrollbar for WebKit browsers (Chrome, Safari, etc.)
|
492
|
-
},
|
493
|
-
msOverflowStyle: 'none', // Hide scrollbar for IE and Edge
|
494
|
-
scrollbarWidth: 'none', // Hide scrollbar for Firefox
|
495
|
-
}}>
|
496
|
-
{/* Initial divider with Add Segment Before button */}
|
497
|
-
<WordDivider
|
498
|
-
onAddWord={() => handleAddWord(-1)}
|
499
|
-
onAddSegmentBefore={() => onAddSegment?.(segmentIndex)}
|
500
|
-
onMergeSegment={() => handleMergeSegment(false)}
|
501
|
-
isFirst={true}
|
502
|
-
sx={{ ml: 15 }}
|
503
|
-
/>
|
638
|
+
)}
|
504
639
|
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
sx={{ color: 'primary.main' }}
|
537
|
-
size="small"
|
538
|
-
>
|
539
|
-
<SplitIcon fontSize="small" />
|
540
|
-
</IconButton>
|
541
|
-
<IconButton
|
542
|
-
onClick={() => handleRemoveWord(index)}
|
543
|
-
disabled={editedSegment.words.length <= 1}
|
544
|
-
title="Remove Word"
|
545
|
-
sx={{ color: 'error.main' }}
|
546
|
-
size="small"
|
547
|
-
>
|
548
|
-
<DeleteIcon fontSize="small" />
|
549
|
-
</IconButton>
|
550
|
-
</Box>
|
551
|
-
|
552
|
-
{/* Update the WordDivider usage to include split segment */}
|
553
|
-
<WordDivider
|
554
|
-
onAddWord={() => handleAddWord(index)}
|
555
|
-
onMergeWords={() => handleMergeWords(index)}
|
556
|
-
onSplitSegment={() => handleSplitSegment(index)}
|
557
|
-
onAddSegmentAfter={
|
558
|
-
index === editedSegment.words.length - 1
|
559
|
-
? () => onAddSegment?.(segmentIndex + 1)
|
560
|
-
: undefined
|
561
|
-
}
|
562
|
-
onMergeSegment={
|
563
|
-
index === editedSegment.words.length - 1
|
564
|
-
? () => handleMergeSegment(true)
|
565
|
-
: undefined
|
566
|
-
}
|
567
|
-
canMerge={index < editedSegment.words.length - 1}
|
568
|
-
isLast={index === editedSegment.words.length - 1}
|
569
|
-
sx={{ ml: 15 }}
|
570
|
-
/>
|
571
|
-
</Box>
|
572
|
-
))}
|
573
|
-
</Box>
|
640
|
+
{!isLoading && editedSegment && originalSegment && (
|
641
|
+
<>
|
642
|
+
<MemoizedTimelineSection
|
643
|
+
words={editedSegment.words}
|
644
|
+
timeRange={timeRange}
|
645
|
+
originalSegment={originalSegment}
|
646
|
+
editedSegment={editedSegment}
|
647
|
+
currentTime={currentTime}
|
648
|
+
isManualSyncing={isManualSyncing}
|
649
|
+
syncWordIndex={syncWordIndex}
|
650
|
+
isSpacebarPressed={isSpacebarPressed}
|
651
|
+
onWordUpdate={handleWordChange}
|
652
|
+
onPlaySegment={onPlaySegment}
|
653
|
+
startManualSync={startManualSync}
|
654
|
+
isGlobal={isGlobal}
|
655
|
+
/>
|
656
|
+
|
657
|
+
<MemoizedWordList
|
658
|
+
words={editedSegment.words}
|
659
|
+
onWordUpdate={handleWordChange}
|
660
|
+
onSplitWord={handleSplitWord}
|
661
|
+
onMergeWords={handleMergeWords}
|
662
|
+
onAddWord={handleAddWord}
|
663
|
+
onRemoveWord={handleRemoveWord}
|
664
|
+
onSplitSegment={handleSplitSegment}
|
665
|
+
onAddSegment={onAddSegment}
|
666
|
+
onMergeSegment={handleMergeSegment}
|
667
|
+
isGlobal={isGlobal}
|
668
|
+
/>
|
669
|
+
</>
|
670
|
+
)}
|
574
671
|
|
575
|
-
|
576
|
-
<
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
sx={{ whiteSpace: 'nowrap' }}
|
588
|
-
>
|
589
|
-
Replace All
|
590
|
-
</Button>
|
591
|
-
</Box>
|
672
|
+
{!isLoading && (!editedSegment || !originalSegment) && (
|
673
|
+
<Box sx={{
|
674
|
+
display: 'flex',
|
675
|
+
alignItems: 'center',
|
676
|
+
justifyContent: 'center',
|
677
|
+
height: '100%'
|
678
|
+
}}>
|
679
|
+
<Typography variant="h6">
|
680
|
+
No segment data available
|
681
|
+
</Typography>
|
682
|
+
</Box>
|
683
|
+
)}
|
592
684
|
</DialogContent>
|
685
|
+
|
593
686
|
<DialogActions>
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
>
|
606
|
-
Un-Correct
|
607
|
-
</Button>
|
687
|
+
{!isLoading && editedSegment && (
|
688
|
+
<MemoizedActionBar
|
689
|
+
onReset={handleReset}
|
690
|
+
onRevertToOriginal={handleRevertToOriginal}
|
691
|
+
onDelete={handleDelete}
|
692
|
+
onClose={handleClose}
|
693
|
+
onSave={handleSave}
|
694
|
+
editedSegment={editedSegment}
|
695
|
+
originalTranscribedSegment={originalTranscribedSegment}
|
696
|
+
isGlobal={isGlobal}
|
697
|
+
/>
|
608
698
|
)}
|
609
|
-
<Box sx={{ mr: 'auto' }}>
|
610
|
-
<Button
|
611
|
-
startIcon={<DeleteIcon />}
|
612
|
-
onClick={handleDelete}
|
613
|
-
color="error"
|
614
|
-
>
|
615
|
-
Delete Segment
|
616
|
-
</Button>
|
617
|
-
</Box>
|
618
|
-
<Button onClick={handleClose}>Cancel</Button>
|
619
|
-
<Button
|
620
|
-
onClick={handleSave}
|
621
|
-
variant="contained"
|
622
|
-
disabled={!editedSegment || editedSegment.words.length === 0}
|
623
|
-
>
|
624
|
-
Save
|
625
|
-
</Button>
|
626
699
|
</DialogActions>
|
627
700
|
</Dialog>
|
628
701
|
)
|