lyrics-transcriber 0.46.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/dist/assets/{index-BXOpmKq-.js → index-2vK-qVJS.js} +128 -65
- lyrics_transcriber/frontend/dist/assets/{index-BXOpmKq-.js.map → index-2vK-qVJS.js.map} +1 -1
- lyrics_transcriber/frontend/dist/index.html +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/LyricsAnalyzer.tsx +21 -5
- lyrics_transcriber/frontend/src/components/ModeSelector.tsx +35 -16
- lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +3 -2
- 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.46.0.dist-info → lyrics_transcriber-0.47.0.dist-info}/METADATA +1 -1
- {lyrics_transcriber-0.46.0.dist-info → lyrics_transcriber-0.47.0.dist-info}/RECORD +16 -16
- {lyrics_transcriber-0.46.0.dist-info → lyrics_transcriber-0.47.0.dist-info}/LICENSE +0 -0
- {lyrics_transcriber-0.46.0.dist-info → lyrics_transcriber-0.47.0.dist-info}/WHEEL +0 -0
- {lyrics_transcriber-0.46.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
|
})}
|
@@ -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'
|
@@ -237,6 +238,7 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
237
238
|
const [originalData] = useState(() => JSON.parse(JSON.stringify(initialData)))
|
238
239
|
const [interactionMode, setInteractionMode] = useState<InteractionMode>('edit')
|
239
240
|
const [isShiftPressed, setIsShiftPressed] = useState(false)
|
241
|
+
const [isCtrlPressed, setIsCtrlPressed] = useState(false)
|
240
242
|
const [editModalSegment, setEditModalSegment] = useState<{
|
241
243
|
segment: LyricsSegment
|
242
244
|
index: number
|
@@ -302,8 +304,9 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
302
304
|
})
|
303
305
|
}
|
304
306
|
|
305
|
-
const { handleKeyDown, handleKeyUp } = setupKeyboardHandlers({
|
307
|
+
const { handleKeyDown, handleKeyUp, cleanup } = setupKeyboardHandlers({
|
306
308
|
setIsShiftPressed,
|
309
|
+
setIsCtrlPressed
|
307
310
|
})
|
308
311
|
|
309
312
|
// Always add keyboard listeners
|
@@ -316,6 +319,7 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
316
319
|
// Reset modifier states when a modal opens
|
317
320
|
if (isAnyModalOpen) {
|
318
321
|
setIsShiftPressed(false)
|
322
|
+
setIsCtrlPressed(false)
|
319
323
|
}
|
320
324
|
|
321
325
|
// Cleanup function
|
@@ -326,8 +330,10 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
326
330
|
window.removeEventListener('keydown', handleKeyDown)
|
327
331
|
window.removeEventListener('keyup', handleKeyUp)
|
328
332
|
document.body.style.userSelect = ''
|
333
|
+
// Call the cleanup function to remove window blur/focus listeners
|
334
|
+
cleanup()
|
329
335
|
}
|
330
|
-
}, [setIsShiftPressed, isAnyModalOpen])
|
336
|
+
}, [setIsShiftPressed, setIsCtrlPressed, isAnyModalOpen])
|
331
337
|
|
332
338
|
// Update modal state tracking
|
333
339
|
useEffect(() => {
|
@@ -343,7 +349,7 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
343
349
|
}, [modalContent, editModalSegment, isReviewModalOpen, isAddLyricsModalOpen, isFindReplaceModalOpen, isEditAllModalOpen])
|
344
350
|
|
345
351
|
// Calculate effective mode based on modifier key states
|
346
|
-
const effectiveMode = isShiftPressed ? 'highlight' : interactionMode
|
352
|
+
const effectiveMode = isCtrlPressed ? 'delete_word' : (isShiftPressed ? 'highlight' : interactionMode)
|
347
353
|
|
348
354
|
const handleFlash = useCallback((type: FlashType, info?: HighlightInfo) => {
|
349
355
|
setFlashingType(null)
|
@@ -368,6 +374,16 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
368
374
|
console.log('LyricsAnalyzer handleWordClick:', { info });
|
369
375
|
}
|
370
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
|
+
|
371
387
|
if (effectiveMode === 'highlight') {
|
372
388
|
// Find if this word is part of a correction
|
373
389
|
const correction = data.corrections?.find(c =>
|
@@ -492,7 +508,7 @@ export default function LyricsAnalyzer({ data: initialData, onFileLoad, apiClien
|
|
492
508
|
});
|
493
509
|
}
|
494
510
|
}
|
495
|
-
}, [data, effectiveMode, setModalContent]);
|
511
|
+
}, [data, effectiveMode, setModalContent, handleFlash, deleteWord]);
|
496
512
|
|
497
513
|
const handleUpdateSegment = useCallback((updatedSegment: LyricsSegment) => {
|
498
514
|
if (!editModalSegment) return
|
@@ -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
|
);
|
@@ -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',
|
@@ -35,6 +35,16 @@ export const setupKeyboardHandlers = (state: KeyboardState) => {
|
|
35
35
|
console.log(`Setting up keyboard handlers [${handlerId}]`)
|
36
36
|
}
|
37
37
|
|
38
|
+
// Function to reset modifier key states
|
39
|
+
const resetModifierStates = () => {
|
40
|
+
if (debugLog) {
|
41
|
+
console.log(`Resetting modifier states [${handlerId}]`)
|
42
|
+
}
|
43
|
+
state.setIsShiftPressed(false)
|
44
|
+
state.setIsCtrlPressed?.(false)
|
45
|
+
document.body.style.userSelect = ''
|
46
|
+
}
|
47
|
+
|
38
48
|
const handleKeyDown = (e: KeyboardEvent) => {
|
39
49
|
if (debugLog) {
|
40
50
|
console.log(`Keyboard event captured [${handlerId}]`, {
|
@@ -59,7 +69,7 @@ export const setupKeyboardHandlers = (state: KeyboardState) => {
|
|
59
69
|
if (e.key === 'Shift') {
|
60
70
|
state.setIsShiftPressed(true)
|
61
71
|
document.body.style.userSelect = 'none'
|
62
|
-
} else if (e.key === 'Meta') {
|
72
|
+
} else if (e.key === 'Control' || e.key === 'Ctrl' || e.key === 'Meta') {
|
63
73
|
state.setIsCtrlPressed?.(true)
|
64
74
|
} else if (e.key === ' ' || e.code === 'Space') {
|
65
75
|
if (debugLog) {
|
@@ -102,12 +112,11 @@ export const setupKeyboardHandlers = (state: KeyboardState) => {
|
|
102
112
|
})
|
103
113
|
}
|
104
114
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
} else if (e.key === ' ' || e.code === 'Space') {
|
115
|
+
// Always reset the modifier states regardless of the key which was released
|
116
|
+
// to help prevent accidentally getting stuck in a mode or accidentally deleting words
|
117
|
+
resetModifierStates()
|
118
|
+
|
119
|
+
if (e.key === ' ' || e.code === 'Space') {
|
111
120
|
if (debugLog) {
|
112
121
|
console.log('Keyboard handler - Spacebar released', {
|
113
122
|
modalOpen: isModalOpen,
|
@@ -128,7 +137,35 @@ export const setupKeyboardHandlers = (state: KeyboardState) => {
|
|
128
137
|
}
|
129
138
|
}
|
130
139
|
|
131
|
-
|
140
|
+
// Handle window blur event (user switches tabs or apps)
|
141
|
+
const handleWindowBlur = () => {
|
142
|
+
if (debugLog) {
|
143
|
+
console.log(`Window blur detected [${handlerId}], resetting modifier states`)
|
144
|
+
}
|
145
|
+
resetModifierStates()
|
146
|
+
}
|
147
|
+
|
148
|
+
// Handle window focus event (user returns to the app)
|
149
|
+
const handleWindowFocus = () => {
|
150
|
+
if (debugLog) {
|
151
|
+
console.log(`Window focus detected [${handlerId}], ensuring modifier states are reset`)
|
152
|
+
}
|
153
|
+
resetModifierStates()
|
154
|
+
}
|
155
|
+
|
156
|
+
// Add window event listeners
|
157
|
+
window.addEventListener('blur', handleWindowBlur)
|
158
|
+
window.addEventListener('focus', handleWindowFocus)
|
159
|
+
|
160
|
+
// Return a cleanup function that includes removing the window event listeners
|
161
|
+
return {
|
162
|
+
handleKeyDown,
|
163
|
+
handleKeyUp,
|
164
|
+
cleanup: () => {
|
165
|
+
window.removeEventListener('blur', handleWindowBlur)
|
166
|
+
window.removeEventListener('focus', handleWindowFocus)
|
167
|
+
}
|
168
|
+
}
|
132
169
|
}
|
133
170
|
|
134
171
|
// Export these for external use
|
@@ -310,4 +310,51 @@ export function findAndReplace(
|
|
310
310
|
newData.corrected_segments = newData.corrected_segments.filter(segment => segment.words.length > 0);
|
311
311
|
|
312
312
|
return newData
|
313
|
+
}
|
314
|
+
|
315
|
+
/**
|
316
|
+
* Deletes a word from a segment in the correction data
|
317
|
+
* @param data The correction data
|
318
|
+
* @param wordId The ID of the word to delete
|
319
|
+
* @returns Updated correction data with the word removed
|
320
|
+
*/
|
321
|
+
export function deleteWord(
|
322
|
+
data: CorrectionData,
|
323
|
+
wordId: string
|
324
|
+
): CorrectionData {
|
325
|
+
// Find the segment containing this word
|
326
|
+
const segmentIndex = data.corrected_segments.findIndex(segment =>
|
327
|
+
segment.words.some(word => word.id === wordId)
|
328
|
+
);
|
329
|
+
|
330
|
+
if (segmentIndex === -1) {
|
331
|
+
// Word not found, return data unchanged
|
332
|
+
return data;
|
333
|
+
}
|
334
|
+
|
335
|
+
const segment = data.corrected_segments[segmentIndex];
|
336
|
+
const wordIndex = segment.words.findIndex(word => word.id === wordId);
|
337
|
+
|
338
|
+
if (wordIndex === -1) {
|
339
|
+
// Word not found in segment (shouldn't happen), return data unchanged
|
340
|
+
return data;
|
341
|
+
}
|
342
|
+
|
343
|
+
// Create a new segment with the word removed
|
344
|
+
const updatedWords = segment.words.filter((_, index) => index !== wordIndex);
|
345
|
+
|
346
|
+
if (updatedWords.length > 0) {
|
347
|
+
// Update the segment with the word removed
|
348
|
+
const updatedSegment = {
|
349
|
+
...segment,
|
350
|
+
words: updatedWords,
|
351
|
+
text: updatedWords.map(w => w.text).join(' ')
|
352
|
+
};
|
353
|
+
|
354
|
+
// Update the data
|
355
|
+
return updateSegment(data, segmentIndex, updatedSegment);
|
356
|
+
} else {
|
357
|
+
// If the segment would be empty, delete the entire segment
|
358
|
+
return deleteSegment(data, segmentIndex);
|
359
|
+
}
|
313
360
|
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: lyrics-transcriber
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.47.0
|
4
4
|
Summary: Automatically create synchronised lyrics files in ASS and MidiCo LRC formats with word-level timestamps, using Whisper and lyrics from Genius and Spotify
|
5
5
|
License: MIT
|
6
6
|
Author: Andrew Beveridge
|