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.
Files changed (26) hide show
  1. lyrics_transcriber/frontend/.yarn/releases/{yarn-4.6.0.cjs → yarn-4.7.0.cjs} +292 -291
  2. lyrics_transcriber/frontend/.yarnrc.yml +1 -1
  3. lyrics_transcriber/frontend/dist/assets/{index-ZCT0s9MG.js → index-2vK-qVJS.js} +231 -151
  4. lyrics_transcriber/frontend/dist/assets/index-2vK-qVJS.js.map +1 -0
  5. lyrics_transcriber/frontend/dist/index.html +1 -1
  6. lyrics_transcriber/frontend/package.json +1 -1
  7. lyrics_transcriber/frontend/src/components/EditModal.tsx +36 -36
  8. lyrics_transcriber/frontend/src/components/EditWordList.tsx +73 -2
  9. lyrics_transcriber/frontend/src/components/Header.tsx +1 -16
  10. lyrics_transcriber/frontend/src/components/LyricsAnalyzer.tsx +26 -8
  11. lyrics_transcriber/frontend/src/components/ModeSelector.tsx +35 -16
  12. lyrics_transcriber/frontend/src/components/ReferenceView.tsx +3 -1
  13. lyrics_transcriber/frontend/src/components/shared/components/HighlightedText.tsx +4 -2
  14. lyrics_transcriber/frontend/src/components/shared/components/SourceSelector.tsx +26 -3
  15. lyrics_transcriber/frontend/src/components/shared/components/Word.tsx +2 -1
  16. lyrics_transcriber/frontend/src/components/shared/hooks/useWordClick.ts +3 -2
  17. lyrics_transcriber/frontend/src/components/shared/types.ts +2 -0
  18. lyrics_transcriber/frontend/src/components/shared/utils/keyboardHandlers.ts +45 -8
  19. lyrics_transcriber/frontend/src/components/shared/utils/segmentOperations.ts +47 -0
  20. lyrics_transcriber/frontend/src/types.ts +1 -1
  21. {lyrics_transcriber-0.45.0.dist-info → lyrics_transcriber-0.47.0.dist-info}/METADATA +1 -1
  22. {lyrics_transcriber-0.45.0.dist-info → lyrics_transcriber-0.47.0.dist-info}/RECORD +25 -25
  23. lyrics_transcriber/frontend/dist/assets/index-ZCT0s9MG.js.map +0 -1
  24. {lyrics_transcriber-0.45.0.dist-info → lyrics_transcriber-0.47.0.dist-info}/LICENSE +0 -0
  25. {lyrics_transcriber-0.45.0.dist-info → lyrics_transcriber-0.47.0.dist-info}/WHEEL +0 -0
  26. {lyrics_transcriber-0.45.0.dist-info → lyrics_transcriber-0.47.0.dist-info}/entry_points.txt +0 -0
@@ -32843,25 +32843,53 @@ function calculateReferenceLinePositions(corrected_segments, anchors, currentSou
32843
32843
  }
32844
32844
  return { linePositions };
32845
32845
  }
32846
- function SourceSelector({ currentSource, onSourceChange, availableSources }) {
32847
- return /* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: { display: "flex", flexWrap: "wrap", gap: 0.3 }, children: availableSources.map((source) => /* @__PURE__ */ jsxRuntimeExports.jsx(
32848
- Button,
32849
- {
32850
- size: "small",
32851
- variant: currentSource === source ? "contained" : "outlined",
32852
- onClick: () => onSourceChange(source),
32853
- sx: {
32854
- mr: 0,
32855
- py: 0.2,
32856
- px: 0.8,
32857
- minWidth: "auto",
32858
- fontSize: "0.7rem",
32859
- lineHeight: 1.2
32846
+ const AddIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
32847
+ d: "M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6z"
32848
+ }), "Add");
32849
+ function SourceSelector({ currentSource, onSourceChange, availableSources, onAddLyrics }) {
32850
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", flexWrap: "wrap", gap: 0.3, alignItems: "center" }, children: [
32851
+ availableSources.map((source) => /* @__PURE__ */ jsxRuntimeExports.jsx(
32852
+ Button,
32853
+ {
32854
+ size: "small",
32855
+ variant: currentSource === source ? "contained" : "outlined",
32856
+ onClick: () => onSourceChange(source),
32857
+ sx: {
32858
+ mr: 0,
32859
+ py: 0.2,
32860
+ px: 0.8,
32861
+ minWidth: "auto",
32862
+ fontSize: "0.7rem",
32863
+ lineHeight: 1.2
32864
+ },
32865
+ children: source.charAt(0).toUpperCase() + source.slice(1)
32860
32866
  },
32861
- children: source.charAt(0).toUpperCase() + source.slice(1)
32862
- },
32863
- source
32864
- )) });
32867
+ source
32868
+ )),
32869
+ onAddLyrics && /* @__PURE__ */ jsxRuntimeExports.jsx(
32870
+ Button,
32871
+ {
32872
+ size: "small",
32873
+ variant: "outlined",
32874
+ onClick: onAddLyrics,
32875
+ startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(AddIcon, { sx: { fontSize: "0.9rem" } }),
32876
+ sx: {
32877
+ mr: 0,
32878
+ py: 0.2,
32879
+ px: 0.8,
32880
+ minWidth: "auto",
32881
+ fontSize: "0.7rem",
32882
+ lineHeight: 1.2,
32883
+ "& .MuiButton-startIcon": {
32884
+ marginLeft: "-5px",
32885
+ marginRight: "1px",
32886
+ marginTop: "-1px"
32887
+ }
32888
+ },
32889
+ children: "New"
32890
+ }
32891
+ )
32892
+ ] });
32865
32893
  }
