lyrics-transcriber 0.48.0__py3-none-any.whl → 0.49.1__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.
@@ -35962,6 +35962,12 @@ const PauseIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
35962
35962
  const PlayArrowIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
35963
35963
  d: "M8 5v14l11-7z"
35964
35964
  }), "PlayArrow");
35965
+ const RedoIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
35966
+ d: "M18.4 10.6C16.55 8.99 14.15 8 11.5 8c-4.65 0-8.58 3.03-9.96 7.22L3.9 16c1.05-3.19 4.05-5.5 7.6-5.5 1.95 0 3.73.72 5.12 1.88L13 16h9V7z"
35967
+ }), "Redo");
35968
+ const UndoIcon = createSvgIcon(/* @__PURE__ */ jsxRuntimeExports.jsx("path", {
35969
+ d: "M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8"
35970
+ }), "Undo");
35965
35971
  const normalizeWordForComparison = (word) => ({
35966
35972
  text: word.text,
35967
35973
  start_time: word.start_time ?? 0,
@@ -35995,13 +36001,15 @@ function ReviewChangesModal({
35995
36001
  reactExports.useEffect(() => {
35996
36002
  if (open) {
35997
36003
  setModalSpacebarHandler(() => (e) => {
35998
- e.preventDefault();
35999
- e.stopPropagation();
36000
- if (videoRef.current) {
36001
- if (videoRef.current.paused) {
36002
- videoRef.current.play();
36003
- } else {
36004
- videoRef.current.pause();
36004
+ if (e.type === "keydown") {
36005
+ e.preventDefault();
36006
+ e.stopPropagation();
36007
+ if (videoRef.current) {
36008
+ if (videoRef.current.paused) {
36009
+ videoRef.current.play();
36010
+ } else {
36011
+ videoRef.current.pause();
36012
+ }
36005
36013
  }
36006
36014
  }
36007
36015
  });
@@ -36546,7 +36554,11 @@ function Header({
36546
36554
  isUpdatingHandlers,
36547
36555
  onHandlerClick,
36548
36556
  onFindReplace,
36549
- onEditAll
36557
+ onEditAll,
36558
+ onUndo,
36559
+ onRedo,
36560
+ canUndo,
36561
+ canRedo
36550
36562
  }) {
36551
36563
  var _a, _b, _c;
36552
36564
  const theme2 = useTheme();
@@ -36711,6 +36723,40 @@ function Header({
36711
36723
  onChange: onModeChange
36712
36724
  }
36713
36725
  ),
36726
+ !isReadOnly && /* @__PURE__ */ jsxRuntimeExports.jsxs(Box, { sx: { display: "flex", height: "32px" }, children: [
36727
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Undo", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: /* @__PURE__ */ jsxRuntimeExports.jsx(
36728
+ IconButton,
36729
+ {
36730
+ size: "small",
36731
+ onClick: onUndo,
36732
+ disabled: !canUndo,
36733
+ sx: {
36734
+ border: `1px solid ${theme2.palette.divider}`,
36735
+ borderRadius: "4px",
36736
+ mx: 0.25,
36737
+ height: "32px",
36738
+ width: "32px"
36739
+ },
36740
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(UndoIcon, { fontSize: "small" })
36741
+ }
36742
+ ) }) }),
36743
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Tooltip, { title: "Redo", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: /* @__PURE__ */ jsxRuntimeExports.jsx(
36744
+ IconButton,
36745
+ {
36746
+ size: "small",
36747
+ onClick: onRedo,
36748
+ disabled: !canRedo,
36749
+ sx: {
36750
+ border: `1px solid ${theme2.palette.divider}`,
36751
+ borderRadius: "4px",
36752
+ mx: 0.25,
36753
+ height: "32px",
36754
+ width: "32px"
36755
+ },
36756
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(RedoIcon, { fontSize: "small" })
36757
+ }
36758
+ ) }) })
36759
+ ] }),
36714
36760
  !isReadOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
36715
36761
  Button,
36716
36762
  {
@@ -37253,7 +37299,11 @@ const MemoizedHeader = reactExports.memo(function MemoizedHeader2({
37253
37299
  isUpdatingHandlers,
37254
37300
  onHandlerClick,
37255
37301
  onFindReplace,
37256
- onEditAll
37302
+ onEditAll,
37303
+ onUndo,
37304
+ onRedo,
37305
+ canUndo,
37306
+ canRedo
37257
37307
  }) {
37258
37308
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
37259
37309
  Header,
@@ -37271,7 +37321,11 @@ const MemoizedHeader = reactExports.memo(function MemoizedHeader2({
37271
37321
  isUpdatingHandlers,
37272
37322
  onHandlerClick,
37273
37323
  onFindReplace,
37274
- onEditAll
37324
+ onEditAll,
37325
+ onUndo,
37326
+ onRedo,
37327
+ canUndo,
37328
+ canRedo
37275
37329
  }
37276
37330
  );
37277
37331
  });
@@ -37287,7 +37341,6 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
37287
37341
  return availableSources.length > 0 ? availableSources[0] : "";
37288
37342
  });
37289
37343
  const [isReviewComplete, setIsReviewComplete] = reactExports.useState(false);
37290
- const [data, setData] = reactExports.useState(initialData);
37291
37344
  const [originalData] = reactExports.useState(() => JSON.parse(JSON.stringify(initialData)));
37292
37345
  const [interactionMode, setInteractionMode] = reactExports.useState("edit");
37293
37346
  const [isShiftPressed, setIsShiftPressed] = reactExports.useState(false);
@@ -37308,12 +37361,27 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
37308
37361
  const [isFindReplaceModalOpen, setIsFindReplaceModalOpen] = reactExports.useState(false);
37309
37362
  const theme2 = useTheme();
37310
37363
  const isMobile = useMediaQuery(theme2.breakpoints.down("md"));
37364
+ const [history, setHistory] = reactExports.useState([initialData]);
37365
+ const [historyIndex, setHistoryIndex] = reactExports.useState(0);
37366
+ const data = history[historyIndex];
37367
+ const updateDataWithHistory = reactExports.useCallback((newData, actionDescription) => {
37368
+ const newHistory = history.slice(0, historyIndex + 1);
37369
+ const deepCopiedNewData = JSON.parse(JSON.stringify(newData));
37370
+ newHistory.push(deepCopiedNewData);
37371
+ setHistory(newHistory);
37372
+ setHistoryIndex(newHistory.length - 1);
37373
+ }, [history, historyIndex]);
37374
+ reactExports.useEffect(() => {
37375
+ setHistory([initialData]);
37376
+ setHistoryIndex(0);
37377
+ }, [initialData]);
37311
37378
  reactExports.useEffect(() => {
37312
37379
  }, [initialData]);
37313
37380
  reactExports.useEffect(() => {
37314
37381
  const savedData = loadSavedData(initialData);
37315
37382
  if (savedData && window.confirm("Found saved progress for this song. Would you like to restore it?")) {
37316
- setData(savedData);
37383
+ setHistory([savedData]);
37384
+ setHistoryIndex(0);
37317
37385
  }
37318
37386
  }, [initialData]);
37319
37387
  reactExports.useEffect(() => {
@@ -37366,7 +37434,7 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
37366
37434
  var _a, _b, _c, _d, _e, _f, _g;
37367
37435
  if (effectiveMode === "delete_word") {
37368
37436
  const newData = deleteWord(data, info.word_id);
37369
- setData(newData);
37437
+ updateDataWithHistory(newData, "delete word");
37370
37438
  handleFlash("word");
37371
37439
  return;
37372
37440
  }
@@ -37477,17 +37545,24 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
37477
37545
  });
37478
37546
  }
37479
37547
  }
