lyrics-transcriber 0.45.0__py3-none-any.whl → 0.47.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-ZCT0s9MG.js → index-2vK-qVJS.js} +231 -151
- lyrics_transcriber/frontend/dist/assets/index-2vK-qVJS.js.map +1 -0
- lyrics_transcriber/frontend/dist/index.html +1 -1
- lyrics_transcriber/frontend/package.json +1 -1
- lyrics_transcriber/frontend/src/components/EditModal.tsx +36 -36
- lyrics_transcriber/frontend/src/components/EditWordList.tsx +73 -2
- lyrics_transcriber/frontend/src/components/Header.tsx +1 -16
- lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +26 -8
- lyrics_transcriber/frontend/src/components/ModeSelector.tsx +35 -16
- lyrics_transcriber/frontend/src/components/ReferenceView.tsx +3 -1
- 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 +2 -1
- lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +3 -2
- lyrics_transcriber/frontend/src/components/shared/types.ts +2 -0
- lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +45 -8
- lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +47 -0
- lyrics_transcriber/frontend/src/types.ts +1 -1
- {lyrics_transcriber-0.45.0.dist-info → lyrics_transcriber-0.47.0.dist-info}/METADATA +1 -1
- {lyrics_transcriber-0.45.0.dist-info → lyrics_transcriber-0.47.0.dist-info}/RECORD +25 -25
- lyrics_transcriber/frontend/dist/assets/index-ZCT0s9MG.js.map +0 -1
- {lyrics_transcriber-0.45.0.dist-info → lyrics_transcriber-0.47.0.dist-info}/LICENSE +0 -0
- {lyrics_transcriber-0.45.0.dist-info → lyrics_transcriber-0.47.0.dist-info}/WHEEL +0 -0
- {lyrics_transcriber-0.45.0.dist-info → lyrics_transcriber-0.47.0.dist-info}/entry_points.txt +0 -0
@@ -5,7 +5,7 @@
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
7
7
|
<title>Lyrics Transcriber Analyzer</title>
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
8
|
+
<script type="module" crossorigin src="/assets/index-2vK-qVJS.js"></script>
|
9
9
|
</head>
|
10
10
|
<body>
|
11
11
|
<div id="root"></div>
|
@@ -185,15 +185,15 @@ export default function EditModal({
|
|
185
185
|
isGlobal = false,
|
186
186
|
isLoading = false
|
187
187
|
}: EditModalProps) {
|
188
|
-
console.log('EditModal - Render', {
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
});
|
188
|
+
// console.log('EditModal - Render', {
|
189
|
+
// open,
|
190
|
+
// isGlobal,
|
191
|
+
// isLoading,
|
192
|
+
// hasSegment: !!segment,
|
193
|
+
// segmentIndex,
|
194
|
+
// hasOriginalSegment: !!originalSegment,
|
195
|
+
// hasOriginalTranscribedSegment: !!originalTranscribedSegment
|
196
|
+
// });
|
197
197
|
|
198
198
|
const [editedSegment, setEditedSegment] = useState<LyricsSegment | null>(segment)
|
199
199
|
const [isPlaying, setIsPlaying] = useState(false)
|
@@ -233,7 +233,7 @@ export default function EditModal({
|
|
233
233
|
})
|
234
234
|
|
235
235
|
const handleClose = useCallback(() => {
|
236
|
-
console.log('EditModal - handleClose called');
|
236
|
+
// console.log('EditModal - handleClose called');
|
237
237
|
cleanupManualSync()
|
238
238
|
onClose()
|
239
239
|
}, [onClose, cleanupManualSync])
|
@@ -243,12 +243,12 @@ export default function EditModal({
|
|
243
243
|
const spacebarHandler = handleSpacebar // Capture the current handler
|
244
244
|
|
245
245
|
if (open) {
|
246
|
-
console.log('EditModal - Setting up modal spacebar handler', {
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
})
|
246
|
+
// console.log('EditModal - Setting up modal spacebar handler', {
|
247
|
+
// hasPlaySegment: !!onPlaySegment,
|
248
|
+
// editedSegmentId: editedSegment?.id,
|
249
|
+
// handlerFunction: spacebarHandler.toString().slice(0, 100),
|
250
|
+
// isLoading
|
251
|
+
// })
|
252
252
|
|
253
253
|
// Create a function that will be called by the global event listeners
|
254
254
|
const handleKeyEvent = (e: KeyboardEvent) => {
|
@@ -262,7 +262,7 @@ export default function EditModal({
|
|
262
262
|
// Only cleanup when the effect is re-run or the modal is closed
|
263
263
|
return () => {
|
264
264
|
if (!open) {
|
265
|
-
console.log('EditModal - Cleanup: clearing modal spacebar handler')
|
265
|
+
// console.log('EditModal - Cleanup: clearing modal spacebar handler')
|
266
266
|
setModalSpacebarHandler(undefined)
|
267
267
|
}
|
268
268
|
}
|
@@ -289,11 +289,11 @@ export default function EditModal({
|
|
289
289
|
|
290
290
|
// All useEffect hooks
|
291
291
|
useEffect(() => {
|
292
|
-
console.log('EditModal - segment changed', {
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
});
|
292
|
+
// console.log('EditModal - segment changed', {
|
293
|
+
// hasSegment: !!segment,
|
294
|
+
// segmentId: segment?.id,
|
295
|
+
// wordCount: segment?.words.length
|
296
|
+
// });
|
297
297
|
setEditedSegment(segment)
|
298
298
|
}, [segment])
|
299
299
|
|
@@ -304,7 +304,7 @@ export default function EditModal({
|
|
304
304
|
const endTime = editedSegment.end_time ?? 0
|
305
305
|
|
306
306
|
if (window.isAudioPlaying && currentTime > endTime) {
|
307
|
-
console.log('Stopping playback: current time exceeded end time')
|
307
|
+
// console.log('Stopping playback: current time exceeded end time')
|
308
308
|
window.toggleAudioPlayback?.()
|
309
309
|
cleanupManualSync()
|
310
310
|
}
|
@@ -519,7 +519,7 @@ export default function EditModal({
|
|
519
519
|
|
520
520
|
// Memoize the dialog title to prevent re-renders
|
521
521
|
const dialogTitle = useMemo(() => {
|
522
|
-
console.log('EditModal - Rendering dialog title', { isLoading, isGlobal });
|
522
|
+
// console.log('EditModal - Rendering dialog title', { isLoading, isGlobal });
|
523
523
|
|
524
524
|
if (isLoading) {
|
525
525
|
return (
|
@@ -563,24 +563,24 @@ export default function EditModal({
|
|
563
563
|
|
564
564
|
// Early return after all hooks and function definitions
|
565
565
|
if (!isLoading && (!segment || !editedSegment || !originalSegment)) {
|
566
|
-
console.log('EditModal - Early return: missing required data', {
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
});
|
566
|
+
// console.log('EditModal - Early return: missing required data', {
|
567
|
+
// hasSegment: !!segment,
|
568
|
+
// hasEditedSegment: !!editedSegment,
|
569
|
+
// hasOriginalSegment: !!originalSegment,
|
570
|
+
// isLoading
|
571
|
+
// });
|
572
572
|
return null;
|
573
573
|
}
|
574
574
|
if (!isLoading && !isGlobal && segmentIndex === null) {
|
575
|
-
console.log('EditModal - Early return: non-global mode with null segmentIndex');
|
575
|
+
// console.log('EditModal - Early return: non-global mode with null segmentIndex');
|
576
576
|
return null;
|
577
577
|
}
|
578
578
|
|
579
|
-
console.log('EditModal - Rendering dialog content', {
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
});
|
579
|
+
// console.log('EditModal - Rendering dialog content', {
|
580
|
+
// isLoading,
|
581
|
+
// hasEditedSegment: !!editedSegment,
|
582
|
+
// hasOriginalSegment: !!originalSegment
|
583
|
+
// });
|
584
584
|
|
585
585
|
return (
|
586
586
|
<Dialog
|
@@ -33,7 +33,8 @@ const WordRow = memo(function WordRow({
|
|
33
33
|
onWordUpdate,
|
34
34
|
onSplitWord,
|
35
35
|
onRemoveWord,
|
36
|
-
wordsLength
|
36
|
+
wordsLength,
|
37
|
+
onTabNavigation
|
37
38
|
}: {
|
38
39
|
word: Word
|
39
40
|
index: number
|
@@ -41,7 +42,17 @@ const WordRow = memo(function WordRow({
|
|
41
42
|
onSplitWord: (index: number) => void
|
42
43
|
onRemoveWord: (index: number) => void
|
43
44
|
wordsLength: number
|
45
|
+
onTabNavigation: (currentIndex: number) => void
|
44
46
|
}) {
|
47
|
+
const handleKeyDown = (e: React.KeyboardEvent) => {
|
48
|
+
// console.log('KeyDown event:', e.key, 'Shift:', e.shiftKey, 'Index:', index);
|
49
|
+
if (e.key === 'Tab' && !e.shiftKey) {
|
50
|
+
// console.log('Tab key detected, preventing default and navigating');
|
51
|
+
e.preventDefault();
|
52
|
+
onTabNavigation(index);
|
53
|
+
}
|
54
|
+
};
|
55
|
+
|
45
56
|
return (
|
46
57
|
<Box sx={{
|
47
58
|
display: 'flex',
|
@@ -53,8 +64,10 @@ const WordRow = memo(function WordRow({
|
|
53
64
|
label={`Word ${index}`}
|
54
65
|
value={word.text}
|
55
66
|
onChange={(e) => onWordUpdate(index, { text: e.target.value })}
|
67
|
+
onKeyDown={handleKeyDown}
|
56
68
|
fullWidth
|
57
69
|
size="small"
|
70
|
+
id={`word-text-${index}`}
|
58
71
|
/>
|
59
72
|
<TextField
|
60
73
|
label="Start Time"
|
@@ -108,7 +121,8 @@ const WordItem = memo(function WordItem({
|
|
108
121
|
onAddSegment,
|
109
122
|
onMergeSegment,
|
110
123
|
wordsLength,
|
111
|
-
isGlobal
|
124
|
+
isGlobal,
|
125
|
+
onTabNavigation
|
112
126
|
}: {
|
113
127
|
word: Word
|
114
128
|
index: number
|
@@ -122,6 +136,7 @@ const WordItem = memo(function WordItem({
|
|
122
136
|
onMergeSegment?: (mergeWithNext: boolean) => void
|
123
137
|
wordsLength: number
|
124
138
|
isGlobal: boolean
|
139
|
+
onTabNavigation: (currentIndex: number) => void
|
125
140
|
}) {
|
126
141
|
return (
|
127
142
|
<Box key={word.id}>
|
@@ -132,6 +147,7 @@ const WordItem = memo(function WordItem({
|
|
132
147
|
onSplitWord={onSplitWord}
|
133
148
|
onRemoveWord={onRemoveWord}
|
134
149
|
wordsLength={wordsLength}
|
150
|
+
onTabNavigation={onTabNavigation}
|
135
151
|
/>
|
136
152
|
|
137
153
|
{/* Word divider with merge/split functionality */}
|
@@ -210,6 +226,60 @@ export default function EditWordList({
|
|
210
226
|
setPage(value);
|
211
227
|
};
|
212
228
|
|
229
|
+
// Handle tab navigation between word text fields
|
230
|
+
const handleTabNavigation = (currentIndex: number) => {
|
231
|
+
// console.log('handleTabNavigation called with index:', currentIndex);
|
232
|
+
const nextIndex = (currentIndex + 1) % words.length;
|
233
|
+
// console.log('Next index calculated:', nextIndex, 'Total words:', words.length);
|
234
|
+
|
235
|
+
// If the next word is on a different page, change the page
|
236
|
+
if (isGlobal && (nextIndex < startIndex || nextIndex >= endIndex)) {
|
237
|
+
// console.log('Next word is on different page. Current page:', page, 'startIndex:', startIndex, 'endIndex:', endIndex);
|
238
|
+
const nextPage = Math.floor(nextIndex / pageSize) + 1;
|
239
|
+
// console.log('Changing to page:', nextPage);
|
240
|
+
setPage(nextPage);
|
241
|
+
|
242
|
+
// Use setTimeout to allow the page change to render before focusing
|
243
|
+
setTimeout(() => {
|
244
|
+
// console.log('Timeout callback executing, trying to focus element with ID:', `word-text-${nextIndex}`);
|
245
|
+
focusWordTextField(nextIndex);
|
246
|
+
}, 50);
|
247
|
+
} else {
|
248
|
+
// console.log('Next word is on same page, trying to focus element with ID:', `word-text-${nextIndex}`);
|
249
|
+
focusWordTextField(nextIndex);
|
250
|
+
}
|
251
|
+
};
|
252
|
+
|
253
|
+
// Helper function to focus a word text field by index
|
254
|
+
const focusWordTextField = (index: number) => {
|
255
|
+
// Material-UI TextField uses a more complex structure
|
256
|
+
// The actual input is inside the TextField component
|
257
|
+
const element = document.getElementById(`word-text-${index}`);
|
258
|
+
// console.log('Element found:', !!element);
|
259
|
+
|
260
|
+
if (element) {
|
261
|
+
// Try different selectors to find the input element
|
262
|
+
// First try the standard input selector
|
263
|
+
let input = element.querySelector('input');
|
264
|
+
|
265
|
+
// If that doesn't work, try the MUI-specific selector
|
266
|
+
if (!input) {
|
267
|
+
input = element.querySelector('.MuiInputBase-input');
|
268
|
+
}
|
269
|
+
|
270
|
+
// console.log('Input element found:', !!input);
|
271
|
+
if (input) {
|
272
|
+
input.focus();
|
273
|
+
input.select();
|
274
|
+
// console.log('Focus and select called on input');
|
275
|
+
} else {
|
276
|
+
// As a fallback, try to focus the TextField itself
|
277
|
+
// console.log('Trying to focus the TextField itself');
|
278
|
+
element.focus();
|
279
|
+
}
|
280
|
+
}
|
281
|
+
};
|
282
|
+
|
213
283
|
return (
|
214
284
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, flexGrow: 1, minHeight: 0 }}>
|
215
285
|
{/* Initial divider with Add Segment Before button */}
|
@@ -265,6 +335,7 @@ export default function EditWordList({
|
|
265
335
|
onMergeSegment={onMergeSegment}
|
266
336
|
wordsLength={words.length}
|
267
337
|
isGlobal={isGlobal}
|
338
|
+
onTabNavigation={handleTabNavigation}
|
268
339
|
/>
|
269
340
|
);
|
270
341
|
})}
|
@@ -1,14 +1,12 @@
|
|
1
1
|
import { Box, Button, Typography, useMediaQuery, useTheme, Switch, FormControlLabel, Tooltip, Paper } from '@mui/material'
|
2
2
|
import LockIcon from '@mui/icons-material/Lock'
|
3
3
|
import UploadFileIcon from '@mui/icons-material/UploadFile'
|
4
|
-
import TextSnippetIcon from '@mui/icons-material/TextSnippet'
|
5
4
|
import FindReplaceIcon from '@mui/icons-material/FindReplace'
|
6
5
|
import EditIcon from '@mui/icons-material/Edit'
|
7
|
-
import { CorrectionData } from '../types'
|
6
|
+
import { CorrectionData, InteractionMode } from '../types'
|
8
7
|
import CorrectionMetrics from './CorrectionMetrics'
|
9
8
|
import ModeSelector from './ModeSelector'
|
10
9
|
import AudioPlayer from './AudioPlayer'
|
11
|
-
import { InteractionMode } from '../types'
|
12
10
|
import { ApiClient } from '../api'
|
13
11
|
import { findWordById } from './shared/utils/wordUtils'
|
14
12
|
|
@@ -29,7 +27,6 @@ interface HeaderProps {
|
|
29
27
|
onHandlerToggle: (handler: string, enabled: boolean) => void
|
30
28
|
isUpdatingHandlers: boolean
|
31
29
|
onHandlerClick?: (handler: string) => void
|
32
|
-
onAddLyrics?: () => void
|
33
30
|
onFindReplace?: () => void
|
34
31
|
onEditAll?: () => void
|
35
32
|
}
|
@@ -47,7 +44,6 @@ export default function Header({
|
|
47
44
|
onHandlerToggle,
|
48
45
|
isUpdatingHandlers,
|
49
46
|
onHandlerClick,
|
50
|
-
onAddLyrics,
|
51
47
|
onFindReplace,
|
52
48
|
onEditAll,
|
53
49
|
}: HeaderProps) {
|
@@ -271,17 +267,6 @@ export default function Header({
|
|
271
267
|
audioHash={audioHash}
|
272
268
|
/>
|
273
269
|
</Box>
|
274
|
-
{!isReadOnly && apiClient && onAddLyrics && (
|
275
|
-
<Button
|
276
|
-
variant="outlined"
|
277
|
-
size="small"
|
278
|
-
onClick={onAddLyrics}
|
279
|
-
startIcon={<TextSnippetIcon />}
|
280
|
-
sx={{ minWidth: 'fit-content', height: '32px' }}
|
281
|
-
>
|
282
|
-
Add Reference Lyrics
|
283
|
-
</Button>
|
284
|
-
)}
|
285
270
|
</Box>
|
286
271
|
</Paper>
|
287
272
|
</>
|
@@ -22,7 +22,8 @@ import {
|
|
22
22
|
deleteSegment,
|
23
23
|
updateSegment,
|
24
24
|
mergeSegment,
|
25
|
-
findAndReplace
|
25
|
+
findAndReplace,
|
26
|
+
deleteWord
|
26
27
|
} from './shared/utils/segmentOperations'
|
27
28
|
import { loadSavedData, saveData, clearSavedData } from './shared/utils/localStorage'
|
28
29
|
import { setupKeyboardHandlers, setModalHandler, getModalState } from './shared/utils/keyboardHandlers'
|
@@ -124,6 +125,7 @@ interface MemoizedReferenceViewProps {
|
|
124
125
|
onSourceChange: (source: string) => void
|
125
126
|
corrected_segments: LyricsSegment[]
|
126
127
|
corrections: WordCorrection[]
|
128
|
+
onAddLyrics?: () => void
|
127
129
|
}
|
128
130
|
|
129
131
|
// Create a memoized ReferenceView component
|
@@ -139,7 +141,8 @@ const MemoizedReferenceView = memo(function MemoizedReferenceView({
|
|
139
141
|
currentSource,
|
140
142
|
onSourceChange,
|
141
143
|
corrected_segments,
|
142
|
-
corrections
|
144
|
+
corrections,
|
145
|
+
onAddLyrics
|
143
146
|
}: MemoizedReferenceViewProps) {
|
144
147
|
return (
|
145
148
|
<ReferenceView
|
@@ -155,6 +158,7 @@ const MemoizedReferenceView = memo(function MemoizedReferenceView({
|
|
155
158
|
onSourceChange={onSourceChange}
|
156
159
|
corrected_segments={corrected_segments}
|
157
160
|
corrections={corrections}
|
161
|
+
onAddLyrics={onAddLyrics}
|
158
162
|
/>
|
159
163
|
);
|
160
164
|
});
|
@@ -195,7 +199,6 @@ const MemoizedHeader = memo(function MemoizedHeader({
|
|
195
199
|
onHandlerToggle,
|
196
200
|
isUpdatingHandlers,
|
197
201
|
onHandlerClick,
|
198
|
-
onAddLyrics,
|
199
202
|
onFindReplace,
|
200
203
|
onEditAll
|
201
204
|
}: MemoizedHeaderProps) {
|
@@ -213,7 +216,6 @@ const MemoizedHeader = memo(function MemoizedHeader({
|
|
213
216
|
onHandlerToggle={onHandlerToggle}
|
214
217
|
isUpdatingHandlers={isUpdatingHandlers}
|
215
218
|
onHandlerClick={onHandlerClick}
|
216
|
-
onAddLyrics={onAddLyrics}
|
217
219
|
onFindReplace={onFindReplace}
|
218
220
|
onEditAll={onEditAll}
|
219
221
|
/>
|
@@ -236,6 +238,7 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
236
238
|
const [originalData] = useState(() => JSON.parse(JSON.stringify(initialData)))
|
237
239
|
const [interactionMode, setInteractionMode] = useState<InteractionMode>('edit')
|
238
240
|
const [isShiftPressed, setIsShiftPressed] = useState(false)
|
241
|
+
const [isCtrlPressed, setIsCtrlPressed] = useState(false)
|
239
242
|
const [editModalSegment, setEditModalSegment] = useState<{
|
240
243
|
segment: LyricsSegment
|
241
244
|
index: number
|
@@ -301,8 +304,9 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
301
304
|
})
|
302
305
|
}
|
303
306
|
|
304
|
-
const { handleKeyDown, handleKeyUp } = setupKeyboardHandlers({
|
307
|
+
const { handleKeyDown, handleKeyUp, cleanup } = setupKeyboardHandlers({
|
305
308
|
setIsShiftPressed,
|
309
|
+
setIsCtrlPressed
|
306
310
|
})
|
307
311
|
|
308
312
|
// Always add keyboard listeners
|
@@ -315,6 +319,7 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
315
319
|
// Reset modifier states when a modal opens
|
316
320
|
if (isAnyModalOpen) {
|
317
321
|
setIsShiftPressed(false)
|
322
|
+
setIsCtrlPressed(false)
|
318
323
|
}
|
319
324
|
|
320
325
|
// Cleanup function
|
@@ -325,8 +330,10 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
325
330
|
window.removeEventListener('keydown', handleKeyDown)
|
326
331
|
window.removeEventListener('keyup', handleKeyUp)
|
327
332
|
document.body.style.userSelect = ''
|
333
|
+
// Call the cleanup function to remove window blur/focus listeners
|
334
|
+
cleanup()
|
328
335
|
}
|
329
|
-
}, [setIsShiftPressed, isAnyModalOpen])
|
336
|
+
}, [setIsShiftPressed, setIsCtrlPressed, isAnyModalOpen])
|
330
337
|
|
331
338
|
// Update modal state tracking
|
332
339
|
useEffect(() => {
|
@@ -342,7 +349,7 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
342
349
|
}, [modalContent, editModalSegment, isReviewModalOpen, isAddLyricsModalOpen, isFindReplaceModalOpen, isEditAllModalOpen])
|
343
350
|
|
344
351
|
// Calculate effective mode based on modifier key states
|
345
|
-
const effectiveMode = isShiftPressed ? 'highlight' : interactionMode
|
352
|
+
const effectiveMode = isCtrlPressed ? 'delete_word' : (isShiftPressed ? 'highlight' : interactionMode)
|
346
353
|
|
347
354
|
const handleFlash = useCallback((type: FlashType, info?: HighlightInfo) => {
|
348
355
|
setFlashingType(null)
|
@@ -367,6 +374,16 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
367
374
|
console.log('LyricsAnalyzer handleWordClick:', { info });
|
368
375
|
}
|
369
376
|
|
377
|
+
if (effectiveMode === 'delete_word') {
|
378
|
+
// Use the shared deleteWord utility function
|
379
|
+
const newData = deleteWord(data, info.word_id);
|
380
|
+
setData(newData);
|
381
|
+
|
382
|
+
// Flash to indicate the word was deleted
|
383
|
+
handleFlash('word');
|
384
|
+
return;
|
385
|
+
}
|
386
|
+
|
370
387
|
if (effectiveMode === 'highlight') {
|
371
388
|
// Find if this word is part of a correction
|
372
389
|
const correction = data.corrections?.find(c =>
|
@@ -491,7 +508,7 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
491
508
|
});
|
492
509
|
}
|
493
510
|
}
|
494
|
-
}, [data, effectiveMode, setModalContent]);
|
511
|
+
}, [data, effectiveMode, setModalContent, handleFlash, deleteWord]);
|
495
512
|
|
496
513
|
const handleUpdateSegment = useCallback((updatedSegment: LyricsSegment) => {
|
497
514
|
if (!editModalSegment) return
|
@@ -948,6 +965,7 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
948
965
|
onSourceChange={setCurrentSource}
|
949
966
|
corrected_segments={data.corrected_segments}
|
950
967
|
corrections={data.corrections}
|
968
|
+
onAddLyrics={() => setIsAddLyricsModalOpen(true)}
|
951
969
|
/>
|
952
970
|
</Grid>
|
953
971
|
</Grid>
|
@@ -1,6 +1,7 @@
|
|
1
|
-
import { ToggleButton, ToggleButtonGroup, Box, Typography } from '@mui/material';
|
1
|
+
import { ToggleButton, ToggleButtonGroup, Box, Typography, Tooltip } from '@mui/material';
|
2
2
|
import HighlightIcon from '@mui/icons-material/Highlight';
|
3
3
|
import EditIcon from '@mui/icons-material/Edit';
|
4
|
+
import DeleteIcon from '@mui/icons-material/Delete';
|
4
5
|
import { InteractionMode } from '../types';
|
5
6
|
|
6
7
|
interface ModeSelectorProps {
|
@@ -17,7 +18,7 @@ export default function ModeSelector({ effectiveMode, onChange }: ModeSelectorPr
|
|
17
18
|
<ToggleButtonGroup
|
18
19
|
value={effectiveMode}
|
19
20
|
exclusive
|
20
|
-
onChange={(_, newMode) => newMode && onChange(newMode)}
|
21
|
+
onChange={(_, newMode) => newMode === 'edit' && onChange(newMode)}
|
21
22
|
size="small"
|
22
23
|
sx={{
|
23
24
|
height: '32px',
|
@@ -28,20 +29,38 @@ export default function ModeSelector({ effectiveMode, onChange }: ModeSelectorPr
|
|
28
29
|
}
|
29
30
|
}}
|
30
31
|
>
|
31
|
-
<
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
32
|
+
<Tooltip title="Default mode; click words to edit that lyrics segment">
|
33
|
+
<ToggleButton
|
34
|
+
value="edit"
|
35
|
+
>
|
36
|
+
<EditIcon sx={{ mr: 0.5, fontSize: '1rem' }} />
|
37
|
+
Edit
|
38
|
+
</ToggleButton>
|
39
|
+
</Tooltip>
|
40
|
+
|
41
|
+
<Tooltip title="Hold SHIFT and click words to highlight the matching anchor sequence in the reference lyrics">
|
42
|
+
<span>
|
43
|
+
<ToggleButton
|
44
|
+
value="highlight"
|
45
|
+
disabled
|
46
|
+
>
|
47
|
+
<HighlightIcon sx={{ mr: 0.5, fontSize: '1rem' }} />
|
48
|
+
Highlight
|
49
|
+
</ToggleButton>
|
50
|
+
</span>
|
51
|
+
</Tooltip>
|
52
|
+
|
53
|
+
<Tooltip title="Hold CTRL and click words to delete them">
|
54
|
+
<span>
|
55
|
+
<ToggleButton
|
56
|
+
value="delete_word"
|
57
|
+
disabled
|
58
|
+
>
|
59
|
+
<DeleteIcon sx={{ mr: 0.5, fontSize: '1rem' }} />
|
60
|
+
Delete
|
61
|
+
</ToggleButton>
|
62
|
+
</span>
|
63
|
+
</Tooltip>
|
45
64
|
</ToggleButtonGroup>
|
46
65
|
</Box>
|
47
66
|
);
|
@@ -34,7 +34,8 @@ export default function ReferenceView({
|
|
34
34
|
highlightInfo,
|
35
35
|
mode,
|
36
36
|
gaps,
|
37
|
-
corrections
|
37
|
+
corrections,
|
38
|
+
onAddLyrics
|
38
39
|
}: ReferenceViewProps) {
|
39
40
|
// Get available sources from referenceSources object
|
40
41
|
const availableSources = useMemo(() =>
|
@@ -183,6 +184,7 @@ export default function ReferenceView({
|
|
183
184
|
availableSources={availableSources}
|
184
185
|
currentSource={effectiveCurrentSource}
|
185
186
|
onSourceChange={onSourceChange}
|
187
|
+
onAddLyrics={onAddLyrics}
|
186
188
|
/>
|
187
189
|
</Box>
|
188
190
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.2 }}>
|
@@ -181,7 +181,8 @@ export function HighlightedText({
|
|
181
181
|
return correction ? {
|
182
182
|
originalWord: correction.original_word,
|
183
183
|
handler: correction.handler,
|
184
|
-
confidence: correction.confidence
|
184
|
+
confidence: correction.confidence,
|
185
|
+
source: correction.source
|
185
186
|
} : null;
|
186
187
|
})()}
|
187
188
|
/>
|
@@ -219,7 +220,8 @@ export function HighlightedText({
|
|
219
220
|
const correctionInfo = correction ? {
|
220
221
|
originalWord: correction.original_word,
|
221
222
|
handler: correction.handler,
|
222
|
-
confidence: correction.confidence
|
223
|
+
confidence: correction.confidence,
|
224
|
+
source: correction.source
|
223
225
|
} : null;
|
224
226
|
|
225
227
|
return (
|
@@ -1,21 +1,23 @@
|
|
1
1
|
import { Box, Button } from '@mui/material'
|
2
|
+
import AddIcon from '@mui/icons-material/Add'
|
2
3
|
|
3
4
|
export interface SourceSelectorProps {
|
4
5
|
currentSource: string
|
5
6
|
onSourceChange: (source: string) => void
|
6
7
|
availableSources: string[]
|
8
|
+
onAddLyrics?: () => void
|
7
9
|
}
|
8
10
|
|
9
|
-
export function SourceSelector({ currentSource, onSourceChange, availableSources }: SourceSelectorProps) {
|
11
|
+
export function SourceSelector({ currentSource, onSourceChange, availableSources, onAddLyrics }: SourceSelectorProps) {
|
10
12
|
return (
|
11
|
-
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.3 }}>
|
13
|
+
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.3, alignItems: 'center' }}>
|
12
14
|
{availableSources.map((source) => (
|
13
15
|
<Button
|
14
16
|
key={source}
|
15
17
|
size="small"
|
16
18
|
variant={currentSource === source ? 'contained' : 'outlined'}
|
17
19
|
onClick={() => onSourceChange(source)}
|
18
|
-
sx={{
|
20
|
+
sx={{
|
19
21
|
mr: 0,
|
20
22
|
py: 0.2,
|
21
23
|
px: 0.8,
|
@@ -28,6 +30,27 @@ export function SourceSelector({ currentSource, onSourceChange, availableSources
|
|
28
30
|
{source.charAt(0).toUpperCase() + source.slice(1)}
|
29
31
|
</Button>
|
30
32
|
))}
|
33
|
+
{onAddLyrics && (
|
34
|
+
<Button
|
35
|
+
size="small"
|
36
|
+
variant="outlined"
|
37
|
+
onClick={onAddLyrics}
|
38
|
+
startIcon={<AddIcon sx={{ fontSize: '0.9rem' }} />}
|
39
|
+
sx={{
|
40
|
+
mr: 0,
|
41
|
+
py: 0.2,
|
42
|
+
px: 0.8,
|
43
|
+
minWidth: 'auto',
|
44
|
+
fontSize: '0.7rem',
|
45
|
+
lineHeight: 1.2,
|
46
|
+
'& .MuiButton-startIcon': {
|
47
|
+
marginLeft: '-5px',
|
48
|
+
marginRight: '1px',
|
49
|
+
marginTop: '-1px'
|
50
|
+
}
|
51
|
+
}}
|
52
|
+
>New</Button>
|
53
|
+
)}
|
31
54
|
</Box>
|
32
55
|
)
|
33
56
|
}
|
@@ -61,7 +61,8 @@ export const WordComponent = React.memo(function Word({
|
|
61
61
|
const tooltipContent = (
|
62
62
|
<>
|
63
63
|
<strong>Original:</strong> "{correction.originalWord}"<br />
|
64
|
-
<strong>Corrected by:</strong> {correction.handler}
|
64
|
+
<strong>Corrected by:</strong> {correction.handler}<br />
|
65
|
+
<strong>Source:</strong> {correction.source}
|
65
66
|
</>
|
66
67
|
)
|
67
68
|
|
@@ -97,7 +97,7 @@ export function useWordClick({
|
|
97
97
|
}
|
98
98
|
}
|
99
99
|
|
100
|
-
if (mode === 'highlight' || mode === 'edit') {
|
100
|
+
if (mode === 'highlight' || mode === 'edit' || mode === 'delete_word') {
|
101
101
|
if (belongsToAnchor && anchor) {
|
102
102
|
onWordClick?.({
|
103
103
|
word_id: wordId,
|
@@ -131,7 +131,8 @@ export function useWordClick({
|
|
131
131
|
gap: undefined
|
132
132
|
})
|
133
133
|
}
|
134
|
-
} else
|
134
|
+
} else {
|
135
|
+
// This is a fallback for any future modes
|
135
136
|
if (belongsToAnchor && anchor) {
|
136
137
|
onElementClick({
|
137
138
|
type: 'anchor',
|