32866
32894
  const HighlightedWord = styled$1("span")(
32867
32895
  ({ shouldFlash }) => ({
@@ -32922,7 +32950,11 @@ const WordComponent = React.memo(function Word({
32922
32950
  /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
32923
32951
  /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Corrected by:" }),
32924
32952
  " ",
32925
- correction.handler
32953
+ correction.handler,
32954
+ /* @__PURE__ */ jsxRuntimeExports.jsx("br", {}),
32955
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { children: "Source:" }),
32956
+ " ",
32957
+ correction.source
32926
32958
  ] });
32927
32959
  return /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: tooltipContent, arrow: true, placement: "top", children: wordElement });
32928
32960
  }
@@ -32986,7 +33018,7 @@ function useWordClick({
32986
33018
  gap2 = matchingGap2;
32987
33019
  }
32988
33020
  }
32989
- if (mode === "highlight" || mode === "edit") {
33021
+ if (mode === "highlight" || mode === "edit" || mode === "delete_word") {
32990
33022
  if (belongsToAnchor && anchor) {
32991
33023
  onWordClick == null ? void 0 : onWordClick({
32992
33024
  word_id: wordId,
@@ -33018,7 +33050,7 @@ function useWordClick({
33018
33050
  gap: void 0
33019
33051
  });
33020
33052
  }
33021
- } else if (mode === "details") {
33053
+ } else {
33022
33054
  if (belongsToAnchor && anchor) {
33023
33055
  onElementClick({
33024
33056
  type: "anchor",
@@ -33186,7 +33218,8 @@ function HighlightedText({
33186
33218
  return correction ? {
33187
33219
  originalWord: correction.original_word,
33188
33220
  handler: correction.handler,
33189
- confidence: correction.confidence
33221
+ confidence: correction.confidence,
33222
+ source: correction.source
33190
33223
  } : null;
33191
33224
  })()
33192
33225
  },
@@ -33215,7 +33248,8 @@ function HighlightedText({
33215
33248
  const correctionInfo = correction ? {
33216
33249
  originalWord: correction.original_word,
33217
33250
  handler: correction.handler,
33218
- confidence: correction.confidence
33251
+ confidence: correction.confidence,
33252
+ source: correction.source
33219
33253
  } : null;
33220
33254
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(React.Fragment, { children: [
33221
33255
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -33386,7 +33420,8 @@ function ReferenceView({
33386
33420
  highlightInfo,
33387
33421
  mode,
33388
33422
  gaps,
33389
- corrections
33423
+ corrections,
33424
+ onAddLyrics
33390
33425
  }) {
33391
33426
  var _a;
33392
33427
  const availableSources = reactExports.useMemo(
@@ -33496,7 +33531,8 @@ function ReferenceView({
33496
33531
  {
33497
33532
  availableSources,
33498
33533
  currentSource: effectiveCurrentSource,
33499
- onSourceChange
33534
+ onSourceChange,
33535
+ onAddLyrics
33500
33536
  }
33501
33537
  )
33502
33538
  ] }),
@@ -34563,9 +34599,6 @@ const SplitIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
34563
34599
  const AutoFixHighIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
34564
34600
  d: "M7.5 5.6 10 7 8.6 4.5 10 2 7.5 3.4 5 2l1.4 2.5L5 7zm12 9.8L17 14l1.4 2.5L17 19l2.5-1.4L22 19l-1.4-2.5L22 14zM22 2l-2.5 1.4L17 2l1.4 2.5L17 7l2.5-1.4L22 7l-1.4-2.5zm-7.63 5.29a.996.996 0 0 0-1.41 0L1.29 18.96c-.39.39-.39 1.02 0 1.41l2.34 2.34c.39.39 1.02.39 1.41 0L16.7 11.05c.39-.39.39-1.02 0-1.41zm-1.03 5.49-2.12-2.12 2.44-2.44 2.12 2.12z"
34565
34601
  }), "AutoFixHigh");
34566
- const AddIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
34567
- d: "M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6z"
34568
- }), "Add");
34569
34602
  const MergeIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
34570
34603
  d: "M17 20.41 18.41 19 15 15.59 13.59 17zM7.5 8H11v5.59L5.59 19 7 20.41l6-6V8h3.5L12 3.5z"
34571
34604
  }), "CallMerge");
@@ -34733,9 +34766,16 @@ const WordRow = reactExports.memo(function WordRow2({
34733
34766
  onWordUpdate,
34734
34767
  onSplitWord,
34735
34768
  onRemoveWord,
34736
- wordsLength
34769
+ wordsLength,
34770
+ onTabNavigation
34737
34771
  }) {
34738
34772
  var _a, _b;
34773
+ const handleKeyDown = (e) => {
34774
+ if (e.key === "Tab" && !e.shiftKey) {
34775
+ e.preventDefault();
34776
+ onTabNavigation(index);
34777
+ }
34778
+ };
34739
34779
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: {
34740
34780
  display: "flex",
34741
34781
  gap: 2,
@@ -34748,8 +34788,10 @@ const WordRow = reactExports.memo(function WordRow2({
34748
34788
  label: `Word ${index}`,
34749
34789
  value: word.text,
34750
34790
  onChange: (e) => onWordUpdate(index, { text: e.target.value }),
34791
+ onKeyDown: handleKeyDown,
34751
34792
  fullWidth: true,
34752
- size: "small"
34793
+ size: "small",
34794
+ id: `word-text-${index}`
34753
34795
  }
34754
34796
  ),
34755
34797
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -34811,7 +34853,8 @@ const WordItem = reactExports.memo(function WordItem2({
34811
34853
  onAddSegment,
34812
34854
  onMergeSegment,
34813
34855
  wordsLength,
34814
- isGlobal
34856
+ isGlobal,
34857
+ onTabNavigation
34815
34858
  }) {
34816
34859
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { children: [
34817
34860
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -34822,7 +34865,8 @@ const WordItem = reactExports.memo(function WordItem2({
34822
34865
  onWordUpdate,
34823
34866
  onSplitWord,
34824
34867
  onRemoveWord,
34825
- wordsLength
34868
+ wordsLength,
34869
+ onTabNavigation
34826
34870
  }
34827
34871
  ),
34828
34872
  !isGlobal && /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -34882,6 +34926,33 @@ function EditWordList({
34882
34926
  const handlePageChange = (_event, value) => {
34883
34927
  setPage(value);
34884
34928
  };
34929
+ const handleTabNavigation = (currentIndex) => {
34930
+ const nextIndex = (currentIndex + 1) % words.length;
34931
+ if (isGlobal && (nextIndex < startIndex || nextIndex >= endIndex)) {
34932
+ const nextPage = Math.floor(nextIndex / pageSize) + 1;
34933
+ setPage(nextPage);
34934
+ setTimeout(() => {
34935
+ focusWordTextField(nextIndex);
34936
+ }, 50);
34937
+ } else {
34938
+ focusWordTextField(nextIndex);
34939
+ }
34940
+ };
34941
+ const focusWordTextField = (index) => {
34942
+ const element = document.getElementById(`word-text-${index}`);
34943
+ if (element) {
34944
+ let input = element.querySelector("input");
34945
+ if (!input) {
34946
+ input = element.querySelector(".MuiInputBase-input");
34947
+ }
34948
+ if (input) {
34949
+ input.focus();
34950
+ input.select();
34951
+ } else {
34952
+ element.focus();
34953
+ }
34954
+ }
34955
+ };
34885
34956
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", flexDirection: "column", gap: 1, flexGrow: 1, minHeight: 0 }, children: [
34886
34957
  !isGlobal && /* @__PURE__ */ jsxRuntimeExports.jsx(
34887
34958
  WordDivider,
@@ -34933,7 +35004,8 @@ function EditWordList({
34933
35004
  onAddSegment,
34934
35005
  onMergeSegment,
34935
35006
  wordsLength: words.length,
34936
- isGlobal
35007
+ isGlobal,
35008
+ onTabNavigation: handleTabNavigation
34937
35009
  },
34938
35010
  word.id
34939
35011
  );
@@ -35146,15 +35218,6 @@ function EditModal({
35146
35218
  isGlobal = false,
35147
35219
  isLoading = false
35148
35220
  }) {
35149
- console.log("EditModal - Render", {
35150
- open,
35151
- isGlobal,
35152
- isLoading,
35153
- hasSegment: !!segment,
35154
- segmentIndex,
35155
- hasOriginalSegment: !!originalSegment,
35156
- hasOriginalTranscribedSegment: !!originalTranscribedSegment
35157
- });
35158
35221
  const [editedSegment, setEditedSegment] = reactExports.useState(segment);
35159
35222
  const [isPlaying, setIsPlaying] = reactExports.useState(false);
35160
35223
  const updateSegment2 = reactExports.useCallback((newWords) => {
@@ -35185,19 +35248,12 @@ function EditModal({
35185
35248
  updateSegment: updateSegment2
35186
35249
  });
35187
35250
  const handleClose = reactExports.useCallback(() => {
35188
- console.log("EditModal - handleClose called");
35189
35251
  cleanupManualSync();
35190
35252
  onClose();
35191
35253
  }, [onClose, cleanupManualSync]);
35192
35254
  reactExports.useEffect(() => {
35193
35255
  const spacebarHandler = handleSpacebar;
35194
35256
  if (open) {
35195
- console.log("EditModal - Setting up modal spacebar handler", {
35196
- hasPlaySegment: !!onPlaySegment,
35197
- editedSegmentId: editedSegment == null ? void 0 : editedSegment.id,
35198
- handlerFunction: spacebarHandler.toString().slice(0, 100),
35199
- isLoading
35200
- });
35201
35257
  const handleKeyEvent = (e) => {
35202
35258
  if (e.code === "Space") {
35203
35259
  spacebarHandler(e);
@@ -35206,7 +35262,6 @@ function EditModal({
35206
35262
  setModalSpacebarHandler(() => handleKeyEvent);
35207
35263
  return () => {
35208
35264
  if (!open) {
35209
- console.log("EditModal - Cleanup: clearing modal spacebar handler");
35210
35265
  setModalSpacebarHandler(void 0);
35211
35266
  }
35212
35267
  };
@@ -35228,11 +35283,6 @@ function EditModal({
35228
35283
  }
35229
35284
  }, [currentTime, editedSegment]);
35230
35285
  reactExports.useEffect(() => {
35231
- console.log("EditModal - segment changed", {
35232
- hasSegment: !!segment,
35233
- segmentId: segment == null ? void 0 : segment.id,
35234
- wordCount: segment == null ? void 0 : segment.words.length
35235
- });
35236
35286
  setEditedSegment(segment);
35237
35287
  }, [segment]);
35238
35288
  reactExports.useEffect(() => {
@@ -35240,7 +35290,6 @@ function EditModal({
35240
35290
  if (!editedSegment) return;
35241
35291
  const endTime = editedSegment.end_time ?? 0;
35242
35292
  if (window.isAudioPlaying && currentTime > endTime) {
35243
- console.log("Stopping playback: current time exceeded end time");
35244
35293
  (_a = window.toggleAudioPlayback) == null ? void 0 : _a.call(window);
35245
35294
  cleanupManualSync();
35246
35295
  }
@@ -35403,7 +35452,6 @@ function EditModal({
35403
35452
  return getSafeTimeRange(editedSegment);
35404
35453
  }, [getSafeTimeRange, editedSegment]);
35405
35454
  const dialogTitle = reactExports.useMemo(() => {
35406
- console.log("EditModal - Rendering dialog title", { isLoading, isGlobal });
35407
35455
  if (isLoading) {
35408
35456
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(DialogTitle, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [
35409
35457
  /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { flex: 1, display: "flex", alignItems: "center", gap: 1 }, children: [
@@ -35433,23 +35481,11 @@ function EditModal({
35433
35481
  ] });
35434
35482
  }, [isGlobal, segmentIndex, segment, onPlaySegment, handlePlayButtonClick, isPlaying, onClose, isLoading]);
35435
35483
  if (!isLoading && (!segment || !editedSegment || !originalSegment)) {
35436
- console.log("EditModal - Early return: missing required data", {
35437
- hasSegment: !!segment,
35438
- hasEditedSegment: !!editedSegment,
35439
- hasOriginalSegment: !!originalSegment,
35440
- isLoading
35441
- });
35442
35484
  return null;
35443
35485
  }
35444
35486
  if (!isLoading && !isGlobal && segmentIndex === null) {
35445
- console.log("EditModal - Early return: non-global mode with null segmentIndex");
35446
35487
  return null;
35447
35488
  }
35448
- console.log("EditModal - Rendering dialog content", {
35449
- isLoading,
35450
- hasEditedSegment: !!editedSegment,
35451
- hasOriginalSegment: !!originalSegment
35452
- });
35453
35489
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
35454
35490
  Dialog,
35455
35491
  {
@@ -35674,9 +35710,6 @@ const PauseIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
35674
35710
  const PlayArrowIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
35675
35711
  d: "M8 5v14l11-7z"
35676
35712
  }), "PlayArrow");
35677
- const TextSnippetIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
35678
- d: "m20.41 8.41-4.83-4.83c-.37-.37-.88-.58-1.41-.58H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V9.83c0-.53-.21-1.04-.59-1.42M7 7h7v2H7zm10 10H7v-2h10zm0-4H7v-2h10z"
35679
- }), "TextSnippet");
35680
35713
  const normalizeWordForComparison = (word) => ({
35681
35714
  text: word.text,
35682
35715
  start_time: word.start_time ?? 0,
@@ -36135,6 +36168,30 @@ function findAndReplace(data, findText, replaceText, options = {
36135
36168
  newData.corrected_segments = newData.corrected_segments.filter((segment) => segment.words.length > 0);
36136
36169
  return newData;
36137
36170
  }
36171
+ function deleteWord(data, wordId) {
36172
+ const segmentIndex = data.corrected_segments.findIndex(
36173
+ (segment2) => segment2.words.some((word) => word.id === wordId)
36174
+ );
36175
+ if (segmentIndex === -1) {
36176
+ return data;
36177
+ }
36178
+ const segment = data.corrected_segments[segmentIndex];
36179
+ const wordIndex = segment.words.findIndex((word) => word.id === wordId);
36180
+ if (wordIndex === -1) {
36181
+ return data;
36182
+ }
36183
+ const updatedWords = segment.words.filter((_, index) => index !== wordIndex);
36184
+ if (updatedWords.length > 0) {
36185
+ const updatedSegment = {
36186
+ ...segment,
36187
+ words: updatedWords,
36188
+ text: updatedWords.map((w) => w.text).join(" ")
36189
+ };
36190
+ return updateSegment(data, segmentIndex, updatedSegment);
36191
+ } else {
36192
+ return deleteSegment(data, segmentIndex);
36193
+ }
36194
+ }
36138
36195
  const generateStorageKey = (data) => {
36139
36196
  var _a;
36140
36197
  const text = ((_a = data.original_segments[0]) == null ? void 0 : _a.text) || "";
@@ -36208,6 +36265,12 @@ const setModalHandler = (handler, open) => {
36208
36265
  };
36209
36266
  const setupKeyboardHandlers = (state) => {
36210
36267
  Math.random().toString(36).substr(2, 9);
36268
+ const resetModifierStates = () => {
36269
+ var _a;
36270
+ state.setIsShiftPressed(false);
36271
+ (_a = state.setIsCtrlPressed) == null ? void 0 : _a.call(state, false);
36272
+ document.body.style.userSelect = "";
36273
+ };
36211
36274
  const handleKeyDown = (e) => {
36212
36275
  var _a;
36213
36276
  if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
@@ -36216,7 +36279,7 @@ const setupKeyboardHandlers = (state) => {
36216
36279
  if (e.key === "Shift") {
36217
36280
  state.setIsShiftPressed(true);
36218
36281
  document.body.style.userSelect = "none";
36219
- } else if (e.key === "Meta") {
36282
+ } else if (e.key === "Control" || e.key === "Ctrl" || e.key === "Meta") {
36220
36283
  (_a = state.setIsCtrlPressed) == null ? void 0 : _a.call(state, true);
36221
36284
  } else if (e.key === " " || e.code === "Space") {
36222
36285
  e.preventDefault();
@@ -36228,20 +36291,30 @@ const setupKeyboardHandlers = (state) => {
36228
36291
  }
36229
36292
  };
36230
36293
  const handleKeyUp = (e) => {
36231
- var _a;
36232
- if (e.key === "Shift") {
36233
- state.setIsShiftPressed(false);
36234
- document.body.style.userSelect = "";
36235
- } else if (e.key === "Meta") {
36236
- (_a = state.setIsCtrlPressed) == null ? void 0 : _a.call(state, false);
36237
- } else if (e.key === " " || e.code === "Space") {
36294
+ resetModifierStates();
36295
+ if (e.key === " " || e.code === "Space") {
36238
36296
  e.preventDefault();
36239
36297
  if (isModalOpen && currentModalHandler) {
36240
36298
  currentModalHandler(e);
36241
36299
  }
36242
36300
  }
36243
36301
  };
36244
- return { handleKeyDown, handleKeyUp };
36302
+ const handleWindowBlur = () => {
36303
+ resetModifierStates();
36304
+ };
36305
+ const handleWindowFocus = () => {
36306
+ resetModifierStates();
36307
+ };
36308
+ window.addEventListener("blur", handleWindowBlur);
36309
+ window.addEventListener("focus", handleWindowFocus);
36310
+ return {
36311
+ handleKeyDown,
36312
+ handleKeyUp,
36313
+ cleanup: () => {
36314
+ window.removeEventListener("blur", handleWindowBlur);
36315
+ window.removeEventListener("focus", handleWindowFocus);
36316
+ }
36317
+ };
36245
36318
  };
36246
36319
  function ModeSelector({ effectiveMode, onChange }) {
36247
36320
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1.2, height: "32px" }, children: [
@@ -36251,7 +36324,7 @@ function ModeSelector({ effectiveMode, onChange }) {
36251
36324
  {
36252
36325
  value: effectiveMode,
36253
36326
  exclusive: true,
36254
- onChange: (_, newMode) => newMode && onChange(newMode),
36327
+ onChange: (_, newMode) => newMode === "edit" && onChange(newMode),
36255
36328
  size: "small",
36256
36329
  sx: {
36257
36330
  height: "32px",
@@ -36262,28 +36335,38 @@ function ModeSelector({ effectiveMode, onChange }) {
36262
36335
  }
36263
36336
  },
36264
36337
  children: [
36265
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
36338
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Default mode; click words to edit that lyrics segment", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
36266
36339
  ToggleButton,
36267
36340
  {
36268
36341
  value: "edit",
36269
- title: "Click to edit segments and make corrections in the transcription view",
36270
36342
  children: [
36271
36343
  /* @__PURE__ */ jsxRuntimeExports.jsx(EditIcon, { sx: { mr: 0.5, fontSize: "1rem" } }),
36272
36344
  "Edit"
36273
36345
  ]
36274
36346
  }
36275
- ),
36276
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
36347
+ ) }),
36348
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Hold SHIFT and click words to highlight the matching anchor sequence in the reference lyrics", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
36277
36349
  ToggleButton,
36278
36350
  {
36279
36351
  value: "highlight",
36280
- title: "Click words in the transcription view to highlight the matching anchor sequence in the reference lyrics. You can also hold SHIFT to temporarily activate this mode.",
36352
+ disabled: true,
36281
36353
  children: [
36282
36354
  /* @__PURE__ */ jsxRuntimeExports.jsx(HighlightIcon, { sx: { mr: 0.5, fontSize: "1rem" } }),
36283
36355
  "Highlight"
36284
36356
  ]
36285
36357
  }
36286
- )
36358
+ ) }) }),
36359
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Hold CTRL and click words to delete them", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
36360
+ ToggleButton,
36361
+ {
36362
+ value: "delete_word",
36363
+ disabled: true,
36364
+ children: [
36365
+ /* @__PURE__ */ jsxRuntimeExports.jsx(DeleteIcon, { sx: { mr: 0.5, fontSize: "1rem" } }),
36366
+ "Delete"
36367
+ ]
36368
+ }
36369
+ ) }) })
36287
36370
  ]
36288
36371
  }
36289
36372
  )
@@ -36435,7 +36518,6 @@ function Header({
36435
36518
  onHandlerToggle,
36436
36519
  isUpdatingHandlers,
36437
36520
  onHandlerClick,
36438
- onAddLyrics,
36439
36521
  onFindReplace,
36440
36522
  onEditAll
36441
36523
  }) {
@@ -36581,71 +36663,58 @@ function Header({
36581
36663
  }
36582
36664
  ) })
36583
36665
  ] }),
36584
- /* @__PURE__ */ jsxRuntimeExports.jsx(Paper, { sx: { p: 0.8, mb: 1 }, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: {
36666
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Paper, { sx: { p: 0.8, mb: 1 }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(Box, { sx: {
36585
36667
  display: "flex",
36586
36668
  flexDirection: isMobile ? "column" : "row",
36587
36669
  gap: 1,
36588
36670
  alignItems: isMobile ? "flex-start" : "center",
36589
36671
  justifyContent: "space-between",
36590
36672
  width: "100%"
36673
+ }, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: {
36674
+ display: "flex",
36675
+ gap: 1,
36676
+ flexDirection: isMobile ? "column" : "row",
36677
+ alignItems: isMobile ? "flex-start" : "center",
36678
+ height: "32px"
36591
36679
  }, children: [
36592
- /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: {
36593
- display: "flex",
36594
- gap: 1,
36595
- flexDirection: isMobile ? "column" : "row",
36596
- alignItems: isMobile ? "flex-start" : "center",
36597
- height: "32px"
36598
- }, children: [
36599
- /* @__PURE__ */ jsxRuntimeExports.jsx(
36600
- ModeSelector,
36601
- {
36602
- effectiveMode,
36603
- onChange: onModeChange
36604
- }
36605
- ),
36606
- !isReadOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
36607
- Button,
36608
- {
36609
- variant: "outlined",
36610
- size: "small",
36611
- onClick: onFindReplace,
36612
- startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(FindReplaceIcon, {}),
36613
- sx: { minWidth: "fit-content", height: "32px" },
36614
- children: "Find/Replace"
36615
- }
36616
- ),
36617
- !isReadOnly && onEditAll && /* @__PURE__ */ jsxRuntimeExports.jsx(
36618
- Button,
36619
- {
36620
- variant: "outlined",
36621
- size: "small",
36622
- onClick: onEditAll,
36623
- startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(EditIcon, {}),
36624
- sx: { minWidth: "fit-content", height: "32px" },
36625
- children: "Edit All"
36626
- }
36627
- ),
36628
- /* @__PURE__ */ jsxRuntimeExports.jsx(
36629
- AudioPlayer,
36630
- {
36631
- apiClient,
36632
- onTimeUpdate,
36633
- audioHash
36634
- }
36635
- )
36636
- ] }),
36637
- !isReadOnly && apiClient && onAddLyrics && /* @__PURE__ */ jsxRuntimeExports.jsx(
36680
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
36681
+ ModeSelector,
36682
+ {
36683
+ effectiveMode,
36684
+ onChange: onModeChange
36685
+ }
36686
+ ),
36687
+ !isReadOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
36688
+ Button,
36689
+ {
36690
+ variant: "outlined",
36691
+ size: "small",
36692
+ onClick: onFindReplace,
36693
+ startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(FindReplaceIcon, {}),
36694
+ sx: { minWidth: "fit-content", height: "32px" },
36695
+ children: "Find/Replace"
36696
+ }
36697
+ ),
36698
+ !isReadOnly && onEditAll && /* @__PURE__ */ jsxRuntimeExports.jsx(
36638
36699
  Button,
36639
36700
  {
36640
36701
  variant: "outlined",
36641
36702
  size: "small",
36642
- onClick: onAddLyrics,
36643
- startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(TextSnippetIcon, {}),
36703
+ onClick: onEditAll,
36704
+ startIcon: /* @__PURE__ */ jsxRuntimeExports.jsx(EditIcon, {}),
36644
36705
  sx: { minWidth: "fit-content", height: "32px" },
36645
- children: "Add Reference Lyrics"
36706
+ children: "Edit All"
36707
+ }
36708
+ ),
36709
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
36710
+ AudioPlayer,
36711
+ {
36712
+ apiClient,
36713
+ onTimeUpdate,
36714
+ audioHash
36646
36715
  }
36647
36716
  )
36648
- ] }) })
36717
+ ] }) }) })
36649
36718
  ] });
36650
36719
  }