37480
- }, [data, effectiveMode, setModalContent, handleFlash, deleteWord]);
37548
+ }, [data, effectiveMode, setModalContent, handleFlash, deleteWord, updateDataWithHistory]);
37481
37549
  const handleUpdateSegment = reactExports.useCallback((updatedSegment) => {
37482
37550
  if (!editModalSegment) return;
37483
- const newData = updateSegment(data, editModalSegment.index, updatedSegment);
37484
- setData(newData);
37551
+ const currentData = history[historyIndex];
37552
+ const newSegments = currentData.corrected_segments.map(
37553
+ (segment, i) => i === editModalSegment.index ? updatedSegment : segment
37554
+ );
37555
+ const newDataImmutable = {
37556
+ ...currentData,
37557
+ corrected_segments: newSegments
37558
+ };
37559
+ updateDataWithHistory(newDataImmutable, "update segment");
37485
37560
  setEditModalSegment(null);
37486
- }, [data, editModalSegment]);
37561
+ }, [history, historyIndex, editModalSegment, updateDataWithHistory]);
37487
37562
  const handleDeleteSegment = reactExports.useCallback((segmentIndex) => {
37488
37563
  const newData = deleteSegment(data, segmentIndex);
37489
- setData(newData);
37490
- }, [data]);
37564
+ updateDataWithHistory(newData, "delete segment");
37565
+ }, [data, updateDataWithHistory]);
37491
37566
  const handleFinishReview = reactExports.useCallback(() => {
37492
37567
  setIsReviewModalOpen(true);
37493
37568
  }, []);
@@ -37512,7 +37587,8 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
37512
37587
  const handleResetCorrections = reactExports.useCallback(() => {
37513
37588
  if (window.confirm("Are you sure you want to reset all corrections? This cannot be undone.")) {
37514
37589
  clearSavedData(initialData);
37515
- setData(JSON.parse(JSON.stringify(initialData)));
37590
+ setHistory([JSON.parse(JSON.stringify(initialData))]);
37591
+ setHistoryIndex(0);
37516
37592
  setModalContent(null);
37517
37593
  setFlashingType(null);
37518
37594
  setHighlightInfo(null);
@@ -37521,20 +37597,20 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
37521
37597
  }, [initialData]);
37522
37598
  const handleAddSegment = reactExports.useCallback((beforeIndex) => {
37523
37599
  const newData = addSegmentBefore(data, beforeIndex);
37524
- setData(newData);
37525
- }, [data]);
37600
+ updateDataWithHistory(newData, "add segment");
37601
+ }, [data, updateDataWithHistory]);
37526
37602
  const handleSplitSegment = reactExports.useCallback((segmentIndex, afterWordIndex) => {
37527
37603
  const newData = splitSegment(data, segmentIndex, afterWordIndex);
37528
37604
  if (newData) {
37529
- setData(newData);
37605
+ updateDataWithHistory(newData, "split segment");
37530
37606
  setEditModalSegment(null);
37531
37607
  }
37532
- }, [data]);
37608
+ }, [data, updateDataWithHistory]);
37533
37609
  const handleMergeSegment = reactExports.useCallback((segmentIndex, mergeWithNext) => {
37534
37610
  const newData = mergeSegment(data, segmentIndex, mergeWithNext);
37535
- setData(newData);
37611
+ updateDataWithHistory(newData, "merge segment");
37536
37612
  setEditModalSegment(null);
37537
- }, [data]);
37613
+ }, [data, updateDataWithHistory]);
37538
37614
  const handleHandlerToggle = reactExports.useCallback(async (handler, enabled) => {
37539
37615
  if (!apiClient) return;
37540
37616
  try {
@@ -37546,7 +37622,7 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
37546
37622
  currentEnabled.delete(handler);
37547
37623
  }
37548
37624
  const newData = await apiClient.updateHandlers(Array.from(currentEnabled));
37549
- setData(newData);
37625
+ updateDataWithHistory(newData, `toggle handler ${handler}`);
37550
37626
  setModalContent(null);
37551
37627
  setFlashingType(null);
37552
37628
  setHighlightInfo(null);
@@ -37557,7 +37633,7 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
37557
37633
  } finally {
37558
37634
  setIsUpdatingHandlers(false);
37559
37635
  }
37560
- }, [apiClient, data.metadata.enabled_handlers, handleFlash]);
37636
+ }, [apiClient, data.metadata.enabled_handlers, handleFlash, updateDataWithHistory]);
37561
37637
  const handleHandlerClick = reactExports.useCallback((handler) => {
37562
37638
  setFlashingHandler(handler);
37563
37639
  setFlashingType("handler");
@@ -37574,14 +37650,14 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
37574
37650
  try {
37575
37651
  setIsAddingLyrics(true);
37576
37652
  const newData = await apiClient.addLyrics(source, lyrics);
37577
- setData(newData);
37653
+ updateDataWithHistory(newData, "add lyrics");
37578
37654
  } finally {
37579
37655
  setIsAddingLyrics(false);
37580
37656
  }
37581
- }, [apiClient]);
37657
+ }, [apiClient, updateDataWithHistory]);
37582
37658
  const handleFindReplace = (findText, replaceText, options) => {
37583
37659
  const newData = findAndReplace(data, findText, replaceText, options);
37584
- setData(newData);
37660
+ updateDataWithHistory(newData, "find/replace");
37585
37661
  };