36651
36720
  function AddLyricsModal({
@@ -37119,7 +37188,8 @@ const MemoizedReferenceView = reactExports.memo(function MemoizedReferenceView2(
37119
37188
  currentSource,
37120
37189
  onSourceChange,
37121
37190
  corrected_segments,
37122
- corrections
37191
+ corrections,
37192
+ onAddLyrics
37123
37193
  }) {
37124
37194
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
37125
37195
  ReferenceView,
@@ -37135,7 +37205,8 @@ const MemoizedReferenceView = reactExports.memo(function MemoizedReferenceView2(
37135
37205
  currentSource,
37136
37206
  onSourceChange,
37137
37207
  corrected_segments,
37138
- corrections
37208
+ corrections,
37209
+ onAddLyrics
37139
37210
  }
37140
37211
  );
37141
37212
  });
@@ -37152,7 +37223,6 @@ const MemoizedHeader = reactExports.memo(function MemoizedHeader2({
37152
37223
  onHandlerToggle,
37153
37224
  isUpdatingHandlers,
37154
37225
  onHandlerClick,
37155
- onAddLyrics,
37156
37226
  onFindReplace,
37157
37227
  onEditAll
37158
37228
  }) {
@@ -37171,7 +37241,6 @@ const MemoizedHeader = reactExports.memo(function MemoizedHeader2({
37171
37241
  onHandlerToggle,
37172
37242
  isUpdatingHandlers,
37173
37243
  onHandlerClick,
37174
- onAddLyrics,
37175
37244
  onFindReplace,
37176
37245
  onEditAll
37177
37246
  }
@@ -37193,6 +37262,7 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
37193
37262
  const [originalData] = reactExports.useState(() => JSON.parse(JSON.stringify(initialData)));
37194
37263
  const [interactionMode, setInteractionMode] = reactExports.useState("edit");
37195
37264
  const [isShiftPressed, setIsShiftPressed] = reactExports.useState(false);
37265
+ const [isCtrlPressed, setIsCtrlPressed] = reactExports.useState(false);
37196
37266
  const [editModalSegment, setEditModalSegment] = reactExports.useState(null);
37197
37267
  const [isEditAllModalOpen, setIsEditAllModalOpen] = reactExports.useState(false);
37198
37268
  const [globalEditSegment, setGlobalEditSegment] = reactExports.useState(null);
@@ -37223,27 +37293,30 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
37223
37293
  }
37224
37294
  }, [data, isReadOnly, initialData]);
37225
37295
  reactExports.useEffect(() => {
37226
- const { handleKeyDown, handleKeyUp } = setupKeyboardHandlers({
37227
- setIsShiftPressed
37296
+ const { handleKeyDown, handleKeyUp, cleanup } = setupKeyboardHandlers({
37297
+ setIsShiftPressed,
37298
+ setIsCtrlPressed
37228
37299
  });
37229
37300
  window.addEventListener("keydown", handleKeyDown);
37230
37301
  window.addEventListener("keyup", handleKeyUp);
37231
37302
  if (isAnyModalOpen) {
37232
37303
  setIsShiftPressed(false);
37304
+ setIsCtrlPressed(false);
37233
37305
  }
37234
37306
  return () => {
37235
37307
  window.removeEventListener("keydown", handleKeyDown);
37236
37308
  window.removeEventListener("keyup", handleKeyUp);
37237
37309
  document.body.style.userSelect = "";
37310
+ cleanup();
37238
37311
  };
37239
- }, [setIsShiftPressed, isAnyModalOpen]);
37312
+ }, [setIsShiftPressed, setIsCtrlPressed, isAnyModalOpen]);
37240
37313
  reactExports.useEffect(() => {
37241
37314
  const modalOpen = Boolean(
37242
37315
  modalContent || editModalSegment || isReviewModalOpen || isAddLyricsModalOpen || isFindReplaceModalOpen || isEditAllModalOpen
37243
37316
  );
37244
37317
  setIsAnyModalOpen(modalOpen);
37245
37318
  }, [modalContent, editModalSegment, isReviewModalOpen, isAddLyricsModalOpen, isFindReplaceModalOpen, isEditAllModalOpen]);
37246
- const effectiveMode = isShiftPressed ? "highlight" : interactionMode;
37319
+ const effectiveMode = isCtrlPressed ? "delete_word" : isShiftPressed ? "highlight" : interactionMode;
37247
37320
  const handleFlash = reactExports.useCallback((type, info) => {
37248
37321
  setFlashingType(null);
37249
37322
  setHighlightInfo(null);
@@ -37262,6 +37335,12 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
37262
37335
  }, []);
37263
37336
  const handleWordClick = reactExports.useCallback((info) => {
37264
37337
  var _a, _b, _c, _d, _e, _f, _g;
37338
+ if (effectiveMode === "delete_word") {
37339
+ const newData = deleteWord(data, info.word_id);
37340
+ setData(newData);
37341
+ handleFlash("word");
37342
+ return;
37343
+ }
37265
37344
  if (effectiveMode === "highlight") {
37266
37345
  const correction = (_a = data.corrections) == null ? void 0 : _a.find(
37267
37346
  (c) => c.corrected_word_id === info.word_id || c.word_id === info.word_id
@@ -37369,7 +37448,7 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
37369
37448
  });
37370
37449
  }
37371
37450
  }
37372
- }, [data, effectiveMode, setModalContent]);
37451
+ }, [data, effectiveMode, setModalContent, handleFlash, deleteWord]);
37373
37452
  const handleUpdateSegment = reactExports.useCallback((updatedSegment) => {
37374
37453
  if (!editModalSegment) return;
37375
37454
  const newData = updateSegment(data, editModalSegment.index, updatedSegment);
@@ -37706,7 +37785,8 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
37706
37785
  currentSource,
37707
37786
  onSourceChange: setCurrentSource,
37708
37787
  corrected_segments: data.corrected_segments,
37709
- corrections: data.corrections
37788
+ corrections: data.corrections,
37789
+ onAddLyrics: () => setIsAddLyricsModalOpen(true)
37710
37790
  }
37711
37791
  ) })
37712
37792
  ] }),
@@ -38141,4 +38221,4 @@ ReactDOM$1.createRoot(document.getElementById("root")).render(
38141
38221
  /* @__PURE__ */ jsxRuntimeExports.jsx(App, {})
38142
38222
  ] })
38143
38223
  );
38144
- //# sourceMappingURL=index-ZCT0s9MG.js.map
38224
+ //# sourceMappingURL=index-2vK-qVJS.js.map