37586
37662
  const handleEditAll = reactExports.useCallback(() => {
37587
37663
  console.log("EditAll - Starting process");
@@ -37714,13 +37790,28 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
37714
37790
  totalWordCount: updatedSegments.reduce((count, segment) => count + segment.words.length, 0),
37715
37791
  originalTotalWordCount: data.corrected_segments.reduce((count, segment) => count + segment.words.length, 0)
37716
37792
  });
37717
- setData({
37793
+ const newData = {
37718
37794
  ...data,
37719
37795
  corrected_segments: updatedSegments
37720
- });
37796
+ };
37797
+ updateDataWithHistory(newData, "edit all");
37721
37798
  setIsEditAllModalOpen(false);
37722
37799
  setGlobalEditSegment(null);
37723
- }, [data]);
37800
+ }, [data, updateDataWithHistory]);
37801
+ const handleUndo = reactExports.useCallback(() => {
37802
+ if (historyIndex > 0) {
37803
+ const newIndex = historyIndex - 1;
37804
+ setHistoryIndex(newIndex);
37805
+ }
37806
+ }, [historyIndex, history]);
37807
+ const handleRedo = reactExports.useCallback(() => {
37808
+ if (historyIndex < history.length - 1) {
37809
+ const newIndex = historyIndex + 1;
37810
+ setHistoryIndex(newIndex);
37811
+ }
37812
+ }, [historyIndex, history]);
37813
+ const canUndo = historyIndex > 0;
37814
+ const canRedo = historyIndex < history.length - 1;
37724
37815
  const metricClickHandlers = reactExports.useMemo(() => ({
37725
37816
  anchor: () => handleFlash("anchor"),
37726
37817
  corrected: () => handleFlash("corrected"),
@@ -37750,7 +37841,11 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
37750
37841
  onHandlerClick: handleHandlerClick,
37751
37842
  onAddLyrics: () => setIsAddLyricsModalOpen(true),
37752
37843
  onFindReplace: () => setIsFindReplaceModalOpen(true),
37753
- onEditAll: handleEditAll
37844
+ onEditAll: handleEditAll,
37845
+ onUndo: handleUndo,
37846
+ onRedo: handleRedo,
37847
+ canUndo,
37848
+ canRedo
37754
37849
  }
37755
37850
  ),
37756
37851
  /* @__PURE__ */ jsxRuntimeExports.jsxs(Grid, { container: true, direction: isMobile ? "column" : "row", children: [
@@ -37770,7 +37865,7 @@ function LyricsAnalyzer({ data: initialData, onFileLoad, apiClient, isReadOnly,
37770
37865
  anchors: data.anchor_sequences,
37771
37866
  disableHighlighting: isAnyModalOpenMemo,
37772
37867
  onDataChange: (updatedData) => {
37773
- setData(updatedData);
37868
+ updateDataWithHistory(updatedData, "direct data change");
37774
37869
  }
37775
37870
  }
37776
37871
  ),
@@ -38253,4 +38348,4 @@ ReactDOM$1.createRoot(document.getElementById("root")).render(
38253
38348
  /* @__PURE__ */ jsxRuntimeExports.jsx(App, {})
38254
38349
  ] })
38255
38350
  );
38256
- //# sourceMappingURL=index-BvRLUQmZ.js.map
38351
+ //# sourceMappingURL=index-DSQidWB1.js.map