diff --git a/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj b/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj index a7d3d6c0..d02d940a 100644 --- a/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj +++ b/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj @@ -24,8 +24,8 @@ - - + + diff --git a/AudioCuesheetEditor.End2EndTests/Models/AppBar.cs b/AudioCuesheetEditor.End2EndTests/Models/AppBar.cs index 19076a17..7495aadd 100644 --- a/AudioCuesheetEditor.End2EndTests/Models/AppBar.cs +++ b/AudioCuesheetEditor.End2EndTests/Models/AppBar.cs @@ -75,5 +75,13 @@ internal async Task OpenDisplayHotkeysAsync() await MenuButton.ClickAsync(); await _page.GetByText("Hotkeys").ClickAsync(); } + + internal async Task ResetCuesheetAsync() + { + await MenuButton.ClickAsync(); + await _page.GetByRole(AriaRole.Menuitem, new() { Name = "Reset" }).ClickAsync(); + await _page.GetByText("Reset cuesheet").ClickAsync(); + await _page.GetByRole(AriaRole.Button, new() { Name = "Yes" }).ClickAsync(); + } } } diff --git a/AudioCuesheetEditor.End2EndTests/Models/DetailView.cs b/AudioCuesheetEditor.End2EndTests/Models/DetailView.cs index 6a9cc1dc..4199c03c 100644 --- a/AudioCuesheetEditor.End2EndTests/Models/DetailView.cs +++ b/AudioCuesheetEditor.End2EndTests/Models/DetailView.cs @@ -44,7 +44,7 @@ internal async Task AddAudiofileAsync() internal async Task SetAudiofileInputFileAsync(int audiofileIndex, string file) { - await _page.GetByRole(AriaRole.Group).Filter(new() { HasText = "AudiofileAudiofile" }).Nth(audiofileIndex).Locator("input[type=\"file\"]").SetInputFilesAsync(file); + await _page.GetByRole(AriaRole.Group).Filter(new() { HasText = "No file uploaded!" }).Nth(audiofileIndex).Locator("input[type=\"file\"]").SetInputFilesAsync(file); } internal async Task AddTrackAsync(int audiofileIndex) @@ -116,8 +116,7 @@ internal async Task RenameAudiofileAsync(int audiofileIndex, string filename) internal async Task OpenRenameAudiofileDialogAsync(int audiofileIndex) { - await _page.GetByRole(AriaRole.Group).Filter(new() { HasText = "AudiofileAudiofile" }).Nth(audiofileIndex).GetByLabel("More").ClickAsync(); - await _page.GetByText("Rename file").ClickAsync(); + await _page.GetByRole(AriaRole.Button, new() { Name = "Rename file" }).Nth(audiofileIndex).ClickAsync(); } async Task EditTrackFieldAsync(int trackPosition, string dataLabel, string value) diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/BasicTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/BasicTest.cs index a8b43579..874a59b2 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/BasicTest.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/BasicTest.cs @@ -48,7 +48,7 @@ public async Task Audiofile_ShouldBeRenamed_WhenEditingFilename() await detailView.AddAudiofileAsync(); await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3"); await detailView.RenameAudiofileAsync(0, "Kalimba test 123.mp3"); - await Expect(TestPage.GetByRole(AriaRole.Textbox, new() { Name = "Audiofile" })).ToMatchAriaSnapshotAsync("- textbox \"Audiofile\": Kalimba test 123.mp3"); + await Expect(TestPage.GetByRole(AriaRole.Group).Filter(new() { HasText = "Kalimba test 123.mp3" })).ToMatchAriaSnapshotAsync("- group:\r\n - checkbox\r\n - heading \"Kalimba test 123.mp3\" [level=6]\r\n - button \"Upload file\"\r\n - button \"Rename file\"\r\n - button \"Clear file\""); } [TestMethod] @@ -61,6 +61,18 @@ public async Task OpenSettings_ShouldDisplaySettings_WhenSelectingSettings() await Expect(TestPage.GetByRole(AriaRole.Heading, new() { Name = "Settings" })).ToBeVisibleAsync(); } + [TestMethod] + public async Task ResetCuesheet_ShouldResetAllCuesheetReferences_WhenResettingCuesheetAsync() + { + var bar = new AppBar(TestPage); + var detailView = new DetailView(TestPage); + await detailView.GotoAsync(); + await detailView.AddAudiofileAsync(); + await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3"); + await bar.ResetCuesheetAsync(); + await Expect(TestPage.GetByRole(AriaRole.Tabpanel, new() { Name = "Detail view" })).ToMatchAriaSnapshotAsync("- group:\n - paragraph: \"--:--:--\"\n - slider \"Playback slider\" [disabled]: \"0\"\n - paragraph: \"--:--:--\"\n- group:\n - button \"Play\" [disabled]\n - group:\n - button \"Jump to previous track\" [disabled]\n - button \"Stop the current playback\" [disabled]\n - button \"Jump to next track\" [disabled]"); + } + [TestMethod] public async Task ChangeLanguage_ShouldSwitchLanguage_WhenGermanIsSelected() { @@ -193,7 +205,7 @@ public async Task KeyboardCommands_ShouldControlDialogs_WhenUsingEnterOrEscapeAs await detailView.NewFileNameInput.FillAsync("Test 123"); await TestPage.Keyboard.PressAsync("Enter"); await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached }); - await Expect(TestPage.GetByRole(AriaRole.Textbox, new() { Name = "Audiofile" })).ToMatchAriaSnapshotAsync("- textbox \"Audiofile\": Test 123.mp3"); + await Expect(TestPage.GetByRole(AriaRole.Group).Filter(new() { HasText = "Test 123.mp3" })).ToMatchAriaSnapshotAsync("- group:\r\n - checkbox\r\n - heading \"Test 123.mp3\" [level=6]\r\n - button \"Upload file\"\r\n - button \"Rename file\"\r\n - button \"Clear file\""); } } } diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs index 6e443a41..00dfb6ef 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs @@ -15,6 +15,7 @@ //. using AudioCuesheetEditor.End2EndTests.Models; using Microsoft.Playwright; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; namespace AudioCuesheetEditor.End2EndTests.Tests.Desktop { @@ -211,7 +212,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta - button - rowgroup: - row"); - await Expect(TestPage.GetByRole(AriaRole.Textbox, new() { Name = "Audiofile" })).ToHaveValueAsync(@"c:\AudioFile.mp3"); + await Expect(TestPage.GetByRole(AriaRole.Group).Filter(new() { HasText = "c:\\AudioFile.mp3" })).ToMatchAriaSnapshotAsync("- group:\r\n - checkbox\r\n - heading \"c:\\\\AudioFile.mp3\" [level=6]\r\n - button \"Upload file\"\r\n - button \"Rename file\"\r\n - button \"Clear file\""); await importView.GotoAsync(); await Expect(TestPage.GetByRole(AriaRole.Button, new() { Name = "Analyze" })).ToBeVisibleAsync(); } @@ -2431,7 +2432,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta - rowgroup: - row"); await appBar.UndoAsync(); - await Expect(TestPage.GetByRole(AriaRole.Paragraph).Filter(new() { HasText = "Audiofiles has invalid Count (0)!" })).ToBeVisibleAsync(); + await Expect(TestPage.GetByRole(AriaRole.Tabpanel, new() { Name = "Detail view" })).ToMatchAriaSnapshotAsync("- text: Audiofiles has invalid count (0)!"); } [TestMethod] @@ -2626,7 +2627,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta - rowgroup: - row"); await appBar.UndoAsync(); - await Expect(TestPage.GetByRole(AriaRole.Paragraph).Filter(new() { HasText = "Audiofiles has invalid Count (0)!" })).ToBeVisibleAsync(); + await Expect(TestPage.GetByRole(AriaRole.Tabpanel, new() { Name = "Detail view" })).ToMatchAriaSnapshotAsync("- text: Audiofiles has invalid count (0)!"); } [TestMethod] diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTest.cs index 212587fc..eb0bf1b6 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTest.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTest.cs @@ -50,7 +50,7 @@ public async Task Audiofile_ShouldBeRenamed_WhenEditingFilename() await detailView.AddAudiofileAsync(); await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3"); await detailView.RenameAudiofileAsync(0, "Kalimba test 123.mp3"); - await Expect(TestPage.GetByRole(AriaRole.Textbox, new() { Name = "Audiofile" })).ToMatchAriaSnapshotAsync("- textbox \"Audiofile\": Kalimba test 123.mp3"); + await Expect(TestPage.GetByRole(AriaRole.Group).Filter(new() { HasText = "Kalimba test 123.mp3" })).ToMatchAriaSnapshotAsync("- group:\r\n - checkbox\r\n - heading \"Kalimba test 123.mp3\" [level=6]\r\n - button \"Upload file\"\r\n - button \"Rename file\"\r\n - button \"Clear file\""); } [TestMethod] @@ -63,6 +63,18 @@ public async Task OpenSettings_ShouldDisplaySettings_WhenSelectingSettings() await Expect(TestPage.GetByRole(AriaRole.Heading, new() { Name = "Settings" })).ToBeVisibleAsync(); } + [TestMethod] + public async Task ResetCuesheet_ShouldResetAllCuesheetReferences_WhenResettingCuesheetAsync() + { + var bar = new AppBar(TestPage); + var detailView = new DetailView(TestPage); + await detailView.GotoAsync(); + await detailView.AddAudiofileAsync(); + await detailView.SetAudiofileInputFileAsync(0, "Kalimba.mp3"); + await bar.ResetCuesheetAsync(); + await Expect(TestPage.GetByRole(AriaRole.Tabpanel, new() { Name = "Detail view" })).ToMatchAriaSnapshotAsync("- group:\n - paragraph: \"--:--:--\"\n - slider \"Playback slider\" [disabled]: \"0\"\n - paragraph: \"--:--:--\"\n- group:\n - button \"Play\" [disabled]\n - group:\n - button \"Jump to previous track\" [disabled]\n - button \"Stop the current playback\" [disabled]\n - button \"Jump to next track\" [disabled]"); + } + [TestMethod] public async Task ChangeLanguage_ShouldSwitchLanguage_WhenGermanIsSelected() { @@ -192,7 +204,7 @@ public async Task KeyboardCommands_ShouldControlDialogs_WhenUsingEnterOrEscapeAs await detailView.NewFileNameInput.FillAsync("Test 123"); await TestPage.Keyboard.PressAsync("Enter"); await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached }); - await Expect(TestPage.GetByRole(AriaRole.Textbox, new() { Name = "Audiofile" })).ToMatchAriaSnapshotAsync("- textbox \"Audiofile\": Test 123.mp3"); + await Expect(TestPage.GetByRole(AriaRole.Group).Filter(new() { HasText = "Test 123.mp3" })).ToMatchAriaSnapshotAsync("- group:\r\n - checkbox\r\n - heading \"Test 123.mp3\" [level=6]\r\n - button \"Upload file\"\r\n - button \"Rename file\"\r\n - button \"Clear file\""); } } } diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTest.cs index f5ff4614..4bfc7b7a 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTest.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTest.cs @@ -248,7 +248,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta - cell ""Status"": - text: Status - button"); - await Expect(TestPage.GetByRole(AriaRole.Textbox, new() { Name = "Audiofile" })).ToHaveValueAsync(@"c:\AudioFile.mp3"); + await Expect(TestPage.GetByRole(AriaRole.Group).Filter(new() { HasText = "c:\\AudioFile.mp3" })).ToMatchAriaSnapshotAsync("- group:\r\n - checkbox\r\n - heading \"c:\\\\AudioFile.mp3\" [level=6]\r\n - button \"Upload file\"\r\n - button \"Rename file\"\r\n - button \"Clear file\""); await importView.GotoAsync(); await Expect(TestPage.GetByRole(AriaRole.Button, new() { Name = "Analyze" })).ToBeVisibleAsync(); } @@ -2984,7 +2984,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta - text: Status - button"); await appBar.UndoAsync(); - await Expect(TestPage.GetByRole(AriaRole.Paragraph).Filter(new() { HasText = "Audiofiles has invalid Count (0)!" })).ToBeVisibleAsync(); + await Expect(TestPage.GetByRole(AriaRole.Tabpanel, new() { Name = "Detail view" })).ToMatchAriaSnapshotAsync("- text: Audiofiles has invalid count (0)!"); } [TestMethod] @@ -3214,7 +3214,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta - text: Status - button"); await appBar.UndoAsync(); - await Expect(TestPage.GetByRole(AriaRole.Paragraph).Filter(new() { HasText = "Audiofiles has invalid Count (0)!" })).ToBeVisibleAsync(); + await Expect(TestPage.GetByRole(AriaRole.Tabpanel, new() { Name = "Detail view" })).ToMatchAriaSnapshotAsync("- text: Audiofiles has invalid count (0)!"); } [TestMethod] diff --git a/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj b/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj index 0714cf11..8fec56a1 100644 --- a/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj +++ b/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj @@ -19,12 +19,12 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/AudioCuesheetEditor.Tests/Services/AudioCuesheet/AudiofileManagerTests.cs b/AudioCuesheetEditor.Tests/Services/AudioCuesheet/AudiofileManagerTests.cs index 73565f58..00da412f 100644 --- a/AudioCuesheetEditor.Tests/Services/AudioCuesheet/AudiofileManagerTests.cs +++ b/AudioCuesheetEditor.Tests/Services/AudioCuesheet/AudiofileManagerTests.cs @@ -78,7 +78,7 @@ public AudiofileManagerTests() } [TestMethod] - public async Task SetPropertiesAsync_EmptyBrowserFile_ShouldClearPropertiesAsync() + public async Task ClearPropertiesAsync_ValidBrowserFile_ShouldClearPropertiesAsync() { // Arrange var audiofile = new Audiofile @@ -88,24 +88,26 @@ public async Task SetPropertiesAsync_EmptyBrowserFile_ShouldClearPropertiesAsync ObjectURL = "Just a test", Duration = TimeSpan.FromSeconds(120) }; - var audiofileChangedFired = false; + var audiofileChangedFired = 0; _audiofileManager.AudiofileChanged += delegate(object? sender, Audiofile e) { if (e == audiofile) { - audiofileChangedFired = true; + audiofileChangedFired++; } }; var expectedUrl = audiofile.ObjectURL; // Act - await _audiofileManager.SetPropertiesAsync(audiofile, null, string.Empty); + await _audiofileManager.ClearPropertiesAsync(audiofile); // Assert Assert.IsNull(audiofile.AudioCodec); Assert.IsNull(audiofile.Name); Assert.IsNull(audiofile.ObjectURL); Assert.IsNull(audiofile.Duration); - Assert.IsTrue(audiofileChangedFired); + Assert.AreEqual(1, audiofileChangedFired); _jsRuntime.Verify(js => js.InvokeAsync("revokeAudioObjectURL", It.Is(args => args != null && args.Length > 0 && (args[0] as string) == expectedUrl)), Times.Once); + _traceChangeManager.VerifySet(t => t.BulkEdit = true, Times.Once); + _traceChangeManager.VerifySet(t => t.BulkEdit = false, Times.Once); } [TestMethod] @@ -114,36 +116,36 @@ public async Task SetPropertiesAsync_WithBrowserFile_ShouldSetPropertiesAsync() // Arrange var objectUrl = "blob:123"; var filename = "song.mp3"; - var inputId = "inputId"; var browserFile = new Mock(); browserFile.SetupGet(b => b.ContentType).Returns("audio/mpeg"); browserFile.SetupGet(b => b.Name).Returns(filename); var codec = Audiofile.AudioCodecs.First(x => x.FileExtension == ".mp3"); - _fileInputManager.Setup(f => f.GetAudioCodec("audio/mpeg", filename)).Returns(codec); - _fileInputManager.Setup(f => f.GetObjectUrlAsync(inputId)).ReturnsAsync(objectUrl); + _fileInputManager.Setup(f => f.GetAudioCodec(filename, "audio/mpeg")).Returns(codec); + _fileInputManager.Setup(f => f.GetObjectUrlAsync(browserFile.Object)).ReturnsAsync(objectUrl); _jsRuntime.Setup(js => js.InvokeAsync("getAudioDurationFromFile", It.IsAny())).Returns(new ValueTask(90.0)); var audiofile = new Audiofile(); - var audiofileChangedFired = false; + var audiofileChangedFired = 0; _audiofileManager.AudiofileChanged += delegate (object? sender, Audiofile e) { if (e == audiofile) { - audiofileChangedFired = true; + audiofileChangedFired++; + } }; // Act - await _audiofileManager.SetPropertiesAsync(audiofile, browserFile.Object, inputId); + await _audiofileManager.SetPropertiesAsync(audiofile, browserFile.Object); // Assert Assert.AreEqual(codec, audiofile.AudioCodec); Assert.AreEqual(filename, audiofile.Name); Assert.AreEqual(objectUrl, audiofile.ObjectURL); Assert.AreEqual(TimeSpan.FromSeconds(90), audiofile.Duration); - Assert.IsTrue(audiofileChangedFired); - _fileInputManager.Verify(f => f.GetAudioCodec("audio/mpeg", filename), Times.Once); - _fileInputManager.Verify(f => f.GetObjectUrlAsync(inputId), Times.Once); + Assert.AreEqual(1, audiofileChangedFired); + _fileInputManager.Verify(f => f.GetAudioCodec(filename, "audio/mpeg"), Times.Once); + _fileInputManager.Verify(f => f.GetObjectUrlAsync(browserFile.Object), Times.Once); _jsRuntime.Verify(js => js.InvokeAsync("getAudioDurationFromFile", It.Is(o => o[0] as string == audiofile.ObjectURL)), Times.Once); } @@ -153,14 +155,13 @@ public async Task SetProperty_AudiofileWithDuration_SetsLastTrackEndAlsoAsync() // Arrange var objectUrl = "blob:123"; var filename = "song.mp3"; - var inputId = "inputId"; var browserFile = new Mock(); browserFile.SetupGet(b => b.ContentType).Returns("audio/mpeg"); browserFile.SetupGet(b => b.Name).Returns(filename); var codec = Audiofile.AudioCodecs.First(x => x.FileExtension == ".mp3"); - _fileInputManager.Setup(f => f.GetAudioCodec("audio/mpeg", filename)).Returns(codec); - _fileInputManager.Setup(f => f.GetObjectUrlAsync(inputId)).ReturnsAsync(objectUrl); + _fileInputManager.Setup(f => f.GetAudioCodec(filename, "audio/mpeg")).Returns(codec); + _fileInputManager.Setup(f => f.GetObjectUrlAsync(browserFile.Object)).ReturnsAsync(objectUrl); _jsRuntime.Setup(js => js.InvokeAsync("getAudioDurationFromFile", It.IsAny())).Returns(new ValueTask(90.0)); var track1 = new Track() { @@ -177,16 +178,16 @@ public async Task SetProperty_AudiofileWithDuration_SetsLastTrackEndAlsoAsync() { Tracks = [track1, track2] }; - var audiofileChangedFired = false; + var audiofileChangedFired = 0; _audiofileManager.AudiofileChanged += delegate (object? sender, Audiofile e) { if (e == audiofile) { - audiofileChangedFired = true; + audiofileChangedFired++; } }; // Act - await _audiofileManager.SetPropertiesAsync(audiofile, browserFile.Object, inputId); + await _audiofileManager.SetPropertiesAsync(audiofile, browserFile.Object); // Assert Assert.AreEqual(codec, audiofile.AudioCodec); @@ -194,14 +195,57 @@ public async Task SetProperty_AudiofileWithDuration_SetsLastTrackEndAlsoAsync() Assert.AreEqual(objectUrl, audiofile.ObjectURL); Assert.AreEqual(TimeSpan.FromSeconds(90), audiofile.Duration); Assert.AreEqual(TimeSpan.FromSeconds(90), track2.End); - Assert.IsTrue(audiofileChangedFired); - _fileInputManager.Verify(f => f.GetAudioCodec("audio/mpeg", filename), Times.Once); - _fileInputManager.Verify(f => f.GetObjectUrlAsync(inputId), Times.Once); + Assert.AreEqual(1, audiofileChangedFired); + _fileInputManager.Verify(f => f.GetAudioCodec(filename, "audio/mpeg"), Times.Once); + _fileInputManager.Verify(f => f.GetObjectUrlAsync(browserFile.Object), Times.Once); _jsRuntime.Verify(js => js.InvokeAsync("getAudioDurationFromFile", It.Is(o => o[0] as string == audiofile.ObjectURL)), Times.Once); } [TestMethod] - public async Task SetPropertiesAsync_NullBrowserFile_WithExistingObjectUrl_ShouldRevokeObjectUrl() + public async Task SetPropertiesAsync_PreviouslySetAudiofile_ShouldClearFirstAndSetPropertiesAsync() + { + // Arrange + var objectUrl = "blob:123"; + var filename = "song.mp3"; + var browserFile = new Mock(); + browserFile.SetupGet(b => b.ContentType).Returns("audio/mpeg"); + browserFile.SetupGet(b => b.Name).Returns(filename); + + var codec = Audiofile.AudioCodecs.First(x => x.FileExtension == ".mp3"); + _fileInputManager.Setup(f => f.GetAudioCodec(filename, "audio/mpeg")).Returns(codec); + _fileInputManager.Setup(f => f.GetObjectUrlAsync(browserFile.Object)).ReturnsAsync(objectUrl); + _jsRuntime.Setup(js => js.InvokeAsync("getAudioDurationFromFile", It.IsAny())).Returns(new ValueTask(90.0)); + + var audiofile = new Audiofile + { + ObjectURL = "previouslySetObjectUrl" + }; + var audiofileChangedFired = 0; + _audiofileManager.AudiofileChanged += delegate (object? sender, Audiofile e) + { + if (e == audiofile) + { + audiofileChangedFired++; + } + }; + // Act + await _audiofileManager.SetPropertiesAsync(audiofile, browserFile.Object); + + // Assert + Assert.AreEqual(codec, audiofile.AudioCodec); + Assert.AreEqual(filename, audiofile.Name); + Assert.AreEqual(objectUrl, audiofile.ObjectURL); + Assert.AreEqual(TimeSpan.FromSeconds(90), audiofile.Duration); + Assert.AreEqual(1, audiofileChangedFired); + _fileInputManager.Verify(f => f.GetAudioCodec(filename, "audio/mpeg"), Times.Once); + _fileInputManager.Verify(f => f.GetObjectUrlAsync(browserFile.Object), Times.Once); + _jsRuntime.Verify(js => js.InvokeAsync("getAudioDurationFromFile", It.Is(o => o[0] as string == audiofile.ObjectURL)), Times.Once); + _traceChangeManager.VerifySet(t => t.BulkEdit = true, Times.Once); + _traceChangeManager.VerifySet(t => t.BulkEdit = false, Times.Once); + } + + [TestMethod] + public async Task ClearPropertiesAsync_WithExistingObjectUrl_ShouldRevokeObjectUrl() { // Arrange var audiofile = new Audiofile @@ -209,21 +253,24 @@ public async Task SetPropertiesAsync_NullBrowserFile_WithExistingObjectUrl_Shoul ObjectURL = "just a test" }; var expectedUrl = audiofile.ObjectURL; - var audiofileChangedFired = false; + var audiofileChangedFired = 0; _audiofileManager.AudiofileChanged += delegate (object? sender, Audiofile e) { if (e == audiofile) { - audiofileChangedFired = true; + audiofileChangedFired++; + } }; // Act - await _audiofileManager.SetPropertiesAsync(audiofile, null, string.Empty); + await _audiofileManager.ClearPropertiesAsync(audiofile); // Assert - Assert.IsTrue(audiofileChangedFired); + Assert.AreEqual(1, audiofileChangedFired); _jsRuntime.Verify(js =>js.InvokeAsync("revokeAudioObjectURL", It.Is(args => args != null && args.Length > 0 && (args[0] as string) == expectedUrl)),Times.Once); Assert.IsNull(audiofile.ObjectURL); + _traceChangeManager.VerifySet(t => t.BulkEdit = true, Times.Once); + _traceChangeManager.VerifySet(t => t.BulkEdit = false, Times.Once); } [TestMethod] @@ -234,12 +281,13 @@ public void SetProperty_ChangedName_UpdatesPropertyAndAddsTrace() { Name = "oldname.mp3" }; - var audiofileChangedFired = false; + var audiofileChangedFired = 0; _audiofileManager.AudiofileChanged += delegate (object? sender, Audiofile e) { if (e == audiofile) { - audiofileChangedFired = true; + audiofileChangedFired++; + } }; // Act @@ -247,7 +295,7 @@ public void SetProperty_ChangedName_UpdatesPropertyAndAddsTrace() // Assert Assert.AreEqual("newname.mp3", audiofile.Name); - Assert.IsTrue(audiofileChangedFired); + Assert.AreEqual(1, audiofileChangedFired); _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == audiofile && y.TraceableChange.PreviousValue!.Equals("oldname.mp3") && y.TraceableChange.PropertyName == nameof(Audiofile.Name))), Times.Once); } @@ -259,19 +307,20 @@ public void SetProperty_EqualValue_DoesntChangeProperty() { Name = "oldname.mp3" }; - var audiofileChangedFired = false; + var audiofileChangedFired = 0; _audiofileManager.AudiofileChanged += delegate (object? sender, Audiofile e) { if (e == audiofile) { - audiofileChangedFired = true; + audiofileChangedFired++; + } }; // Act _audiofileManager.SetProperty(audiofile, x => x.Name, "oldname.mp3"); // Assert - Assert.IsFalse(audiofileChangedFired); + Assert.AreEqual(0, audiofileChangedFired); _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == audiofile && y.TraceableChange.PreviousValue!.Equals("oldname.mp3") && y.TraceableChange.PropertyName == nameof(Audiofile.Name))), Times.Never); } @@ -295,12 +344,13 @@ public void SetProperty_DurationChanged_SetsLastTrackEndAlso() Tracks = [track1, track2], }; var duration = new TimeSpan(0, 3, 37, 12); - var audiofileChangedFired = false; + var audiofileChangedFired = 0; _audiofileManager.AudiofileChanged += delegate (object? sender, Audiofile e) { if (e == audiofile) { - audiofileChangedFired = true; + audiofileChangedFired++; + } }; // Act @@ -309,7 +359,7 @@ public void SetProperty_DurationChanged_SetsLastTrackEndAlso() // Assert Assert.AreEqual(duration, audiofile.Duration); Assert.AreEqual(duration, track2.End); - Assert.IsTrue(audiofileChangedFired); + Assert.AreEqual(1, audiofileChangedFired); } [TestMethod] @@ -323,12 +373,13 @@ public void AddTrack_FirstTrack_AddsNewTrackWithCalulatedTrackProperties() Audiofiles = [audiofile] }; _sessionStateContainer.Setup(x => x.ActiveCuesheet).Returns(cuesheet); - var audiofileChangedFired = false; + var audiofileChangedFired = 0; _audiofileManager.AudiofileChanged += delegate (object? sender, Audiofile e) { if (e == audiofile) { - audiofileChangedFired = true; + audiofileChangedFired++; + } }; var track = new Track(); @@ -336,7 +387,7 @@ public void AddTrack_FirstTrack_AddsNewTrackWithCalulatedTrackProperties() _audiofileManager.AddTrack(audiofile, track); // Assert Assert.HasCount(1, audiofile.Tracks); - Assert.IsTrue(audiofileChangedFired); + Assert.AreEqual(1, audiofileChangedFired); Assert.AreEqual((ushort)1, audiofile.Tracks.First().Position); Assert.AreEqual(TimeSpan.Zero, audiofile.Tracks.First().Begin); Assert.AreEqual(duration, audiofile.Tracks.First().End); @@ -371,12 +422,12 @@ public void AddTrack_AddToPreviousTracks_AddsNewTrackWithCalulatedTrackPropertie { IsLinkedToPreviousTrack = true }; - var audiofileChangedFired = false; + var audiofileChangedFired = 0; _audiofileManager.AudiofileChanged += delegate (object? sender, Audiofile e) { if (e == audiofile) { - audiofileChangedFired = true; + audiofileChangedFired++; } }; // Act @@ -388,7 +439,7 @@ public void AddTrack_AddToPreviousTracks_AddsNewTrackWithCalulatedTrackPropertie Assert.AreEqual(duration, audiofile.Tracks.Last().End); Assert.AreEqual(cuesheet, track.Cuesheet); Assert.AreEqual(audiofile, track.Audiofile); - Assert.IsTrue(audiofileChangedFired); + Assert.AreEqual(1, audiofileChangedFired); _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == audiofile && y.TraceableChange.PreviousValue == tracks && y.TraceableChange.PropertyName == nameof(Audiofile.Tracks))), Times.Once); _traceChangeManager.VerifySet(t => t.BulkEdit = true, Times.Once); _traceChangeManager.VerifySet(t => t.BulkEdit = false, Times.Once); @@ -421,18 +472,19 @@ public void AddTrack_DuringRecording_AddsNewTrackWithCalulatedTrackProperties() { IsLinkedToPreviousTrack = true }; - var audiofileChangedFired = false; + var audiofileChangedFired = 0; _audiofileManager.AudiofileChanged += delegate (object? sender, Audiofile e) { if (e == audiofile) { - audiofileChangedFired = true; + audiofileChangedFired++; + } }; // Act _audiofileManager.AddTrack(audiofile, track); // Assert - Assert.IsTrue(audiofileChangedFired); + Assert.AreEqual(1, audiofileChangedFired); Assert.HasCount(2, audiofile.Tracks); Assert.AreEqual((ushort)2, audiofile.Tracks.Last().Position); Assert.IsNotNull(audiofile.Tracks.First().End); @@ -456,18 +508,19 @@ public void AddTrack_FirstTrackImporting_AddsNewTrackWithCalulatedTrackPropertie }; _sessionStateContainer.Setup(x => x.ActiveCuesheet).Returns(importCuesheet); var track = new Track(); - var audiofileChangedFired = false; + var audiofileChangedFired = 0; _audiofileManager.AudiofileChanged += delegate (object? sender, Audiofile e) { if (e == audiofile) { - audiofileChangedFired = true; + audiofileChangedFired++; + } }; // Act _audiofileManager.AddTrack(audiofile, track); // Assert - Assert.IsTrue(audiofileChangedFired); + Assert.AreEqual(1, audiofileChangedFired); Assert.HasCount(1, audiofile.Tracks); Assert.AreEqual((ushort)1, audiofile.Tracks.First().Position); Assert.AreEqual(TimeSpan.Zero, audiofile.Tracks.First().Begin); @@ -533,12 +586,12 @@ public void RemoveTracks_WithAttachedTracks_RemovesTracksAndRecalculatesRemainin track4.Cuesheet = cuesheet; track5.Cuesheet = cuesheet; _sessionStateContainer.Setup(x => x.ActiveCuesheet).Returns(cuesheet); - var audiofileChangedFired = false; + var audiofileChangedFired = 0; _audiofileManager.AudiofileChanged += delegate (object? sender, Audiofile e) { if (e == audiofile) { - audiofileChangedFired = true; + audiofileChangedFired++; } }; // Act @@ -548,7 +601,7 @@ public void RemoveTracks_WithAttachedTracks_RemovesTracksAndRecalculatesRemainin Assert.Contains(track1, audiofile.Tracks); Assert.Contains(track3, audiofile.Tracks); Assert.Contains(track5, audiofile.Tracks); - Assert.IsTrue(audiofileChangedFired); + Assert.AreEqual(1, audiofileChangedFired); Assert.AreEqual((ushort)1, track1.Position); Assert.AreEqual(TimeSpan.Zero, track1.Begin); Assert.AreEqual(track1.End, track3.Begin); @@ -620,12 +673,12 @@ public void RemoveTracks_DuringImport_RemovesTracksAndRecalculatesRemainingTrack track4.Audiofile = audiofile; track5.Audiofile = audiofile; _sessionStateContainer.Setup(x => x.ActiveCuesheet).Returns(importCuesheet); - var audiofileChangedFired = false; + var audiofileChangedFired = 0; _audiofileManager.AudiofileChanged += delegate (object? sender, Audiofile e) { if (e == audiofile) { - audiofileChangedFired = true; + audiofileChangedFired++; } }; // Act @@ -641,7 +694,7 @@ public void RemoveTracks_DuringImport_RemovesTracksAndRecalculatesRemainingTrack Assert.AreEqual((ushort)2, track3.Position); Assert.AreEqual(track5.Begin, track3.End); Assert.AreEqual((ushort)3, track5.Position); - Assert.IsTrue(audiofileChangedFired); + Assert.AreEqual(1, audiofileChangedFired); Assert.AreEqual(duration, track5.End); _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == audiofile && y.TraceableChange.PreviousValue == previousValue && y.TraceableChange.PropertyName == nameof(Audiofile.Tracks))), Times.Once); _traceChangeManager.VerifySet(t => t.BulkEdit = true, Times.Once); diff --git a/AudioCuesheetEditor.Tests/Services/AudioCuesheet/CuesheetManagerTests.cs b/AudioCuesheetEditor.Tests/Services/AudioCuesheet/CuesheetManagerTests.cs index 39a36e29..dea190de 100644 --- a/AudioCuesheetEditor.Tests/Services/AudioCuesheet/CuesheetManagerTests.cs +++ b/AudioCuesheetEditor.Tests/Services/AudioCuesheet/CuesheetManagerTests.cs @@ -64,6 +64,7 @@ public void SetProperty_NewValue_ChangesPropertyAndSetsTracedChange() { // Arrange var cuesheet = new Cuesheet(); + _sessionStateContainer.SetupGet(x => x.Cuesheet).Returns(cuesheet); _sessionStateContainer.Setup(x => x.ActiveCuesheet).Returns(cuesheet); // Act _cuesheetManager.SetProperty(x => x.Artist, "Artist"); @@ -116,6 +117,21 @@ public void SetProperty_DeletedAudiofile_RevokesObjectURLs() _jsRuntime.Verify(x => x.InvokeAsync("revokeAudioObjectURL", It.Is(args => args.Length == 1 && args[0].Equals(audiofile2.ObjectURL))), Times.Once()); } + [TestMethod] + public void SetProperty_OnImportCuesheet_DoesNotTraceChanges() + { + // Arrange + var importCuesheet = new Cuesheet(); + var cuesheet = new Cuesheet(); + _sessionStateContainer.SetupGet(x => x.ImportCuesheet).Returns(importCuesheet); + _sessionStateContainer.SetupGet(x => x.Cuesheet).Returns(cuesheet); + _sessionStateContainer.SetupGet(x => x.ActiveCuesheet).Returns(importCuesheet); + // Act + _cuesheetManager.SetProperty(x => x.Artist, nameof(SetProperty_OnImportCuesheet_DoesNotTraceChanges)); + // Assert + _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == importCuesheet)), Times.Never); + } + [TestMethod] public void IsRecordingPossible_WithoutTracks_ReturnsSuccess() { @@ -690,6 +706,75 @@ public void MoveUp_TracksAbove_ReturnsSuccess() _traceChangeManager.VerifySet(x => x.BulkEdit = false, Times.Once); } + [TestMethod] + public void MoveUp_Move2Positions_ReturnsSuccess() + { + // Arrange + var track1End = new TimeSpan(0, 3, 12); + var track1 = new Track() + { + Position = 1, + Begin = TimeSpan.Zero, + End = track1End, + IsLinkedToPreviousTrack = true + }; + var track2End = new TimeSpan(0, 7, 34); + var track2 = new Track() + { + Position = 2, + Begin = track1End, + End = track2End, + IsLinkedToPreviousTrack = true + }; + var track3End = new TimeSpan(0, 10, 4); + var track3 = new Track() + { + Position = 3, + Begin = track2End, + End = track3End, + IsLinkedToPreviousTrack = true + }; + var track4 = new Track() + { + Position = 4, + Begin = track3End, + End = new TimeSpan(0, 13, 45), + IsLinkedToPreviousTrack = true + }; + var cuesheet = new Cuesheet() + { + Audiofiles = [ + new() { + Tracks = [track4, track3, track2, track1] + } + ], + }; + track1.Cuesheet = cuesheet; + track2.Cuesheet = cuesheet; + track3.Cuesheet = cuesheet; + track4.Cuesheet = cuesheet; + track1.Audiofile = cuesheet.Audiofiles.First(); + track2.Audiofile = cuesheet.Audiofiles.First(); + track3.Audiofile = cuesheet.Audiofiles.First(); + track4.Audiofile = cuesheet.Audiofiles.First(); + _sessionStateContainer.Setup(x => x.ActiveCuesheet).Returns(cuesheet); + // Act + var result = _cuesheetManager.MoveUp([track2, track3], 2); + // Assert + Assert.IsTrue(result.IsSuccess); + Assert.AreEqual((ushort?)1, track2.Position); + Assert.AreEqual(TimeSpan.Zero, track2.Begin); + Assert.AreEqual(track1End, track2.End); + Assert.AreEqual((ushort?)2, track3.Position); + Assert.AreEqual(track1End, track3.Begin); + Assert.AreEqual(track2End, track3.End); + Assert.AreEqual((ushort?)3, track1.Position); + Assert.AreEqual(track2End, track1.Begin); + _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == cuesheet.Audiofiles.First() && y.TraceableChange.PropertyName == nameof(Audiofile.Tracks))), Times.Never); + _traceChangeManager.VerifySet(x => x.BulkEdit = true, Times.Once); + _traceChangeManager.VerifySet(x => x.BulkEdit = false, Times.Once); + } + [TestMethod] public void MoveUp_NoTracksAbove_ReturnsFailure() { @@ -1044,6 +1129,78 @@ public void MoveDown_TracksBelow_ReturnsSuccess() _traceChangeManager.VerifySet(x => x.BulkEdit = false, Times.Once); } + [TestMethod] + public void MoveDown_Move2Positions_ReturnsSuccess() + { + // Arrange + var track1End = new TimeSpan(0, 3, 12); + var track1 = new Track() + { + Position = 1, + Begin = TimeSpan.Zero, + End = track1End, + IsLinkedToPreviousTrack = true + }; + var track2End = new TimeSpan(0, 7, 34); + var track2 = new Track() + { + Position = 2, + Begin = track1End, + End = track2End, + IsLinkedToPreviousTrack = true + }; + var track3End = new TimeSpan(0, 10, 4); + var track3 = new Track() + { + Position = 3, + Begin = track2End, + End = track3End, + IsLinkedToPreviousTrack = true + }; + var track4 = new Track() + { + Position = 4, + Begin = track3End, + End = new TimeSpan(0, 13, 45), + IsLinkedToPreviousTrack = true + }; + var cuesheet = new Cuesheet() + { + Audiofiles = [ + new() { + Tracks = [track4, track3, track2, track1] + } + ] + }; + track1.Cuesheet = cuesheet; + track2.Cuesheet = cuesheet; + track3.Cuesheet = cuesheet; + track4.Cuesheet = cuesheet; + track1.Audiofile = cuesheet.Audiofiles.First(); + track2.Audiofile = cuesheet.Audiofiles.First(); + track3.Audiofile = cuesheet.Audiofiles.First(); + track4.Audiofile = cuesheet.Audiofiles.First(); + _sessionStateContainer.Setup(x => x.ActiveCuesheet).Returns(cuesheet); + // Act + var result = _cuesheetManager.MoveDown([track2, track1], 2); + // Assert + Assert.IsTrue(result.IsSuccess); + Assert.AreEqual((ushort?)1, track3.Position); + Assert.AreEqual(TimeSpan.Zero, track3.Begin); + Assert.AreEqual(track1End, track3.End); + Assert.AreEqual((ushort?)2, track4.Position); + Assert.AreEqual(track1End, track4.Begin); + Assert.AreEqual(track2End, track4.End); + Assert.AreEqual((ushort?)3, track1.Position); + Assert.AreEqual(track2End, track1.Begin); + Assert.AreEqual(track3End, track1.End); + Assert.AreEqual((ushort?)4, track2.Position); + Assert.AreEqual(track3End, track2.Begin); + _traceChangeManager.Verify(x => x.AddChange(It.Is(y => y.TraceableObject == cuesheet.Audiofiles.First() && y.TraceableChange.PropertyName == nameof(Audiofile.Tracks))), Times.Never); + _traceChangeManager.VerifySet(x => x.BulkEdit = true, Times.Once); + _traceChangeManager.VerifySet(x => x.BulkEdit = false, Times.Once); + } + [TestMethod] public void MoveDown_NoTracksBelow_ReturnsFailure() { diff --git a/AudioCuesheetEditor.Tests/Services/IO/AudioImportServiceTests.cs b/AudioCuesheetEditor.Tests/Services/IO/AudioImportServiceTests.cs new file mode 100644 index 00000000..751e3aaa --- /dev/null +++ b/AudioCuesheetEditor.Tests/Services/IO/AudioImportServiceTests.cs @@ -0,0 +1,120 @@ +//This file is part of AudioCuesheetEditor. + +//AudioCuesheetEditor is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. + +//AudioCuesheetEditor is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. + +//You should have received a copy of the GNU General Public License +//along with Foobar. If not, see +//. +using AudioCuesheetEditor.Model.AudioCuesheet; +using AudioCuesheetEditor.Model.IO.Audio; +using AudioCuesheetEditor.Services.AudioCuesheet; +using AudioCuesheetEditor.Services.IO; +using AudioCuesheetEditor.Services.UI; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace AudioCuesheetEditor.Tests.Services.IO +{ + [TestClass] + public class AudioImportServiceTests + { + private readonly AudioImportService _service; + private readonly Mock _dialogManager; + private readonly Mock _sessionStateContainer; + private readonly Mock _cuesheetManager; + + public AudioImportServiceTests() + { + _dialogManager = new(); + _sessionStateContainer = new(); + _cuesheetManager = new(); + _service = new(_dialogManager.Object, _sessionStateContainer.Object, _cuesheetManager.Object); + } + + [TestMethod] + public async Task MapAudioImportAsync_WhenMappingIsNotRequired_ReturnsTrueAsync() + { + // Arrange + var cuesheet = new Cuesheet(); + var importAudiofileMapping = new Dictionary(); + _dialogManager.Setup(x => x.ShowImportAudiofilesDialogAsync()).ReturnsAsync(true); + _sessionStateContainer.SetupGet(x => x.ImportAudiofileMapping).Returns(importAudiofileMapping); + _sessionStateContainer.SetupSet(x => x.ImportAudiofileMapping = It.IsAny>()).Callback>(cs => importAudiofileMapping = cs); + _sessionStateContainer.SetupGet(x => x.ImportAudiofiles).Returns([]); + _sessionStateContainer.SetupGet(x => x.ActiveCuesheet).Returns(cuesheet); + // Act + var result = await _service.MapAudioImportAsync(); + // Assert + Assert.IsTrue(result); + } + + [TestMethod] + public async Task MapAudioImportAsync_WhenMappingIsRequiredAndDoneAutomatically_ReturnsTrueAsync() + { + // Arrange + var cuesheet = new Cuesheet() + { + Audiofiles = [ + new() + ] + }; + var importAudiofileMapping = new Dictionary + { + { new(), null } + }; + var importAudioFiles = new List + { + new() + }; + _dialogManager.Setup(x => x.ShowImportAudiofilesDialogAsync()).ReturnsAsync(true); + _sessionStateContainer.SetupGet(x => x.ImportAudiofileMapping).Returns(importAudiofileMapping); + _sessionStateContainer.SetupSet(x => x.ImportAudiofileMapping = It.IsAny>()).Callback>(cs => importAudiofileMapping = cs); + _sessionStateContainer.SetupGet(x => x.ImportAudiofiles).Returns(importAudioFiles); + _sessionStateContainer.SetupGet(x => x.ActiveCuesheet).Returns(cuesheet); + // Act + var result = await _service.MapAudioImportAsync(); + // Assert + Assert.IsTrue(result); + } + + [TestMethod] + public async Task MapAudioImportAsync_WhenMappingIsRequiredAndCannotBeDoneAutomatically_ReturnsDialogResultAsync() + { + // Arrange + var cuesheet = new Cuesheet() + { + Audiofiles = [ + new() + ] + }; + var importAudiofileMapping = new Dictionary + { + { new(), null } + }; + var importAudioFiles = new List + { + new(), new() + }; + var dialogResultAborted = true; + _dialogManager.Setup(x => x.ShowImportAudiofilesDialogAsync()).ReturnsAsync(dialogResultAborted); + _sessionStateContainer.SetupGet(x => x.ImportAudiofileMapping).Returns(importAudiofileMapping); + _sessionStateContainer.SetupSet(x => x.ImportAudiofileMapping = It.IsAny>()).Callback>(cs => importAudiofileMapping = cs); + _sessionStateContainer.SetupGet(x => x.ImportAudiofiles).Returns(importAudioFiles); + _sessionStateContainer.SetupGet(x => x.ActiveCuesheet).Returns(cuesheet); + // Act + var result = await _service.MapAudioImportAsync(); + // Assert + Assert.AreEqual(!dialogResultAborted, result); + } + } +} diff --git a/AudioCuesheetEditor.Tests/Services/IO/FileInputManagerTests.cs b/AudioCuesheetEditor.Tests/Services/IO/FileInputManagerTests.cs index 14022cca..90d6038e 100644 --- a/AudioCuesheetEditor.Tests/Services/IO/FileInputManagerTests.cs +++ b/AudioCuesheetEditor.Tests/Services/IO/FileInputManagerTests.cs @@ -120,7 +120,7 @@ public void IsValidAudiofile_ReturnsTrue_WithValidAudiocodec() var contentType = "audio/wav"; // Act - var result = _service.IsValidAudiofile(contentType, fileName); + var result = _service.IsValidAudiofile(fileName, contentType); // Assert Assert.IsTrue(result); @@ -134,7 +134,7 @@ public void IsValidAudiofile_ReturnsFalse_WithInvalidAudiocodecAndExtension() var contentType = "just a fantasy"; // Act - var result = _service.IsValidAudiofile(contentType, fileName); + var result = _service.IsValidAudiofile(fileName, contentType); // Assert Assert.IsFalse(result); @@ -148,7 +148,7 @@ public void GetAudioCodec_ReturnsAudiocodec_WhenContentTypeMatches() var contentType = "audio/webm"; // Act - var result = _service.GetAudioCodec(contentType, fileName); + var result = _service.GetAudioCodec(fileName, contentType); // Assert Assert.IsNotNull(result); @@ -163,7 +163,7 @@ public void GetAudioCodec_ReturnsAudiocodec_WhenContentTypeAndFileExtensionMatch var contentType = "audio/webm"; // Act - var result = _service.GetAudioCodec(contentType, fileName); + var result = _service.GetAudioCodec(fileName, contentType); // Assert Assert.IsNotNull(result); @@ -178,12 +178,26 @@ public void GetAudioCodec_ReturnsNull_WhenContentTypeAndFileExtensionNotMatch() var contentType = "fantasy stuff"; // Act - var result = _service.GetAudioCodec(contentType, fileName); + var result = _service.GetAudioCodec(fileName, contentType); // Assert Assert.IsNull(result); } + [TestMethod()] + public void GetAudioCodec_ReturnsAudiocodec_WhenFileExtensionMatches() + { + // Arrange + var fileName = "test.mP3"; + + // Act + var result = _service.GetAudioCodec(fileName); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(Audiofile.AudioCodecs.Single(x => x.FileExtension.Equals(".mp3", StringComparison.OrdinalIgnoreCase)), result); + } + [TestMethod()] public void IsValidForImportView_ReturnsTrue_WhenFileIsHtml() { @@ -218,7 +232,6 @@ public async Task CreateFileUploadsAsync_ReturnsFileUploads_WhenFileHasTextConte // Arrange var firstFile = CreateBrowserFile("Test.txt", "text/plain", "Just a test!"); var secondFile = CreateBrowserFile("Test.mp3", "audio/mpeg"); - var fileInputId = nameof(CreateFileUploadsAsync_ReturnsFileUploads_WhenFileHasTextContentAsync); IReadOnlyList browserfiles = [ firstFile, secondFile @@ -226,7 +239,7 @@ public async Task CreateFileUploadsAsync_ReturnsFileUploads_WhenFileHasTextConte var objectUrl = "Some object url!"; _jsRuntimeMock.Setup(js => js.InvokeAsync(It.IsAny(), It.IsAny())).ReturnsAsync(objectUrl); // Act - var result = await _service.CreateFileUploadsAsync(browserfiles, fileInputId); + var result = await _service.CreateFileUploadsAsync(browserfiles); // Assert Assert.HasCount(2, result); Assert.AreEqual(firstFile.Name, result.First().Name); @@ -251,7 +264,7 @@ public async Task CreateFileUploadsAsync_ReturnsEmpty_WhenFilesHaveInvalidMimeTy // Act var result = await _service.CreateFileUploadsAsync(browserfiles); // Assert - Assert.HasCount(0, result); + Assert.IsEmpty(result); } static IBrowserFile CreateBrowserFile(string name, string contentType, string? content = null) diff --git a/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs b/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs index 658d9f32..fabbd9fa 100644 --- a/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs +++ b/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs @@ -38,19 +38,29 @@ namespace AudioCuesheetEditor.Tests.Services.IO public class ImportManagerTests { private readonly ImportManager _service; - private readonly Mock _sessionStateContainerMock; - private readonly Mock _traceChangeManagerMock; - private readonly Mock _fileInputManagerMock; - private readonly Mock _textImportServiceMock; - private readonly Mock _trackManagerMock; + private readonly Mock _sessionStateContainer; + private readonly Mock _traceChangeManager; + private readonly Mock _fileInputManager; + private readonly Mock _textImportService; + private readonly Mock _trackManager; + private readonly Mock _audioImportService; public ImportManagerTests() { - _traceChangeManagerMock = new(); - _sessionStateContainerMock = new(); - _fileInputManagerMock = new(); - _textImportServiceMock = new(); - _trackManagerMock = new(); - _trackManagerMock.Setup(x => x.CopyValues(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + _traceChangeManager = new(); + _sessionStateContainer = new(); + _fileInputManager = new(); + _textImportService = new(); + _trackManager = new(); + _trackManager.Setup(x => x.SetProperty(It.IsAny(), It.IsAny>>(), It.IsAny())) + .Callback((Track track, LambdaExpression propExpr, object value) => + { + var memberExpression = propExpr.Body as MemberExpression; + if (memberExpression?.Member is PropertyInfo propertyInfo) + { + propertyInfo.SetValue(track, value); + } + }); + _trackManager.Setup(x => x.CopyValues(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Callback((source, target, setIsLinkedToPreviousTrack, setPosition, setArtist, setTitle, setBegin, setEnd, setLength, setFlags, setPreGap, setPostGap) => { if (setIsLinkedToPreviousTrack) @@ -94,7 +104,7 @@ public ImportManagerTests() TrackManager_SetValue(target, x => x.PostGap, source.PostGap, false); } }); - _trackManagerMock.Setup(x => x.Clone(It.IsAny())).Returns(track => + _trackManager.Setup(x => x.Clone(It.IsAny())).Returns(track => { var clone = new Track(); Boolean setLength = true; @@ -102,11 +112,12 @@ public ImportManagerTests() { setLength = false; } - _trackManagerMock.Object.CopyValues(track, clone, setLength: setLength); + _trackManager.Object.CopyValues(track, clone, setLength: setLength); return clone; }); + _audioImportService = new(); var loggerMock = new Mock>(); - _service = new ImportManager(_sessionStateContainerMock.Object, _traceChangeManagerMock.Object, _fileInputManagerMock.Object, _textImportServiceMock.Object, _trackManagerMock.Object, loggerMock.Object); + _service = new ImportManager(_sessionStateContainer.Object, _traceChangeManager.Object, _fileInputManager.Object, _textImportService.Object, _trackManager.Object, _audioImportService.Object, loggerMock.Object); } void TrackManager_SetValue(Track track, Expression> propertyExpression, TProperty value, Boolean signalTraceChangeManager = true) @@ -130,7 +141,7 @@ void TrackManager_SetValue(Track track, Expression x.AnalyseAsync(fileContent)).ReturnsAsync(importFile); - _sessionStateContainerMock.SetupGet(x => x.Importfile).Returns(importFile); + _textImportService.Setup(x => x.AnalyseAsync(fileContent)).ReturnsAsync(importFile); + _sessionStateContainer.SetupGet(x => x.Importfile).Returns(importFile); IList? sessionStateContainerImportAudiofiles = []; - _sessionStateContainerMock.SetupGet(x => x.ImportAudiofiles).Returns(() => sessionStateContainerImportAudiofiles); + _sessionStateContainer.SetupGet(x => x.ImportAudiofiles).Returns(() => sessionStateContainerImportAudiofiles); Cuesheet? sessionStateContainerImportCuesheet = null; - _sessionStateContainerMock.SetupSet(x => x.ImportCuesheet = It.IsAny()).Callback(cs => sessionStateContainerImportCuesheet = cs); - _sessionStateContainerMock.SetupGet(x => x.ImportCuesheet).Returns(() => sessionStateContainerImportCuesheet); + _sessionStateContainer.SetupSet(x => x.ImportCuesheet = It.IsAny()).Callback(cs => sessionStateContainerImportCuesheet = cs); + _sessionStateContainer.SetupGet(x => x.ImportCuesheet).Returns(() => sessionStateContainerImportCuesheet); IImportfile? sessionStateContainerImportfile = null; - _sessionStateContainerMock.SetupSet(x => x.Importfile = It.IsAny()).Callback(x => sessionStateContainerImportfile = x); + _sessionStateContainer.SetupSet(x => x.Importfile = It.IsAny()).Callback(x => sessionStateContainerImportfile = x); // Act await _service.AnalyseImportfile(); // Assert @@ -209,12 +220,12 @@ public async Task AnalyseImportfile_WithoutAnalysedCuesheet_DoesNothing() FileContent = fileContent, FileType = ImportFileType.Textfile, }; - _textImportServiceMock.Setup(x => x.AnalyseAsync(fileContent)).ReturnsAsync(importFile); - _sessionStateContainerMock.SetupGet(x => x.Importfile).Returns(importFile); + _textImportService.Setup(x => x.AnalyseAsync(fileContent)).ReturnsAsync(importFile); + _sessionStateContainer.SetupGet(x => x.Importfile).Returns(importFile); Cuesheet? sessionStateContainerImportCuesheet = null; - _sessionStateContainerMock.SetupSet(x => x.ImportCuesheet = It.IsAny()).Callback(cs => sessionStateContainerImportCuesheet = cs); + _sessionStateContainer.SetupSet(x => x.ImportCuesheet = It.IsAny()).Callback(cs => sessionStateContainerImportCuesheet = cs); IImportfile? sessionStateContainerImportfile = null; - _sessionStateContainerMock.SetupSet(x => x.Importfile = It.IsAny()).Callback(x => sessionStateContainerImportfile = x); + _sessionStateContainer.SetupSet(x => x.Importfile = It.IsAny()).Callback(x => sessionStateContainerImportfile = x); // Act await _service.AnalyseImportfile(); // Assert @@ -268,12 +279,12 @@ TRACK 08 AUDIO FileType = ImportFileType.Cuesheet }; IList? sessionStateContainerImportAudiofiles = []; - _sessionStateContainerMock.SetupGet(x => x.ImportAudiofiles).Returns(() => sessionStateContainerImportAudiofiles); - _sessionStateContainerMock.SetupGet(x => x.Importfile).Returns(() => sessionStateContainerImportFile); - _sessionStateContainerMock.SetupSet(x => x.Importfile = It.IsAny()).Callback(importfile => sessionStateContainerImportFile = importfile); + _sessionStateContainer.SetupGet(x => x.ImportAudiofiles).Returns(() => sessionStateContainerImportAudiofiles); + _sessionStateContainer.SetupGet(x => x.Importfile).Returns(() => sessionStateContainerImportFile); + _sessionStateContainer.SetupSet(x => x.Importfile = It.IsAny()).Callback(importfile => sessionStateContainerImportFile = importfile); Cuesheet? sessionStateContainerImportCuesheet = null; - _sessionStateContainerMock.SetupSet(x => x.ImportCuesheet = It.IsAny()).Callback(cuesheet => sessionStateContainerImportCuesheet = cuesheet); - _sessionStateContainerMock.SetupGet(x => x.ImportCuesheet).Returns(() => sessionStateContainerImportCuesheet); + _sessionStateContainer.SetupSet(x => x.ImportCuesheet = It.IsAny()).Callback(cuesheet => sessionStateContainerImportCuesheet = cuesheet); + _sessionStateContainer.SetupGet(x => x.ImportCuesheet).Returns(() => sessionStateContainerImportCuesheet); // Act await _service.AnalyseImportfile(); @@ -297,12 +308,12 @@ public async Task UploadFilesAsync_ProjectFile_ImportsCorrectly() // Arrange var fileContent = "This is the content"; var file = new FileUpload("test.projectfile", "text/plain", fileContent); - _fileInputManagerMock.Setup(f => f.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Projectfile, It.IsAny>())).Returns(true); - _fileInputManagerMock.Setup(f => f.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Cuesheet, It.IsAny>())).Returns(false); - _fileInputManagerMock.Setup(f => f.IsValidForImportView(file.ContentType, file.Name)).Returns(false); + _fileInputManager.Setup(f => f.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Projectfile, It.IsAny>())).Returns(true); + _fileInputManager.Setup(f => f.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Cuesheet, It.IsAny>())).Returns(false); + _fileInputManager.Setup(f => f.IsValidForImportView(file.ContentType, file.Name)).Returns(false); IImportfile? sessionStateContainerImportfile = null; - _sessionStateContainerMock.SetupSet(x => x.Importfile = It.IsAny()).Callback(x => sessionStateContainerImportfile = x); + _sessionStateContainer.SetupSet(x => x.Importfile = It.IsAny()).Callback(x => sessionStateContainerImportfile = x); // Act await _service.UploadFilesAsync([file]); @@ -320,12 +331,12 @@ public async Task UploadFilesAsync_CuesheetFile_ImportsCorrectly() var fileContent = "Cuesheet file content"; var file = new FileUpload("test.cue", "text/plain", fileContent); - _fileInputManagerMock.Setup(f => f.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Projectfile, It.IsAny>())).Returns(false); - _fileInputManagerMock.Setup(f => f.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Cuesheet, It.IsAny>())).Returns(true); - _fileInputManagerMock.Setup(f => f.IsValidForImportView(file.ContentType, file.Name)).Returns(false); + _fileInputManager.Setup(f => f.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Projectfile, It.IsAny>())).Returns(false); + _fileInputManager.Setup(f => f.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Cuesheet, It.IsAny>())).Returns(true); + _fileInputManager.Setup(f => f.IsValidForImportView(file.ContentType, file.Name)).Returns(false); IImportfile? sessionStateContainerImportfile = null; - _sessionStateContainerMock.SetupSet(x => x.Importfile = It.IsAny()).Callback(x => sessionStateContainerImportfile = x); + _sessionStateContainer.SetupSet(x => x.Importfile = It.IsAny()).Callback(x => sessionStateContainerImportfile = x); // Act await _service.UploadFilesAsync([file]); @@ -343,12 +354,12 @@ public async Task UploadFilesAsync_TextFile_ImportsCorrectly() var fileContent = "TextFileContent"; var file = new FileUpload("test.txt", "text/plain", fileContent); - _fileInputManagerMock.Setup(f => f.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Projectfile, It.IsAny>())).Returns(false); - _fileInputManagerMock.Setup(f => f.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Cuesheet, It.IsAny>())).Returns(false); - _fileInputManagerMock.Setup(f => f.IsValidForImportView(file.ContentType, file.Name)).Returns(true); + _fileInputManager.Setup(f => f.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Projectfile, It.IsAny>())).Returns(false); + _fileInputManager.Setup(f => f.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Cuesheet, It.IsAny>())).Returns(false); + _fileInputManager.Setup(f => f.IsValidForImportView(file.ContentType, file.Name)).Returns(true); IImportfile? sessionStateContainerImportfile = null; - _sessionStateContainerMock.SetupSet(x => x.Importfile = It.IsAny()).Callback(x => sessionStateContainerImportfile = x); + _sessionStateContainer.SetupSet(x => x.Importfile = It.IsAny()).Callback(x => sessionStateContainerImportfile = x); // Act await _service.UploadFilesAsync([file]); @@ -365,17 +376,17 @@ public async Task UploadFilesAsync_WithAudiofile_ImportsCorrectly() // Arrange var file = new FileUpload("test.mp3", "audio/mpeg"); - _fileInputManagerMock.Setup(f => f.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Projectfile, It.IsAny>())).Returns(false); - _fileInputManagerMock.Setup(f => f.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Cuesheet, It.IsAny>())).Returns(false); - _fileInputManagerMock.Setup(f => f.IsValidForImportView(file.ContentType, file.Name)).Returns(false); - _fileInputManagerMock.Setup(f => f.IsValidAudiofile(file.ContentType, file.Name)).Returns(true); - _fileInputManagerMock.Setup(f => f.CreateAudiofileAsync(It.IsAny())).ReturnsAsync(new Audiofile() { Name = file.Name }); + _fileInputManager.Setup(f => f.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Projectfile, It.IsAny>())).Returns(false); + _fileInputManager.Setup(f => f.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Cuesheet, It.IsAny>())).Returns(false); + _fileInputManager.Setup(f => f.IsValidForImportView(file.ContentType, file.Name)).Returns(false); + _fileInputManager.Setup(f => f.IsValidAudiofile(file.Name, file.ContentType)).Returns(true); + _fileInputManager.Setup(f => f.CreateAudiofileAsync(It.IsAny())).ReturnsAsync(new Audiofile() { Name = file.Name }); IImportfile? sessionStateContainerImportfile = null; - _sessionStateContainerMock.SetupSet(x => x.Importfile = It.IsAny()).Callback(x => sessionStateContainerImportfile = x); + _sessionStateContainer.SetupSet(x => x.Importfile = It.IsAny()).Callback(x => sessionStateContainerImportfile = x); IList? sessionStateContainerImportAudiofiles = []; - _sessionStateContainerMock.SetupGet(x => x.ImportAudiofiles).Returns(() => sessionStateContainerImportAudiofiles); - _sessionStateContainerMock.SetupSet(x => x.ImportAudiofiles = It.IsAny>()).Callback>(x => sessionStateContainerImportAudiofiles = x); + _sessionStateContainer.SetupGet(x => x.ImportAudiofiles).Returns(() => sessionStateContainerImportAudiofiles); + _sessionStateContainer.SetupSet(x => x.ImportAudiofiles = It.IsAny>()).Callback>(x => sessionStateContainerImportAudiofiles = x); // Act await _service.UploadFilesAsync([file]); @@ -385,7 +396,7 @@ public async Task UploadFilesAsync_WithAudiofile_ImportsCorrectly() } [TestMethod] - public void ImportCuesheet_WithImportCuesheetAvailable_ImportsCuesheetData() + public async Task ImportCuesheetAsync_WithImportCuesheetAvailable_ImportsCuesheetDataAsync() { // Arrange var track1 = new Track() @@ -398,8 +409,8 @@ public void ImportCuesheet_WithImportCuesheetAvailable_ImportsCuesheetData() { Artist = "Track Artist 2", Title = "Track Title 2", - End = new TimeSpan(0, 8, 54), }; + var audiofileDuration = new TimeSpan(0, 12, 45); var sessionStateContainerImportCuesheet = new Cuesheet() { Artist = "Artist 123", @@ -409,7 +420,8 @@ public void ImportCuesheet_WithImportCuesheetAvailable_ImportsCuesheetData() { Name = "song.mp3", ObjectURL = "Just a test", - Tracks = [track1, track2] + Tracks = [track1, track2], + Duration = audiofileDuration } ] }; @@ -417,15 +429,15 @@ public void ImportCuesheet_WithImportCuesheetAvailable_ImportsCuesheetData() track2.Cuesheet = sessionStateContainerImportCuesheet; track1.Audiofile = sessionStateContainerImportCuesheet.Audiofiles.First(); track2.Audiofile = sessionStateContainerImportCuesheet.Audiofiles.First(); - _sessionStateContainerMock.SetupGet(x => x.ImportCuesheet).Returns(sessionStateContainerImportCuesheet); - _sessionStateContainerMock.SetupSet(x => x.ImportCuesheet = It.IsAny()).Callback(cuesheet => sessionStateContainerImportCuesheet = cuesheet); + _sessionStateContainer.SetupGet(x => x.ImportCuesheet).Returns(sessionStateContainerImportCuesheet); + _sessionStateContainer.SetupSet(x => x.ImportCuesheet = It.IsAny()).Callback(cuesheet => sessionStateContainerImportCuesheet = cuesheet); Cuesheet sessionStateContainerCuesheet = new(); - _sessionStateContainerMock.SetupSet(x => x.Cuesheet = It.IsAny()).Callback(cuesheet => sessionStateContainerCuesheet = cuesheet); - _sessionStateContainerMock.SetupGet(x => x.Cuesheet).Returns(() => sessionStateContainerCuesheet); + _sessionStateContainer.SetupSet(x => x.Cuesheet = It.IsAny()).Callback(cuesheet => sessionStateContainerCuesheet = cuesheet); + _sessionStateContainer.SetupGet(x => x.Cuesheet).Returns(() => sessionStateContainerCuesheet); IList? sessionStateContainerImportAudiofiles = []; - _sessionStateContainerMock.SetupGet(x => x.ImportAudiofiles).Returns(() => sessionStateContainerImportAudiofiles); + _sessionStateContainer.SetupGet(x => x.ImportAudiofiles).Returns(() => sessionStateContainerImportAudiofiles); // Act - _service.ImportCuesheet(); + await _service.ImportCuesheetAsync(); // Assert Assert.IsNotNull(sessionStateContainerCuesheet); Assert.AreEqual(sessionStateContainerImportCuesheet.Artist, sessionStateContainerCuesheet.Artist); @@ -438,8 +450,8 @@ public void ImportCuesheet_WithImportCuesheetAvailable_ImportsCuesheetData() Assert.AreEqual(track2.Artist, sessionStateContainerCuesheet.Audiofiles.First().Tracks.Last().Artist); Assert.AreEqual(track2.Title, sessionStateContainerCuesheet.Audiofiles.First().Tracks.Last().Title); Assert.AreEqual(track1.End, sessionStateContainerCuesheet.Audiofiles.First().Tracks.Last().Begin); - Assert.AreEqual(track2.End, sessionStateContainerCuesheet.Audiofiles.First().Tracks.Last().End); - _traceChangeManagerMock.Verify(x => x.RemoveTracedChanges(It.IsAny>())); + Assert.AreEqual(audiofileDuration, sessionStateContainerCuesheet.Audiofiles.First().Tracks.Last().End); + _traceChangeManager.Verify(x => x.RemoveTracedChanges(It.IsAny>())); } [TestMethod] @@ -448,7 +460,7 @@ public void ImportData_ValidData_SetsImportfile() // Arrange var importData = nameof(ImportData_ValidData_SetsImportfile); IImportfile? sessionStateContainerImportfile = null; - _sessionStateContainerMock.SetupSet(x => x.Importfile = It.IsAny()).Callback(x => sessionStateContainerImportfile = x); + _sessionStateContainer.SetupSet(x => x.Importfile = It.IsAny()).Callback(x => sessionStateContainerImportfile = x); // Act _service.ImportData(importData); // Assert diff --git a/AudioCuesheetEditor.Tests/Services/UI/SessionStateContainerTests.cs b/AudioCuesheetEditor.Tests/Services/UI/SessionStateContainerTests.cs index 5188a1de..3d00bf25 100644 --- a/AudioCuesheetEditor.Tests/Services/UI/SessionStateContainerTests.cs +++ b/AudioCuesheetEditor.Tests/Services/UI/SessionStateContainerTests.cs @@ -14,10 +14,12 @@ //along with Foobar. If not, see //. using AudioCuesheetEditor.Model.AudioCuesheet; +using AudioCuesheetEditor.Model.IO.Audio; using AudioCuesheetEditor.Model.IO.Import; using AudioCuesheetEditor.Model.Options; using AudioCuesheetEditor.Services.Options; using AudioCuesheetEditor.Services.UI; +using Microsoft.JSInterop; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using System; @@ -30,11 +32,13 @@ public class SessionStateContainerTests { private readonly SessionStateContainer _sessionStateContainer; private readonly Mock _localStorageOptionsProvider; + private readonly Mock _jsRuntime; public SessionStateContainerTests() { _localStorageOptionsProvider = new(); - _sessionStateContainer = new(_localStorageOptionsProvider.Object); + _jsRuntime = new(); + _sessionStateContainer = new(_localStorageOptionsProvider.Object, _jsRuntime.Object); } [TestMethod] @@ -121,7 +125,7 @@ public async Task ImportCuesheet_SetNewValueInDetailView_ShouldTriggerActiveCues } [TestMethod] - public async Task ResetImport_InitializedService_ShouldClearPropertiesAsync() + public async Task ResetImportAsync_RevokeObjectUrls_ShouldClearPropertiesAsync() { // Arrange var viewOptions = new ViewOptions(); @@ -130,15 +134,86 @@ public async Task ResetImport_InitializedService_ShouldClearPropertiesAsync() _sessionStateContainer.Importfile = Mock.Of(); _sessionStateContainer.ImportCuesheet = new Cuesheet(); _sessionStateContainer.ImportIsAnalyzed = true; + var importAudiofile1 = new Audiofile() + { + ObjectURL = "File 1" + }; + var importAudiofile2 = new Audiofile() + { + ObjectURL = "File 2" + }; + _sessionStateContainer.ImportAudiofiles.Add(importAudiofile1); + _sessionStateContainer.ImportAudiofiles.Add(importAudiofile2); + var importAudiofile3 = new Audiofile() + { + ObjectURL = "File 3" + }; + var importAudiofile4 = new Audiofile() + { + ObjectURL = "File 4" + }; + _sessionStateContainer.ImportAudiofileMapping.Add(new(), importAudiofile3); + _sessionStateContainer.ImportAudiofileMapping.Add(new(), importAudiofile4); + + // Act + await _sessionStateContainer.ResetImportAsync(true); + + // Assert + Assert.IsNull(_sessionStateContainer.Importfile); + Assert.IsEmpty(_sessionStateContainer.ImportAudiofiles); + Assert.IsNull(_sessionStateContainer.ImportCuesheet); + Assert.IsFalse(_sessionStateContainer.ImportIsAnalyzed); + Assert.IsEmpty(_sessionStateContainer.ImportAudiofileMapping); + _jsRuntime.Verify(js => js.InvokeAsync("revokeAudioObjectURL", It.Is(args => args != null && args.Length > 0 && (args[0] as string) == importAudiofile1.ObjectURL)), Times.Once); + _jsRuntime.Verify(js => js.InvokeAsync("revokeAudioObjectURL", It.Is(args => args != null && args.Length > 0 && (args[0] as string) == importAudiofile2.ObjectURL)), Times.Once); + _jsRuntime.Verify(js => js.InvokeAsync("revokeAudioObjectURL", It.Is(args => args != null && args.Length > 0 && (args[0] as string) == importAudiofile3.ObjectURL)), Times.Once); + _jsRuntime.Verify(js => js.InvokeAsync("revokeAudioObjectURL", It.Is(args => args != null && args.Length > 0 && (args[0] as string) == importAudiofile4.ObjectURL)), Times.Once); + } + + [TestMethod] + public async Task ResetImportAsync_RevokeObjectUrlsNotSet_ShouldClearPropertiesAsync() + { + // Arrange + var viewOptions = new ViewOptions(); + _localStorageOptionsProvider.Setup(x => x.GetOptionsAsync()).ReturnsAsync(viewOptions); + await _sessionStateContainer.InitializeAsync(); + _sessionStateContainer.Importfile = Mock.Of(); + _sessionStateContainer.ImportCuesheet = new Cuesheet(); + _sessionStateContainer.ImportIsAnalyzed = true; + var importAudiofile1 = new Audiofile() + { + ObjectURL = "File 1" + }; + var importAudiofile2 = new Audiofile() + { + ObjectURL = "File 2" + }; + _sessionStateContainer.ImportAudiofiles.Add(importAudiofile1); + _sessionStateContainer.ImportAudiofiles.Add(importAudiofile2); + var importAudiofile3 = new Audiofile() + { + ObjectURL = "File 3" + }; + var importAudiofile4 = new Audiofile() + { + ObjectURL = "File 4" + }; + _sessionStateContainer.ImportAudiofileMapping.Add(new(), importAudiofile3); + _sessionStateContainer.ImportAudiofileMapping.Add(new(), importAudiofile4); // Act - _sessionStateContainer.ResetImport(); + await _sessionStateContainer.ResetImportAsync(false); // Assert Assert.IsNull(_sessionStateContainer.Importfile); Assert.IsEmpty(_sessionStateContainer.ImportAudiofiles); Assert.IsNull(_sessionStateContainer.ImportCuesheet); Assert.IsFalse(_sessionStateContainer.ImportIsAnalyzed); + Assert.IsEmpty(_sessionStateContainer.ImportAudiofileMapping); + _jsRuntime.Verify(js => js.InvokeAsync("revokeAudioObjectURL", It.Is(args => args != null && args.Length > 0 && (args[0] as string) == importAudiofile1.ObjectURL)), Times.Never); + _jsRuntime.Verify(js => js.InvokeAsync("revokeAudioObjectURL", It.Is(args => args != null && args.Length > 0 && (args[0] as string) == importAudiofile2.ObjectURL)), Times.Never); + _jsRuntime.Verify(js => js.InvokeAsync("revokeAudioObjectURL", It.Is(args => args != null && args.Length > 0 && (args[0] as string) == importAudiofile3.ObjectURL)), Times.Never); + _jsRuntime.Verify(js => js.InvokeAsync("revokeAudioObjectURL", It.Is(args => args != null && args.Length > 0 && (args[0] as string) == importAudiofile4.ObjectURL)), Times.Never); } [TestMethod] diff --git a/AudioCuesheetEditor.Tests/Services/UI/TraceChangeManagerTests.cs b/AudioCuesheetEditor.Tests/Services/UI/TraceChangeManagerTests.cs index 06613a8b..f1eeef6f 100644 --- a/AudioCuesheetEditor.Tests/Services/UI/TraceChangeManagerTests.cs +++ b/AudioCuesheetEditor.Tests/Services/UI/TraceChangeManagerTests.cs @@ -19,6 +19,7 @@ using AudioCuesheetEditor.Tests.Utility; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; +using System.Collections.Generic; using System.Linq; namespace AudioCuesheetEditor.Tests.Services.UI @@ -112,6 +113,40 @@ public void BulkEdit_AlreadyEnabledBulkEdit_DoesntDropPreviousEdits() Assert.IsTrue(_traceChangeManager.CanRedo); } + + [TestMethod()] + public void BulkEditFalse_AfterBulkEditAndUndo_ResetsRedoStack() + { + // Arrange + var audiofile = new Audiofile(); + var track1 = new Track(); + var track2 = new Track(); + var track3 = new Track(); + _traceChangeManager.BulkEdit = true; + _traceChangeManager.AddChange(new(audiofile, new(new List() { track1 }, nameof(Audiofile.Tracks)))); + track1.Audiofile = audiofile; + _traceChangeManager.BulkEdit = false; + _traceChangeManager.BulkEdit = true; + _traceChangeManager.AddChange(new(audiofile, new(new List() { track1, track2 }, nameof(Audiofile.Tracks)))); + track1.Audiofile = audiofile; + track2.Audiofile = audiofile; + _traceChangeManager.BulkEdit = false; + _traceChangeManager.Undo(); + _traceChangeManager.BulkEdit = true; + _traceChangeManager.AddChange(new(audiofile, new(new List() { track1, track2, track3 }, nameof(Audiofile.Tracks)))); + track1.Audiofile = audiofile; + track2.Audiofile = audiofile; + track3.Audiofile = audiofile; + _traceChangeManager.BulkEdit = false; + _traceChangeManager.BulkEdit = true; + _traceChangeManager.AddChange(new(track2, new("Testartist", nameof(Track.Artist)))); + // Act + _traceChangeManager.BulkEdit = false; + // Assert + Assert.IsTrue(_traceChangeManager.CanUndo); + Assert.IsFalse(_traceChangeManager.CanRedo); + } + [TestMethod()] public void Undo_SingleChange_UndoesChange() { diff --git a/AudioCuesheetEditor.sln b/AudioCuesheetEditor.sln index 40cbf088..73237fb1 100644 --- a/AudioCuesheetEditor.sln +++ b/AudioCuesheetEditor.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 -VisualStudioVersion = 18.3.11512.155 d18.3 +VisualStudioVersion = 18.3.11512.155 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudioCuesheetEditor", "AudioCuesheetEditor\AudioCuesheetEditor.csproj", "{099AE4DF-4847-460F-BBF2-207B9A35B58D}" EndProject diff --git a/AudioCuesheetEditor/AudioCuesheetEditor.csproj b/AudioCuesheetEditor/AudioCuesheetEditor.csproj index 0ee729b8..8c641a64 100644 --- a/AudioCuesheetEditor/AudioCuesheetEditor.csproj +++ b/AudioCuesheetEditor/AudioCuesheetEditor.csproj @@ -7,7 +7,7 @@ enable https://github.com/NeoCoderMatrix86/AudioCuesheetEditor Latest - 13.1.0 + 13.2.0 false true true @@ -30,13 +30,13 @@ - - + + - - - - + + + + diff --git a/AudioCuesheetEditor/Model/Entity/ValidationMessage.de.resx b/AudioCuesheetEditor/Model/Entity/ValidationMessage.de.resx index 2f6686f8..fe1d904c 100644 --- a/AudioCuesheetEditor/Model/Entity/ValidationMessage.de.resx +++ b/AudioCuesheetEditor/Model/Entity/ValidationMessage.de.resx @@ -216,4 +216,7 @@ {0}({1},{2},{3},{4},{5}) überlappt mit {0}({6},{7},{8},{9},{10}). Bitte stellen Sie sicher, dass die Zeitspanne nur einmal genutzt wird! + + Audio codec + \ No newline at end of file diff --git a/AudioCuesheetEditor/Model/Entity/ValidationMessage.resx b/AudioCuesheetEditor/Model/Entity/ValidationMessage.resx index 95e63d1c..6546ba73 100644 --- a/AudioCuesheetEditor/Model/Entity/ValidationMessage.resx +++ b/AudioCuesheetEditor/Model/Entity/ValidationMessage.resx @@ -216,4 +216,7 @@ {0}({1},{2},{3},{4},{5}) is overlapping with {0}({6},{7},{8},{9},{10}). Please make shure the timeinterval is only used once! + + Audio codec + \ No newline at end of file diff --git a/AudioCuesheetEditor/Pages/Index.razor b/AudioCuesheetEditor/Pages/Index.razor index a8b75db0..35741b3e 100644 --- a/AudioCuesheetEditor/Pages/Index.razor +++ b/AudioCuesheetEditor/Pages/Index.razor @@ -23,9 +23,10 @@ along with Foobar. If not, see @inject IStringLocalizer _localizer @inject ImportManager _importManager @inject ISessionStateContainer _sessionStateContainer -@inject DialogManager _dialogManager +@inject IDialogManager _dialogManager @inject ISnackbar _snackbar @inject ICuesheetManager _cuesheetManager +@inject IAudioImportService _audioImportService @@ -97,22 +98,29 @@ along with Foobar. If not, see }); } } + var switchToImportView = false; await _dialogManager.ShowLoadingDialogAsync(); switch (_sessionStateContainer.Importfile?.FileType) { case ImportFileType.ProjectFile: await _importManager.AnalyseImportfile(); - _sessionStateContainer.ResetImport(); + await _sessionStateContainer.ResetImportAsync(false); break; case ImportFileType.Cuesheet: await _importManager.AnalyseImportfile(); - _importManager.ImportCuesheet(); - _sessionStateContainer.ResetImport(); + await _importManager.ImportCuesheetAsync(); + await _sessionStateContainer.ResetImportAsync(false); break; case ImportFileType.Textfile: + switchToImportView = true; await LocalStorageOptionsProvider.SaveOptionsValueAsync(x => x.ActiveTab, ViewMode.ImportView); break; } + //In import view we analyze the text by user and ask for mapping afterwards + if (switchToImportView == false) + { + await _audioImportService.MapAudioImportAsync(); + } } finally { diff --git a/AudioCuesheetEditor/Program.cs b/AudioCuesheetEditor/Program.cs index 52932a92..38b84b89 100644 --- a/AudioCuesheetEditor/Program.cs +++ b/AudioCuesheetEditor/Program.cs @@ -55,12 +55,13 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddLogging(); // Read out configuration for loglevel diff --git a/AudioCuesheetEditor/Services/Audio/PlaybackService.cs b/AudioCuesheetEditor/Services/Audio/PlaybackService.cs index 2bd6d2f5..c27c0c2c 100644 --- a/AudioCuesheetEditor/Services/Audio/PlaybackService.cs +++ b/AudioCuesheetEditor/Services/Audio/PlaybackService.cs @@ -92,6 +92,7 @@ void AudiofileManager_AudiofileChanged(object? sender, Audiofile audiofile) void SessionStateContainer_CuesheetChanged(object? sender, EventArgs e) { _ = StopAsync(); + CurrentPositionChanged?.Invoke(); } public async Task PlayOrPauseAsync() diff --git a/AudioCuesheetEditor/Services/AudioCuesheet/AudiofileManager.cs b/AudioCuesheetEditor/Services/AudioCuesheet/AudiofileManager.cs index 1c0bd31d..37acac4d 100644 --- a/AudioCuesheetEditor/Services/AudioCuesheet/AudiofileManager.cs +++ b/AudioCuesheetEditor/Services/AudioCuesheet/AudiofileManager.cs @@ -36,44 +36,67 @@ public class AudiofileManager(IFileInputManager fileInputManager, ITraceChangeMa public event EventHandler? AudiofileChanged; /// - public async Task SetPropertiesAsync(Audiofile audiofile, IBrowserFile? browserFile, string fileInputId) + public async Task SetPropertiesAsync(Audiofile audiofile, IBrowserFile browserFile) { _traceChangeManager.BulkEdit = true; - if (browserFile == null) + if (String.IsNullOrEmpty(audiofile.ObjectURL) == false) { - if (string.IsNullOrEmpty(audiofile.ObjectURL) == false) - { - await _jsRuntime.InvokeVoidAsync("revokeAudioObjectURL", audiofile.ObjectURL); - } - SetValue(audiofile, x => x.AudioCodec, null); - SetValue(audiofile, x => x.Name, null); - SetValue(audiofile, x => x.ObjectURL, null); - SetValue(audiofile, x => x.Duration, null); + await ClearPropertiesAsync(audiofile, false); + } + var codec = _fileInputManager.GetAudioCodec(browserFile.Name, browserFile.ContentType); + var objectUrl = await _fileInputManager.GetObjectUrlAsync(browserFile); + TimeSpan? duration = null; + if (String.IsNullOrEmpty(objectUrl) == false) + { + var durationSeconds = await _jsRuntime.InvokeAsync("getAudioDurationFromFile", objectUrl); + duration = TimeSpan.FromSeconds(durationSeconds); + } + var codecChanged = SetValue(audiofile, x => x.AudioCodec, codec); + var nameChanged = SetValue(audiofile, x => x.Name, browserFile.Name); + var objectUrlChanged = SetValue(audiofile, x => x.ObjectURL, objectUrl); + var durationChanged = SetValue(audiofile, x => x.Duration, duration); + SetLastTrackEnd(audiofile); + _traceChangeManager.BulkEdit = false; + if (codecChanged || nameChanged || objectUrlChanged || durationChanged) + { + AudiofileChanged?.Invoke(this, audiofile); + } + } + + /// + public async Task ClearPropertiesAsync(Audiofile audiofile, Boolean setTracing = true) + { + if (setTracing) + { + _traceChangeManager.BulkEdit = true; } - else + if (string.IsNullOrEmpty(audiofile.ObjectURL) == false) { - var codec = _fileInputManager.GetAudioCodec(browserFile.ContentType, browserFile.Name); - var objectUrl = await _fileInputManager.GetObjectUrlAsync(fileInputId); - TimeSpan? duration = null; - if (String.IsNullOrEmpty(objectUrl) == false) + await _jsRuntime.InvokeVoidAsync("revokeAudioObjectURL", audiofile.ObjectURL); + } + var codecChanged = SetValue(audiofile, x => x.AudioCodec, null); + var nameChanged = SetValue(audiofile, x => x.Name, null); + var objectUrlChanged = SetValue(audiofile, x => x.ObjectURL, null); + var durationChanged = SetValue(audiofile, x => x.Duration, null); + if (setTracing) + { + _traceChangeManager.BulkEdit = false; + if (codecChanged || nameChanged || objectUrlChanged || durationChanged) { - var durationSeconds = await _jsRuntime.InvokeAsync("getAudioDurationFromFile", objectUrl); - duration = TimeSpan.FromSeconds(durationSeconds); + AudiofileChanged?.Invoke(this, audiofile); } - SetValue(audiofile, x => x.AudioCodec, codec); - SetValue(audiofile, x => x.Name, browserFile.Name); - SetValue(audiofile, x => x.ObjectURL, objectUrl); - SetValue(audiofile, x => x.Duration, duration); - SetLastTrackEnd(audiofile); } - _traceChangeManager.BulkEdit = false; } /// public void SetProperty(Audiofile audiofile, Expression> propertyExpression, TProperty value) { - SetValue(audiofile, propertyExpression, value); + var changed = SetValue(audiofile, propertyExpression, value); SetLastTrackEnd(audiofile); + if (changed) + { + AudiofileChanged?.Invoke(this, audiofile); + } } /// @@ -99,12 +122,16 @@ public void AddTrack(Audiofile audiofile, Track track, Boolean setTracing = true { track }; - SetValue(audiofile, x => x.Tracks, newValue); + var changed = SetValue(audiofile, x => x.Tracks, newValue); RecalculateTrackProperties(cuesheet!); if (setTracing) { _traceChangeManager.BulkEdit = false; } + if (changed) + { + AudiofileChanged?.Invoke(this, audiofile); + } } /// @@ -121,15 +148,19 @@ public void RemoveTracks(Audiofile audiofile, IEnumerable tracksToRemove, { _traceChangeManager.BulkEdit = true; } - SetValue(audiofile, x => x.Tracks, newValue); + var changed = SetValue(audiofile, x => x.Tracks, newValue); RecalculateTrackProperties(cuesheet!); if (setTracing) { _traceChangeManager.BulkEdit = false; } + if (changed) + { + AudiofileChanged?.Invoke(this, audiofile); + } } - void SetValue(Audiofile audiofile, Expression> propertyExpression, TProperty value) + Boolean SetValue(Audiofile audiofile, Expression> propertyExpression, TProperty value) { if (propertyExpression.Body is not MemberExpression memberExpression) { @@ -144,12 +175,12 @@ void SetValue(Audiofile audiofile, Expression public void SetProperty(Expression> propertyExpression, TProperty value) { - _traceChangeManager.BulkEdit = true; + var workingOnCuesheet = _sessionStateContainer.ActiveCuesheet == _sessionStateContainer.Cuesheet; + if (workingOnCuesheet) + { + _traceChangeManager.BulkEdit = true; + } var cuesheet = _sessionStateContainer.ActiveCuesheet; - SetValue(cuesheet!, propertyExpression, value); - _traceChangeManager.BulkEdit = false; + SetValue(cuesheet!, propertyExpression, value, workingOnCuesheet); + if (workingOnCuesheet) + { + _traceChangeManager.BulkEdit = false; + } } /// @@ -134,41 +141,50 @@ public bool IsMoveDownPossible(HashSet selectedAudiofiles) } /// - public Result MoveUp(HashSet selectedTracks) + public Result MoveUp(HashSet selectedTracks, uint positions = 1) { if (IsMoveUpPossible(selectedTracks) == false) { return Result.Failure(new Error(ErrorType.NotPossible, "Moving tracks up is not possible!")); } _traceChangeManager.BulkEdit = true; - var cuesheet = _sessionStateContainer.ActiveCuesheet; - foreach (var selectedTrack in selectedTracks.OrderBy(x => x.Position)) + while (positions > 0) { - var previousTrack = cuesheet?.Audiofiles.SelectMany(x => x.Tracks).FirstOrDefault(x => x.Position == selectedTrack.Position - 1); - if (previousTrack?.Audiofile != null && previousTrack.Audiofile != selectedTrack.Audiofile) + if (IsMoveUpPossible(selectedTracks) == false) { - SwitchAudiofile(selectedTrack, previousTrack.Audiofile); + positions = 0; + break; } - else + var cuesheet = _sessionStateContainer.ActiveCuesheet; + foreach (var selectedTrack in selectedTracks.OrderBy(x => x.Position)) { - var newBegin = previousTrack?.Begin; - var newEnd = previousTrack?.End; - if (previousTrack != null) + var previousTrack = cuesheet?.Audiofiles.SelectMany(x => x.Tracks).FirstOrDefault(x => x.Position == selectedTrack.Position - 1); + if (previousTrack?.Audiofile != null && previousTrack.Audiofile != selectedTrack.Audiofile) + { + SwitchAudiofile(selectedTrack, previousTrack.Audiofile); + } + else { - _trackManager.SetProperty(previousTrack, x => x.Position, selectedTrack.Position); - _trackManager.SetProperty(previousTrack, x => x.Begin, selectedTrack.Begin); - _trackManager.SetProperty(previousTrack, x => x.End, selectedTrack.End); + var newBegin = previousTrack?.Begin; + var newEnd = previousTrack?.End; + if (previousTrack != null) + { + _trackManager.SetProperty(previousTrack, x => x.Position, selectedTrack.Position); + _trackManager.SetProperty(previousTrack, x => x.Begin, selectedTrack.Begin); + _trackManager.SetProperty(previousTrack, x => x.End, selectedTrack.End); + } + _trackManager.SetProperty(selectedTrack, x => x.Position, (ushort?)(selectedTrack.Position - 1)); + _trackManager.SetProperty(selectedTrack, x => x.Begin, newBegin); + _trackManager.SetProperty(selectedTrack, x => x.End, newEnd); } - _trackManager.SetProperty(selectedTrack, x => x.Position, (ushort?)(selectedTrack.Position - 1)); - _trackManager.SetProperty(selectedTrack, x => x.Begin, newBegin); - _trackManager.SetProperty(selectedTrack, x => x.End, newEnd); + } - - } - foreach (var audiofile in cuesheet!.Audiofiles) - { - var orderedTracks = audiofile.Tracks.OrderBy(x => x.Position).ToList(); - _audiofileManager.SetProperty(audiofile, x => x.Tracks, orderedTracks); + foreach (var audiofile in cuesheet!.Audiofiles) + { + var orderedTracks = audiofile.Tracks.OrderBy(x => x.Position).ToList(); + _audiofileManager.SetProperty(audiofile, x => x.Tracks, orderedTracks); + } + positions--; } _traceChangeManager.BulkEdit = false; return Result.Success(); @@ -209,40 +225,49 @@ public Result MoveUp(HashSet selectedAudiofiles) } /// - public Result MoveDown(HashSet selectedTracks) + public Result MoveDown(HashSet selectedTracks, uint positions = 1) { if (IsMoveDownPossible(selectedTracks) == false) { return Result.Failure(new Error(ErrorType.NotPossible, "Moving tracks down is not possible!")); } _traceChangeManager.BulkEdit = true; - var cuesheet = _sessionStateContainer.ActiveCuesheet; - foreach (var selectedTrack in selectedTracks.OrderByDescending(x => x.Position)) + while (positions > 0) { - var nextTrack = cuesheet?.Audiofiles.SelectMany(x => x.Tracks).FirstOrDefault(x => x.Position == selectedTrack.Position + 1); - if (nextTrack?.Audiofile != null && nextTrack.Audiofile != selectedTrack.Audiofile) + if (IsMoveDownPossible(selectedTracks) == false) { - SwitchAudiofile(selectedTrack, nextTrack.Audiofile); + positions = 0; + break; } - else + var cuesheet = _sessionStateContainer.ActiveCuesheet; + foreach (var selectedTrack in selectedTracks.OrderByDescending(x => x.Position)) { - var newBegin = nextTrack?.Begin; - var newEnd = nextTrack?.End; - if (nextTrack != null) + var nextTrack = cuesheet?.Audiofiles.SelectMany(x => x.Tracks).FirstOrDefault(x => x.Position == selectedTrack.Position + 1); + if (nextTrack?.Audiofile != null && nextTrack.Audiofile != selectedTrack.Audiofile) { - _trackManager.SetProperty(nextTrack, x => x.Position, selectedTrack.Position); - _trackManager.SetProperty(nextTrack, x => x.Begin, selectedTrack.Begin); - _trackManager.SetProperty(nextTrack, x => x.End, selectedTrack.End); + SwitchAudiofile(selectedTrack, nextTrack.Audiofile); + } + else + { + var newBegin = nextTrack?.Begin; + var newEnd = nextTrack?.End; + if (nextTrack != null) + { + _trackManager.SetProperty(nextTrack, x => x.Position, selectedTrack.Position); + _trackManager.SetProperty(nextTrack, x => x.Begin, selectedTrack.Begin); + _trackManager.SetProperty(nextTrack, x => x.End, selectedTrack.End); + } + _trackManager.SetProperty(selectedTrack, x => x.Position, (ushort?)(selectedTrack.Position + 1)); + _trackManager.SetProperty(selectedTrack, x => x.Begin, newBegin); + _trackManager.SetProperty(selectedTrack, x => x.End, newEnd); } - _trackManager.SetProperty(selectedTrack, x => x.Position, (ushort?)(selectedTrack.Position + 1)); - _trackManager.SetProperty(selectedTrack, x => x.Begin, newBegin); - _trackManager.SetProperty(selectedTrack, x => x.End, newEnd); } - } - foreach (var audiofile in cuesheet!.Audiofiles) - { - var orderedTracks = audiofile.Tracks.OrderBy(x => x.Position).ToList(); - _audiofileManager.SetProperty(audiofile, x => x.Tracks, orderedTracks); + foreach (var audiofile in cuesheet!.Audiofiles) + { + var orderedTracks = audiofile.Tracks.OrderBy(x => x.Position).ToList(); + _audiofileManager.SetProperty(audiofile, x => x.Tracks, orderedTracks); + } + positions--; } _traceChangeManager.BulkEdit = false; return Result.Success(); @@ -282,7 +307,7 @@ public Result MoveDown(HashSet selectedAudiofiles) return Result.Success(); } - void SetValue(Cuesheet cuesheet, Expression> propertyExpression, TProperty value) + void SetValue(Cuesheet cuesheet, Expression> propertyExpression, TProperty value, Boolean traceChanges = true) { if (propertyExpression.Body is not MemberExpression memberExpression) { @@ -301,8 +326,10 @@ void SetValue(Cuesheet cuesheet, Expression } propertyInfo.SetValue(cuesheet, value); - - _traceChangeManager.AddChange(new(cuesheet, new(previousValue, propertyInfo.Name))); + if (traceChanges) + { + _traceChangeManager.AddChange(new(cuesheet, new(previousValue, propertyInfo.Name))); + } _ = RevokeObjectUrlOfRemovedAudiofilesAsync(cuesheet, propertyInfo, previousValue); } diff --git a/AudioCuesheetEditor/Services/AudioCuesheet/IAudiofileManager.cs b/AudioCuesheetEditor/Services/AudioCuesheet/IAudiofileManager.cs index 672e938f..6cf526cb 100644 --- a/AudioCuesheetEditor/Services/AudioCuesheet/IAudiofileManager.cs +++ b/AudioCuesheetEditor/Services/AudioCuesheet/IAudiofileManager.cs @@ -31,8 +31,14 @@ public interface IAudiofileManager /// /// /// - /// - Task SetPropertiesAsync(Audiofile audiofile, IBrowserFile? browserFile, string fileInputId); + Task SetPropertiesAsync(Audiofile audiofile, IBrowserFile browserFile); + /// + /// Clear properties of an audiofile + /// + /// + /// Parameter controlling if tracing should be handled by this service or by calling services + /// + Task ClearPropertiesAsync(Audiofile audiofile, Boolean setTracing = true); /// /// Set property for an audio file /// diff --git a/AudioCuesheetEditor/Services/AudioCuesheet/ICuesheetManager.cs b/AudioCuesheetEditor/Services/AudioCuesheet/ICuesheetManager.cs index bb4daed6..66631da8 100644 --- a/AudioCuesheetEditor/Services/AudioCuesheet/ICuesheetManager.cs +++ b/AudioCuesheetEditor/Services/AudioCuesheet/ICuesheetManager.cs @@ -78,7 +78,9 @@ public interface ICuesheetManager /// Moves selected tracks up /// /// - Result MoveUp(HashSet selectedTracks); + /// + /// + Result MoveUp(HashSet selectedTracks, uint positions = 1); /// /// Moves selected audiofiles up /// @@ -89,8 +91,9 @@ public interface ICuesheetManager /// Moves selected tracks down /// /// + /// /// - Result MoveDown(HashSet selectedTracks); + Result MoveDown(HashSet selectedTracks, uint positions = 1); /// /// Moves selected audiofiles down /// diff --git a/AudioCuesheetEditor/Services/IO/AudioImportService.cs b/AudioCuesheetEditor/Services/IO/AudioImportService.cs new file mode 100644 index 00000000..bf44fb64 --- /dev/null +++ b/AudioCuesheetEditor/Services/IO/AudioImportService.cs @@ -0,0 +1,91 @@ +//This file is part of AudioCuesheetEditor. + +//AudioCuesheetEditor is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. + +//AudioCuesheetEditor is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. + +//You should have received a copy of the GNU General Public License +//along with Foobar. If not, see +//. +using AudioCuesheetEditor.Model.IO.Audio; +using AudioCuesheetEditor.Services.AudioCuesheet; +using AudioCuesheetEditor.Services.UI; +using MudBlazor; + +namespace AudioCuesheetEditor.Services.IO +{ + /// + public class AudioImportService(IDialogManager dialogManager, ISessionStateContainer sessionStateContainer, ICuesheetManager cuesheetManager) : IAudioImportService + { + private readonly ISessionStateContainer _sessionStateContainer = sessionStateContainer; + private readonly IDialogManager _dialogManager = dialogManager; + private readonly ICuesheetManager _cuesheetManager = cuesheetManager; + + /// + public async Task MapAudioImportAsync() + { + // Just run an import if there are audiofiles to import, otherwise this service has nothing to do + if (_sessionStateContainer.ImportAudiofiles.Count == 0) + { + return true; + } + var mappingRequired = MapImportfiles(); + var cancel = false; + if (mappingRequired) + { + cancel = await _dialogManager.ShowImportAudiofilesDialogAsync(); + } + if (cancel == false) + { + var files = new List(_sessionStateContainer.ActiveCuesheet!.Audiofiles); + foreach (var mapping in _sessionStateContainer.ImportAudiofileMapping.Where(x => x.Value != null)) + { + var audiofile = files.FirstOrDefault(x => x.Equals(mapping.Key)); + if (audiofile == null) + { + audiofile = mapping.Key; + files.Add(audiofile); + } + audiofile.ObjectURL = mapping.Value!.ObjectURL; + audiofile.AudioCodec = mapping.Value!.AudioCodec; + audiofile.Duration = mapping.Value!.Duration; + audiofile.Name = mapping.Value!.Name; + } + _cuesheetManager.SetProperty(x => x.Audiofiles, files); + } + else + { + await _sessionStateContainer.ResetAudioImportAsync(cancel); + } + return cancel == false; + } + + Boolean MapImportfiles() + { + _sessionStateContainer.ImportAudiofileMapping.Clear(); + foreach (var audiofile in _sessionStateContainer.ActiveCuesheet!.Audiofiles) + { + var importAudiofile = _sessionStateContainer.ImportAudiofiles.FirstOrDefault(); + if (string.IsNullOrEmpty(audiofile.Name) || audiofile.Name == importAudiofile?.Name) + { + if (importAudiofile != null) + { + _sessionStateContainer.ImportAudiofiles.Remove(importAudiofile); + _sessionStateContainer.ImportAudiofileMapping.Add(audiofile, importAudiofile); + } + } + else + { + _sessionStateContainer.ImportAudiofileMapping.Add(audiofile, null); + } + } + return _sessionStateContainer.ImportAudiofiles.Count > 0; + } + } +} diff --git a/AudioCuesheetEditor/Services/IO/FileInputManager.cs b/AudioCuesheetEditor/Services/IO/FileInputManager.cs index 160a7701..1ba191ab 100644 --- a/AudioCuesheetEditor/Services/IO/FileInputManager.cs +++ b/AudioCuesheetEditor/Services/IO/FileInputManager.cs @@ -26,7 +26,7 @@ public class FileInputManager(IJSRuntime jsRuntime, ILogger lo private readonly IJSRuntime _jsRuntime = jsRuntime; private readonly ILogger _logger = logger; - public AudioCodec? GetAudioCodec(string? fileContentType, string fileName) + public AudioCodec? GetAudioCodec(string fileName, string? fileContentType = null) { AudioCodec? foundAudioCodec = null; var extension = Path.GetExtension(fileName); @@ -46,14 +46,14 @@ public class FileInputManager(IJSRuntime jsRuntime, ILogger lo } /// - public async Task GetObjectUrlAsync(string fileInputId) + public async Task GetObjectUrlAsync(IBrowserFile browserFile) { - return await _jsRuntime.InvokeAsync("getObjectURLFromMudFileUpload", fileInputId); + return await _jsRuntime.InvokeAsync("getObjectURLFromMudFileUpload", browserFile.Name, browserFile.Size, browserFile.ContentType, browserFile.LastModified.ToUnixTimeMilliseconds()); } - public bool IsValidAudiofile(string? fileContentType, string fileName) + public bool IsValidAudiofile(string fileName, string? fileContentType = null) { - return GetAudioCodec(fileContentType, fileName) != null; + return GetAudioCodec(fileName, fileContentType) != null; } public bool CheckFileMimeType(string? fileContentType, string fileName, string mimeType, IEnumerable fileExtensions) @@ -89,25 +89,22 @@ public bool CheckFileMimeType(string? fileContentType, string fileName, string m public async Task CreateAudiofileAsync(FileUpload fileUpload) { - Audiofile? audiofile = null; - if (fileUpload.ObjectUrl != null) + // Check file mime type + var codec = GetAudioCodec(fileUpload.Name, fileUpload.ContentType); + Audiofile? audiofile; + if (codec != null) { - // Check file mime type - var codec = GetAudioCodec(fileUpload.ContentType, fileUpload.Name); - if (codec != null) + TimeSpan? duration = null; + if (String.IsNullOrEmpty(fileUpload.ObjectUrl) == false) { - TimeSpan? duration = null; - if (String.IsNullOrEmpty(fileUpload.ObjectUrl) == false) - { - var durationSeconds = await _jsRuntime.InvokeAsync("getAudioDurationFromFile", fileUpload.ObjectUrl); - duration = TimeSpan.FromSeconds(durationSeconds); - } - audiofile = new Audiofile(fileUpload.Name, fileUpload.ObjectUrl, codec, duration); - } - else - { - throw new ArgumentException("The audiofile provided is not of a valid type."); + var durationSeconds = await _jsRuntime.InvokeAsync("getAudioDurationFromFile", fileUpload.ObjectUrl); + duration = TimeSpan.FromSeconds(durationSeconds); } + audiofile = new Audiofile(fileUpload.Name, fileUpload.ObjectUrl, codec, duration); + } + else + { + throw new ArgumentException("The audiofile provided is not of a valid type."); } return audiofile; } @@ -140,7 +137,7 @@ public async Task ReadFileContentAsync(IBrowserFile browserFile) } /// - public async Task> CreateFileUploadsAsync(IReadOnlyList browserFiles, string? fileInputId = null) + public async Task> CreateFileUploadsAsync(IReadOnlyList browserFiles) { List fileUploads = []; foreach (var file in browserFiles) @@ -148,13 +145,13 @@ public async Task> CreateFileUploadsAsync(IReadOnlyList< if (CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Projectfile, [FileExtensions.Projectfile]) || CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Cuesheet, [FileExtensions.Cuesheet]) || IsValidForImportView(file.ContentType, file.Name) - || IsValidAudiofile(file.ContentType, file.Name)) + || IsValidAudiofile(file.Name, file.ContentType)) { string? content = null; string? objectUrl = null; - if (IsValidAudiofile(file.ContentType, file.Name)) + if (IsValidAudiofile(file.Name, file.ContentType)) { - objectUrl = await _jsRuntime.InvokeAsync("getObjectURLFromMudFileUpload", fileInputId); + objectUrl = await GetObjectUrlAsync(file); } else { diff --git a/AudioCuesheetEditor/Services/IO/IAudioImportService.cs b/AudioCuesheetEditor/Services/IO/IAudioImportService.cs new file mode 100644 index 00000000..f01d34b6 --- /dev/null +++ b/AudioCuesheetEditor/Services/IO/IAudioImportService.cs @@ -0,0 +1,29 @@ +//This file is part of AudioCuesheetEditor. + +//AudioCuesheetEditor is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. + +//AudioCuesheetEditor is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. + +//You should have received a copy of the GNU General Public License +//along with Foobar. If not, see +//. +namespace AudioCuesheetEditor.Services.IO +{ + /// + /// Service for importing audio files and matching them to the cuesheet. + /// + public interface IAudioImportService + { + /// + /// Maps the import audio from session state to the active cuesheet. If mapping is not possible automatically the user gets a dialog. + /// + /// + Task MapAudioImportAsync(); + } +} diff --git a/AudioCuesheetEditor/Services/IO/IFileInputManager.cs b/AudioCuesheetEditor/Services/IO/IFileInputManager.cs index e5f4ff39..95383d55 100644 --- a/AudioCuesheetEditor/Services/IO/IFileInputManager.cs +++ b/AudioCuesheetEditor/Services/IO/IFileInputManager.cs @@ -23,14 +23,14 @@ namespace AudioCuesheetEditor.Services.IO { public interface IFileInputManager { - bool IsValidAudiofile(string? fileContentType, string fileName); - AudioCodec? GetAudioCodec(string? fileContentType, string fileName); + bool IsValidAudiofile(string fileName, string? fileContentType = null); + AudioCodec? GetAudioCodec(string fileName, string? fileContentType = null); /// - /// Get object url from a mud file upload + /// Get object url for an IBrowserFile. /// - /// + /// /// - Task GetObjectUrlAsync(string fileInputId); + Task GetObjectUrlAsync(IBrowserFile browserFile); /// /// Checks if a file content type and name matches given parameters /// @@ -58,8 +58,7 @@ public interface IFileInputManager /// Generates file upload references for files /// /// - /// /// - Task> CreateFileUploadsAsync(IReadOnlyList browserFiles, string? fileInputId = null); + Task> CreateFileUploadsAsync(IReadOnlyList browserFiles); } } \ No newline at end of file diff --git a/AudioCuesheetEditor/Services/IO/ImportManager.cs b/AudioCuesheetEditor/Services/IO/ImportManager.cs index 6637210f..8294d69c 100644 --- a/AudioCuesheetEditor/Services/IO/ImportManager.cs +++ b/AudioCuesheetEditor/Services/IO/ImportManager.cs @@ -13,7 +13,6 @@ //You should have received a copy of the GNU General Public License //along with Foobar. If not, see //. -using AngleSharp.Media.Dom; using AudioCuesheetEditor.Model.AudioCuesheet; using AudioCuesheetEditor.Model.AudioCuesheet.Import; using AudioCuesheetEditor.Model.IO; @@ -34,7 +33,7 @@ public enum ImportFileType Textfile, Audiofile } - public class ImportManager(ISessionStateContainer sessionStateContainer, ITraceChangeManager traceChangeManager, IFileInputManager fileInputManager, ITextImportService textImportService, ITrackManager trackManager, ILogger logger) + public class ImportManager(ISessionStateContainer sessionStateContainer, ITraceChangeManager traceChangeManager, IFileInputManager fileInputManager, ITextImportService textImportService, ITrackManager trackManager, IAudioImportService audioImportService, ILogger logger) { public event EventHandler>? UploadFilesFinished; @@ -44,6 +43,7 @@ public class ImportManager(ISessionStateContainer sessionStateContainer, ITraceC private readonly IFileInputManager _fileInputManager = fileInputManager; private readonly ITextImportService _textImportService = textImportService; private readonly ITrackManager _trackManager = trackManager; + private readonly IAudioImportService _audioImportService = audioImportService; public void ImportData(String? data) { @@ -61,7 +61,7 @@ public void ImportData(String? data) } } - public async Task AnalyseImportfile() + public async Task AnalyseImportfile() { ResetTracing(); var stopwatch = Stopwatch.StartNew(); @@ -96,14 +96,16 @@ public async Task AnalyseImportfile() break; } } + var cancelImport = await _audioImportService.MapAudioImportAsync(); stopwatch.Stop(); if (_logger.IsEnabled(LogLevel.Debug)) { _logger.LogDebug("AnalyseImportfile duration: {stopwatch.Elapsed}", stopwatch.Elapsed); } + return cancelImport; } - public void ImportCuesheet() + public async Task ImportCuesheetAsync() { var stopwatch = Stopwatch.StartNew(); ResetTracing(); @@ -115,7 +117,7 @@ public void ImportCuesheet() _sessionStateContainer.Cuesheet = newCuesheet; _traceChangeManager.AddChange(new TracedChange(_sessionStateContainer, new(previousValue, nameof(SessionStateContainer.Cuesheet)))); } - _sessionStateContainer.ResetImport(); + await _sessionStateContainer.ResetImportAsync(false); stopwatch.Stop(); if (_logger.IsEnabled(LogLevel.Debug)) { @@ -132,7 +134,7 @@ public async Task UploadFilesAsync(IEnumerable files) if (_fileInputManager.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Projectfile, [FileExtensions.Projectfile]) || _fileInputManager.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Cuesheet, [FileExtensions.Cuesheet]) || _fileInputManager.IsValidForImportView(file.ContentType, file.Name) - || _fileInputManager.IsValidAudiofile(file.ContentType, file.Name)) + || _fileInputManager.IsValidAudiofile(file.Name, file.ContentType)) { if (_fileInputManager.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Projectfile, [FileExtensions.Projectfile])) { @@ -161,7 +163,7 @@ public async Task UploadFilesAsync(IEnumerable files) FileType = ImportFileType.Textfile }; } - if (_fileInputManager.IsValidAudiofile(file.ContentType, file.Name)) + if (_fileInputManager.IsValidAudiofile(file.Name, file.ContentType)) { var audioFile = await _fileInputManager.CreateAudiofileAsync(file); if (audioFile != null) @@ -188,10 +190,11 @@ void CopyCuesheet(Cuesheet target, ICuesheet cuesheetToCopy) target.Artist = cuesheetToCopy.Artist; target.Title = cuesheetToCopy.Title; target.Cataloguenumber = cuesheetToCopy.Cataloguenumber; + IEnumerable audiofiles = []; if (cuesheetToCopy is Cuesheet originCuesheet) { target.CDTextfile = originCuesheet.CDTextfile; - AttachClonedAudiofiles(target, originCuesheet.Audiofiles); + audiofiles = originCuesheet.Audiofiles; } if (cuesheetToCopy is ImportCuesheet importCuesheet) { @@ -199,8 +202,9 @@ void CopyCuesheet(Cuesheet target, ICuesheet cuesheetToCopy) { target.CDTextfile = new CDTextfile(importCuesheet.CDTextfile); } - AttachClonedAudiofiles(target, importCuesheet.Audiofiles); + audiofiles = importCuesheet.Audiofiles; } + AttachClonedAudiofiles(target, audiofiles); } void AttachClonedAudiofiles(Cuesheet target, IEnumerable audiofiles) @@ -220,6 +224,7 @@ void AttachClonedAudiofiles(Cuesheet target, IEnumerable audiofiles) targetAudiofile ??= new Audiofile() { Name = importAudiofile.Name, + AudioCodec = String.IsNullOrEmpty(importAudiofile.Name) == false ? _fileInputManager.GetAudioCodec(importAudiofile.Name) : null }; tracks = importAudiofile.Tracks; } @@ -294,6 +299,12 @@ void AttachClonedAudiofiles(Cuesheet target, IEnumerable audiofiles) position++; targetAudiofile.Tracks.Add(clone); } + //Set last track end + var lastTrack = targetAudiofile.Tracks.LastOrDefault(); + if ((lastTrack?.End.HasValue == false) && (targetAudiofile.Duration.HasValue == true)) + { + _trackManager.SetProperty(lastTrack, x => x.End, targetAudiofile.Duration); + } target.Audiofiles.Add(targetAudiofile); } } diff --git a/AudioCuesheetEditor/Services/Result.cs b/AudioCuesheetEditor/Services/Result.cs index fac41830..565b1fe0 100644 --- a/AudioCuesheetEditor/Services/Result.cs +++ b/AudioCuesheetEditor/Services/Result.cs @@ -31,7 +31,11 @@ public enum ErrorType /// /// Current action is not possible /// - NotPossible + NotPossible, + /// + /// Action was aborted + /// + Aborted, } /// diff --git a/AudioCuesheetEditor/Services/UI/DialogManager.cs b/AudioCuesheetEditor/Services/UI/DialogManager.cs index a98841fa..f8712578 100644 --- a/AudioCuesheetEditor/Services/UI/DialogManager.cs +++ b/AudioCuesheetEditor/Services/UI/DialogManager.cs @@ -14,6 +14,7 @@ //along with Foobar. If not, see //. using AudioCuesheetEditor.Model.AudioCuesheet; +using AudioCuesheetEditor.Model.IO.Audio; using AudioCuesheetEditor.Model.UI; using AudioCuesheetEditor.Services.AudioCuesheet; using AudioCuesheetEditor.Shared.Dialogs; @@ -21,11 +22,12 @@ namespace AudioCuesheetEditor.Services.UI { - public class DialogManager(IDialogService dialogService, ITraceChangeManager traceChangeManager, ITrackManager trackManager) + public class DialogManager(IDialogService dialogService, ITraceChangeManager traceChangeManager, ITrackManager trackManager, ISessionStateContainer sessionStateContainer) : IDialogManager { private readonly IDialogService _dialogService = dialogService; private readonly ITraceChangeManager _traceChangeManager = traceChangeManager; private readonly ITrackManager _trackManager = trackManager; + private readonly ISessionStateContainer _sessionStateContainer = sessionStateContainer; private IDialogReference? _loadingDialog; @@ -192,7 +194,7 @@ public async Task ShowAndHandleModalEditDialogAsync(IEnumerable tracks) public async Task ShowLoadingDialogAsync() { if (_loadingDialog == null) - { + { var options = new DialogOptions() { BackdropClick = false, FullWidth = true, MaxWidth = MaxWidth.ExtraSmall, NoHeader = true }; _loadingDialog = await _dialogService.ShowAsync(options); await Task.Delay(1); @@ -204,5 +206,51 @@ public void HideLoadingDialog() _loadingDialog?.Close(); _loadingDialog = null; } + + public async Task ShowImportAudiofilesDialogAsync() + { + var parameters = new DialogParameters + { + { x => x.MappedAudiofiles, _sessionStateContainer.ImportAudiofileMapping }, + { x => x.ImportAudiofiles, _sessionStateContainer.ImportAudiofiles } + }; + var options = new DialogOptions() { BackdropClick = false, FullWidth = true }; + var dialog = await _dialogService.ShowAsync(null, parameters, options); + var result = await dialog.Result; + var canceled = result?.Canceled ?? true; + if (canceled == false) + { + if (result?.Data is Dictionary audiofileMapping) + { + _sessionStateContainer.ImportAudiofileMapping = audiofileMapping; + } + } + return canceled; + } + + /// + public async Task> ShowInputDialogAsync(string? title, string? placeholder, string? label, T initialValue, string? OkText = "Ok", string? OkButtonStartIcon = null) + { + var parameters = new DialogParameters> + { + { x => x.Placeholder, placeholder }, + { x => x.Label, label }, + { x => x.InitialValue, initialValue }, + { x => x.OkText, OkText }, + { x => x.OkButtonStartIcon, OkButtonStartIcon } + }; + var options = new DialogOptions() { CloseOnEscapeKey = true, BackdropClick = false, FullWidth = true, CloseButton = true }; + var dialog = await _dialogService.ShowAsync>(title, parameters, options); + var result = await dialog.Result; + if (result?.Canceled == false) + { + var value = (T)result.Data!; + return Result.Success(value); + } + else + { + return Result.Failure(new Error(ErrorType.Aborted, "User canceled the input dialog.")); + } + } } } diff --git a/AudioCuesheetEditor/Services/UI/IDialogManager.cs b/AudioCuesheetEditor/Services/UI/IDialogManager.cs new file mode 100644 index 00000000..1394af97 --- /dev/null +++ b/AudioCuesheetEditor/Services/UI/IDialogManager.cs @@ -0,0 +1,40 @@ +//This file is part of AudioCuesheetEditor. + +//AudioCuesheetEditor is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. + +//AudioCuesheetEditor is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. + +//You should have received a copy of the GNU General Public License +//along with Foobar. If not, see +//. + +using AudioCuesheetEditor.Model.AudioCuesheet; + +namespace AudioCuesheetEditor.Services.UI +{ + public interface IDialogManager + { + void HideLoadingDialog(); + Task ShowAndHandleModalEditDialogAsync(IEnumerable tracks); + Task ShowImportAudiofilesDialogAsync(); + Task ShowLoadingDialogAsync(); + /// + /// Show input dialog and wait for user input. + /// + /// + /// + /// + /// + /// + /// + /// + /// + Task> ShowInputDialogAsync(string? title, string? placeholder, string? label, T initialValue, string? OkText = "Ok", string? OkButtonStartIcon = null); + } +} \ No newline at end of file diff --git a/AudioCuesheetEditor/Services/UI/ISessionStateContainer.cs b/AudioCuesheetEditor/Services/UI/ISessionStateContainer.cs index 0e68b86b..8267e2ed 100644 --- a/AudioCuesheetEditor/Services/UI/ISessionStateContainer.cs +++ b/AudioCuesheetEditor/Services/UI/ISessionStateContainer.cs @@ -26,9 +26,11 @@ public interface ISessionStateContainer public Cuesheet Cuesheet { get; set; } public Cuesheet? ImportCuesheet { get; set; } public IList ImportAudiofiles { get; set; } + public Dictionary ImportAudiofileMapping { get; set; } public IImportfile? Importfile { get; set; } public Boolean ImportIsAnalyzed { get; set; } - public void ResetImport(); + public Task ResetImportAsync(Boolean revokeObjectURLs); + public Task ResetAudioImportAsync(Boolean revokeObjectURLs); /// /// Initialize this class /// diff --git a/AudioCuesheetEditor/Services/UI/SessionStateContainer.cs b/AudioCuesheetEditor/Services/UI/SessionStateContainer.cs index 4e5d6cb7..3a8caa84 100644 --- a/AudioCuesheetEditor/Services/UI/SessionStateContainer.cs +++ b/AudioCuesheetEditor/Services/UI/SessionStateContainer.cs @@ -18,12 +18,14 @@ using AudioCuesheetEditor.Model.IO.Import; using AudioCuesheetEditor.Model.Options; using AudioCuesheetEditor.Services.Options; +using Microsoft.JSInterop; namespace AudioCuesheetEditor.Services.UI { public class SessionStateContainer : ISessionStateContainer, IDisposable { private readonly ILocalStorageOptionsProvider _localStorageOptionsProvider; + private readonly IJSRuntime _jsRuntime; private Cuesheet _cuesheet = new(); private Cuesheet? _importCuesheet; @@ -34,10 +36,11 @@ public class SessionStateContainer : ISessionStateContainer, IDisposable public event EventHandler? CuesheetChanged; public event EventHandler? ActiveCuesheetChanged; - public SessionStateContainer(ILocalStorageOptionsProvider localStorageOptionsProvider) + public SessionStateContainer(ILocalStorageOptionsProvider localStorageOptionsProvider, IJSRuntime jsRuntime) { _localStorageOptionsProvider = localStorageOptionsProvider; _localStorageOptionsProvider.OptionSaved += LocalStorageOptionsProvider_OptionSaved; + _jsRuntime = jsRuntime; } public void Dispose() @@ -66,6 +69,7 @@ public Cuesheet? ImportCuesheet } } public IList ImportAudiofiles { get; set; } = []; + public Dictionary ImportAudiofileMapping { get; set; } = []; public IImportfile? Importfile { get; set; } public Boolean ImportIsAnalyzed { get; set; } = false; @@ -76,14 +80,37 @@ public async Task InitializeAsync() SetActiveCuesheet(); } - public void ResetImport() + public async Task ResetImportAsync(Boolean revokeObjectURLs) { Importfile = null; - ImportAudiofiles = []; + await ResetAudioImportAsync(revokeObjectURLs); ImportCuesheet = null; ImportIsAnalyzed = false; } + public async Task ResetAudioImportAsync(bool revokeObjectURLs) + { + if (revokeObjectURLs) + { + foreach (var audiofile in ImportAudiofiles) + { + if (!string.IsNullOrEmpty(audiofile.ObjectURL)) + { + await _jsRuntime.InvokeVoidAsync("revokeAudioObjectURL", audiofile.ObjectURL); + } + } + foreach (var audiofile in ImportAudiofileMapping.Values.Where(x => x != null)) + { + if (!string.IsNullOrEmpty(audiofile!.ObjectURL)) + { + await _jsRuntime.InvokeVoidAsync("revokeAudioObjectURL", audiofile.ObjectURL); + } + } + } + ImportAudiofileMapping = []; + ImportAudiofiles = []; + } + /// public Cuesheet? ActiveCuesheet => _activeCuesheet; @@ -131,5 +158,6 @@ void SetActiveCuesheet() } } } + } } diff --git a/AudioCuesheetEditor/Services/UI/TraceChangeManager.cs b/AudioCuesheetEditor/Services/UI/TraceChangeManager.cs index 79a53ef1..192c3866 100644 --- a/AudioCuesheetEditor/Services/UI/TraceChangeManager.cs +++ b/AudioCuesheetEditor/Services/UI/TraceChangeManager.cs @@ -175,6 +175,7 @@ public bool BulkEdit if (_bulkEditTracedChanges?.Count > 0) { _undoStack.Add(new TracedChanges(_bulkEditTracedChanges)); + _redoStack.Clear(); TracedObjectHistoryChanged?.Invoke(this, EventArgs.Empty); } _bulkEditTracedChanges = null; diff --git a/AudioCuesheetEditor/Shared/AppBar.razor b/AudioCuesheetEditor/Shared/AppBar.razor index 5c8c03f6..392f6c76 100644 --- a/AudioCuesheetEditor/Shared/AppBar.razor +++ b/AudioCuesheetEditor/Shared/AppBar.razor @@ -37,10 +37,10 @@ along with Foobar. If not, see { - + - + } @@ -82,8 +82,8 @@ along with Foobar. If not, see @if (DisplayReset) { - @_localizer["Reset cuesheet"] - @_localizer["Reset application"] + @_localizer["Reset cuesheet"] + @_localizer["Reset application"] } @@ -97,8 +97,8 @@ along with Foobar. If not, see { base.OnInitialized(); _hotKeysContext = _hotKeys.CreateContext() - .Add(ModKey.Ctrl, Key.z, () => TraceChangeManager.Undo()) - .Add(ModKey.Ctrl, Key.y, () => TraceChangeManager.Redo()) + .Add(ModKey.Ctrl, Key.z, TraceChangeManager.Undo) + .Add(ModKey.Ctrl, Key.y, TraceChangeManager.Redo) .Add(ModKey.Ctrl, Key.s, DownloadProjectfileClicked) .Add(ModKey.Ctrl, Key.e, DownloadExportClicked) .Add(ModKey.Ctrl, Key.u, DownloadCuesheetClicked) diff --git a/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.de.resx b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.de.resx index 1735ab5d..1b75969a 100644 --- a/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.de.resx +++ b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.de.resx @@ -129,6 +129,9 @@ Neuer Dateiname + + Keine Datei hochgeladen! + Validierungsfehler diff --git a/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor index 29222814..ddf19bd9 100644 --- a/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor +++ b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor @@ -20,23 +20,15 @@ along with Foobar. If not, see @inject IStringLocalizer _localizer @inject IStringLocalizer _validationMessageLocalizer @inject IAudiofileManager _audiofileManager -@inject IDialogService _dialogService +@inject IDialogManager _dialogManager @switch (CurrentViewMode) { case ViewMode.DetailView: case ViewMode.ImportView: - var validationResult = Cuesheet?.Validate(nameof(Cuesheet.Audiofiles)); - if (validationResult?.Status == ValidationStatus.Error) + if (Cuesheet != null) { - - @_localizer["Validation errors"] - @foreach (var message in validationResult.ValidationMessages) - { - @message.GetMessageLocalized(_validationMessageLocalizer) - } - -
+ } break; } @@ -46,14 +38,18 @@ along with Foobar. If not, see { - - - @{ - var fileInputId = $"Audiofile_{Guid.NewGuid()}"; - } - x.MimeType))" - FileRenameDisabled="file.Name == null" Error="file.Validate().Status == ValidationStatus.Error" /> + + + + @GetFileName(file) + x.MimeType))" FilesChanged="(browserFile) => FileUploaded(browserFile, file)" Hidden InputClass="file-upload"> + + + + + + + @@ -81,34 +77,15 @@ along with Foobar. If not, see [Parameter] public EventCallback> SelectedFilesChanged { get; set; } - async Task ShowInputDialog(string? initialValue) - { - var parameters = new DialogParameters - { - { x => x.Placeholder, _localizer["Enter the new file name here"] }, - { x => x.Label, _localizer["New file name"] }, - { x => x.InitialValue, initialValue } - }; - var options = new DialogOptions() { CloseOnEscapeKey = true, BackdropClick = false, FullWidth = true, CloseButton = true }; - var dialog = await _dialogService.ShowAsync(_localizer["Change file name"], parameters, options); - var result = await dialog.Result; - var newFileName = result?.Data as string; - if ((result?.Canceled == false) && (String.IsNullOrEmpty(newFileName) == false)) - { - return newFileName; - } - else - { - return null; - } - } - async Task FileRenameClicked(Audiofile audiofile) { - var newFileName = await ShowInputDialog(audiofile.Name); - if (String.IsNullOrEmpty(newFileName) == false) + var result = await _dialogManager.ShowInputDialogAsync(_localizer["Change file name"], _localizer["Enter the new file name here"], _localizer["New file name"], audiofile.Name); + if (result.IsSuccess) { - _audiofileManager.SetProperty(audiofile, x => x.Name, newFileName); + if (String.IsNullOrEmpty(result.Value) == false) + { + _audiofileManager.SetProperty(audiofile, x => x.Name, result.Value); + } } } @@ -124,4 +101,17 @@ along with Foobar. If not, see } SelectedFilesChanged.InvokeAsync(SelectedFiles); } + + async Task FileUploaded(IBrowserFile? browserFile, Audiofile audiofile) + { + if (browserFile != null) + { + await _audiofileManager.SetPropertiesAsync(audiofile, browserFile); + } + } + + String GetFileName(Audiofile audiofile) + { + return String.IsNullOrEmpty(audiofile.Name) ? _localizer["No file uploaded!"] : audiofile.Name; + } } diff --git a/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.resx b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.resx index 7c5456d4..7f62ef36 100644 --- a/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.resx +++ b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.resx @@ -129,6 +129,9 @@ New file name + + No file uploaded! + Validation errors diff --git a/AudioCuesheetEditor/Shared/Cuesheet/CuesheetData.razor b/AudioCuesheetEditor/Shared/Cuesheet/CuesheetData.razor index f42de74b..07df226a 100644 --- a/AudioCuesheetEditor/Shared/Cuesheet/CuesheetData.razor +++ b/AudioCuesheetEditor/Shared/Cuesheet/CuesheetData.razor @@ -20,7 +20,7 @@ along with Foobar. If not, see @inject IStringLocalizer _localizer @inject ValidationService _validationService @inject IFileInputManager _fileInputManager -@inject IDialogService _dialogService +@inject IDialogManager _dialogManager @inject ISessionStateContainer _sessionStateContainer @inject ICuesheetManager _cuesheetManager @@ -93,39 +93,20 @@ along with Foobar. If not, see async Task CDTextFileRename() { - var newFileName = await ShowInputDialog(Cuesheet?.CDTextfile?.Name); - if (String.IsNullOrEmpty(newFileName) == false) + var result = await _dialogManager.ShowInputDialogAsync(_localizer["Change file name"], _localizer["Enter the new file name here"], _localizer["New file name"], Cuesheet?.CDTextfile?.Name); + if (result.IsSuccess) { - var cdTextFile = Cuesheet?.CDTextfile; - if (cdTextFile != null) + if (String.IsNullOrEmpty(result.Value) == false) { - cdTextFile.Name = newFileName; + var cdTextFile = Cuesheet?.CDTextfile; + if (cdTextFile != null) + { + cdTextFile.Name = result.Value; + } } } } - async Task ShowInputDialog(string? initialValue) - { - var parameters = new DialogParameters - { - { x => x.Placeholder, _localizer["Enter the new file name here"] }, - { x => x.Label, _localizer["New file name"] }, - { x => x.InitialValue, initialValue } - }; - var options = new DialogOptions() { CloseOnEscapeKey = true, BackdropClick = false, FullWidth = true, CloseButton = true }; - var dialog = await _dialogService.ShowAsync(_localizer["Change file name"], parameters, options); - var result = await dialog.Result; - var newFileName = result?.Data as string; - if ((result?.Canceled == false) && (String.IsNullOrEmpty(newFileName) == false)) - { - return newFileName; - } - else - { - return null; - } - } - void CataloguenumberOnBlur() { var newValue = _catalogueNumber?.Replace(" ", string.Empty); diff --git a/AudioCuesheetEditor/Shared/Dialogs/StartRecordCountdownDialog.resx b/AudioCuesheetEditor/Shared/Dialogs/ImportAudiofilesDialog.de.resx similarity index 89% rename from AudioCuesheetEditor/Shared/Dialogs/StartRecordCountdownDialog.resx rename to AudioCuesheetEditor/Shared/Dialogs/ImportAudiofilesDialog.de.resx index d829f469..ef606d12 100644 --- a/AudioCuesheetEditor/Shared/Dialogs/StartRecordCountdownDialog.resx +++ b/AudioCuesheetEditor/Shared/Dialogs/ImportAudiofilesDialog.de.resx @@ -117,16 +117,28 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Record countdown in sconds + + Abbrechen - - Seconds till record starts + + Datei hinzufügen - - Start countdown + + Dateien zuordnen - - Start record countdown timer + + Dateien + + + Keine Datei hochgeladen! + + + Ok + + + Anzahl Titel: {0} + + + Hochgeladene Dateien \ No newline at end of file diff --git a/AudioCuesheetEditor/Shared/Dialogs/ImportAudiofilesDialog.razor b/AudioCuesheetEditor/Shared/Dialogs/ImportAudiofilesDialog.razor new file mode 100644 index 00000000..7228b7f1 --- /dev/null +++ b/AudioCuesheetEditor/Shared/Dialogs/ImportAudiofilesDialog.razor @@ -0,0 +1,171 @@ + +@inherits BaseLocalizedComponent + +@inject IStringLocalizer _localizer + + + + + @_localizer["Assign files"] + + + + + + + + + @_localizer["Files"] + + + + + + + + + + + + @foreach (var dropzone in _dropzones) + { + + + @GetFileName(dropzone.Value) + @_localizer["Track count: {0}", @dropzone.Value.Tracks.Count] + + + + } + + + + @_localizer["Uploaded files"] + + + + + @context.Name + + + + + + @_localizer["Ok"] + @_localizer["Abort"] + + + +@code { + [CascadingParameter] + IMudDialogInstance? _mudDialog { get; set; } + + [Parameter] + [EditorRequired] + public IList ImportAudiofiles { get; set; } + + [Parameter] + [EditorRequired] + public Dictionary MappedAudiofiles { get; set; } + + List _audiofiles = []; + const string UploadedZoneIdentifier = "uploaded"; + Dictionary _dropzones = []; + string? _sourceZoneIdentifier; + + protected override void OnInitialized() + { + base.OnInitialized(); + SetDropzones(); + SetImportAudiofiles(); + } + + bool ItemsSelector(Audiofile item, string zone) + { + if (zone == UploadedZoneIdentifier) + { + return ImportAudiofiles.Contains(item); + } + var audiofile = _dropzones[zone]; + return MappedAudiofiles[audiofile] == item; + } + + void ItemPicked(MudDragAndDropItemTransaction itemTransaction) + { + _sourceZoneIdentifier = itemTransaction.SourceZoneIdentifier; + } + + void ItemDropped(MudItemDropInfo itemDropInfo) + { + if (_sourceZoneIdentifier != null && _sourceZoneIdentifier != UploadedZoneIdentifier) + { + var sourceAudiofile = _dropzones[_sourceZoneIdentifier]; + var currentlyMapped = MappedAudiofiles[sourceAudiofile]; + if (currentlyMapped != null) + { + ImportAudiofiles.Add(currentlyMapped); + } + MappedAudiofiles[sourceAudiofile] = null; + } + var targetAudiofile = _dropzones[itemDropInfo.DropzoneIdentifier]; + var currentlyMappedFile = MappedAudiofiles[targetAudiofile]; + MappedAudiofiles[targetAudiofile] = itemDropInfo.Item; + ImportAudiofiles.Remove(itemDropInfo.Item!); + if (currentlyMappedFile != null) + { + ImportAudiofiles.Add(currentlyMappedFile); + } + SetImportAudiofiles(); + _sourceZoneIdentifier = null; + } + + String GetFileName(Audiofile audiofile) + { + return String.IsNullOrEmpty(audiofile.Name) ? _localizer["No file uploaded!"] : audiofile.Name; + } + + void SetImportAudiofiles() + { + _audiofiles = [.. ImportAudiofiles, .. MappedAudiofiles.Values.Where(x => x != null).Select(x => x!)]; + } + + void SetDropzones() + { + _dropzones = MappedAudiofiles.ToDictionary(x => Guid.NewGuid().ToString(), x => x.Key); + } + + void AddButtonClicked() + { + var audiofile = new Audiofile(); + MappedAudiofiles.Add(audiofile, null); + SetDropzones(); + } + + void DeleteButtonClicked(Audiofile audiofile) + { + var currentlyMappedAudiofile = MappedAudiofiles[audiofile]; + if (currentlyMappedAudiofile != null) + { + ImportAudiofiles.Add(currentlyMappedAudiofile); + } + MappedAudiofiles.Remove(audiofile); + SetDropzones(); + } +} \ No newline at end of file diff --git a/AudioCuesheetEditor/Shared/Dialogs/StartRecordCountdownDialog.de.resx b/AudioCuesheetEditor/Shared/Dialogs/ImportAudiofilesDialog.resx similarity index 89% rename from AudioCuesheetEditor/Shared/Dialogs/StartRecordCountdownDialog.de.resx rename to AudioCuesheetEditor/Shared/Dialogs/ImportAudiofilesDialog.resx index 2128b9f9..2cd88420 100644 --- a/AudioCuesheetEditor/Shared/Dialogs/StartRecordCountdownDialog.de.resx +++ b/AudioCuesheetEditor/Shared/Dialogs/ImportAudiofilesDialog.resx @@ -117,16 +117,28 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Aufnahmecountdown in Sekunden + + Abort - - Sekunden bis die Aufnahme startet + + Add file - - Countdown starten + + Assign files - - Aufnahmecountdown starten + + Files + + + No file uploaded! + + + Ok + + + Track count: {0} + + + Uploaded files \ No newline at end of file diff --git a/AudioCuesheetEditor/Shared/Dialogs/InputTextDialog.de.resx b/AudioCuesheetEditor/Shared/Dialogs/InputDialog.de.resx similarity index 100% rename from AudioCuesheetEditor/Shared/Dialogs/InputTextDialog.de.resx rename to AudioCuesheetEditor/Shared/Dialogs/InputDialog.de.resx diff --git a/AudioCuesheetEditor/Shared/Dialogs/InputDialog.razor b/AudioCuesheetEditor/Shared/Dialogs/InputDialog.razor new file mode 100644 index 00000000..b835ed40 --- /dev/null +++ b/AudioCuesheetEditor/Shared/Dialogs/InputDialog.razor @@ -0,0 +1,100 @@ + +@typeparam T +@inherits BaseLocalizedComponent + +@inject IStringLocalizer> _localizer +@inject HotKeys _hotKeys + + + + @if (IsNumericType) + { + + } + else + { + + } + + + @OkText + + + +@code { + HotKeysContext? _hotKeysContext; + T? _value; + + [Parameter] + public T? InitialValue { get; set; } + + [Parameter] + public string? Label { get; set; } + + [Parameter] + public string? Placeholder { get; set; } + + [Parameter] + public string? OkText { get; set; } + + [Parameter] + public string? OkButtonStartIcon { get; set; } + + [CascadingParameter] + private IMudDialogInstance? MudDialog { get; set; } + + bool IsNumericType => typeof(T) == typeof(short) || typeof(T) == typeof(ushort) || typeof(T) == typeof(int) || typeof(T) == typeof(uint) || typeof(T) == typeof(long) + || typeof(T) == typeof(ulong) || typeof(T) == typeof(float) || typeof(T) == typeof(double) || typeof(T) == typeof(decimal); + + protected override void OnInitialized() + { + base.OnInitialized(); + _hotKeysContext = _hotKeys.CreateContext().Add(ModKey.None, Key.Enter, CloseDialogOk); + } + + protected override void OnParametersSet() + { + base.OnParametersSet(); + _value = InitialValue; + if (String.IsNullOrEmpty(OkText)) + { + OkText = _localizer["Ok"]; + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + _hotKeysContext?.DisposeAsync(); + } + + void OnKeyDown(KeyboardEventArgs args) + { + if (args.Key == "Enter") + { + //Use a timer to close the dialog after a short delay to ensure that the keyboard event is fully processed before closing the dialog. + new System.Threading.Timer(CloseDialogOk, null, 0, 10); + } + } + + void CloseDialogOk(object? state = null) + { + MudDialog?.Close(_value); + } +} diff --git a/AudioCuesheetEditor/Shared/Dialogs/InputTextDialog.resx b/AudioCuesheetEditor/Shared/Dialogs/InputDialog.resx similarity index 100% rename from AudioCuesheetEditor/Shared/Dialogs/InputTextDialog.resx rename to AudioCuesheetEditor/Shared/Dialogs/InputDialog.resx diff --git a/AudioCuesheetEditor/Shared/Dialogs/InputTextDialog.razor b/AudioCuesheetEditor/Shared/Dialogs/InputTextDialog.razor deleted file mode 100644 index 6e1a3aa9..00000000 --- a/AudioCuesheetEditor/Shared/Dialogs/InputTextDialog.razor +++ /dev/null @@ -1,78 +0,0 @@ - -@inherits BaseLocalizedComponent - -@inject IStringLocalizer _localizer -@inject HotKeys _hotKeys - - - - - - - @_localizer["Ok"] - - - -@code { - HotKeysContext? hotKeysContext; - string? inputValue; - - [CascadingParameter] - private IMudDialogInstance? MudDialog { get; set; } - - [Parameter] - public string? Label { get; set; } - - [Parameter] - public string? Placeholder { get; set; } - - [Parameter] - public String? InitialValue { get; set; } - - protected override void OnInitialized() - { - base.OnInitialized(); - hotKeysContext = _hotKeys.CreateContext().Add(ModKey.None, Key.Enter, CloseDialogOk); - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - hotKeysContext?.DisposeAsync(); - } - - protected override void OnParametersSet() - { - base.OnParametersSet(); - inputValue = InitialValue; - } - - void TextFieldKeyDown(KeyboardEventArgs args) - { - if (args.Key == "Enter") - { - CloseDialogOk(); - } - } - - void CloseDialogOk() - { - MudDialog?.Close(inputValue); - } -} diff --git a/AudioCuesheetEditor/Shared/Dialogs/StartRecordCountdownDialog.razor b/AudioCuesheetEditor/Shared/Dialogs/StartRecordCountdownDialog.razor deleted file mode 100644 index 1bccb8c9..00000000 --- a/AudioCuesheetEditor/Shared/Dialogs/StartRecordCountdownDialog.razor +++ /dev/null @@ -1,62 +0,0 @@ - -@inherits BaseLocalizedComponent - -@inject IStringLocalizer _localizer - - - - @_localizer["Start record countdown timer"] - - - - - - @_localizer["Start countdown"] - - - -@code { - RecordOptions? options; - - [CascadingParameter] - private IMudDialogInstance? MudDialog { get; set; } - - protected override async Task OnInitializedAsync() - { - await base.OnInitializedAsync(); - options = await base.LocalStorageOptionsProvider.GetOptionsAsync(); - base.LocalStorageOptionsProvider.OptionSaved += LocalStorageOptionsProvider_OptionSaved; - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - base.LocalStorageOptionsProvider.OptionSaved -= LocalStorageOptionsProvider_OptionSaved; - } - - void LocalStorageOptionsProvider_OptionSaved(object? sender, IOptions option) - { - if (option is RecordOptions recordOptions) - { - options = recordOptions; - StateHasChanged(); - } - } -} diff --git a/AudioCuesheetEditor/Shared/Inputs/FileDropOverlay.razor b/AudioCuesheetEditor/Shared/Inputs/FileDropOverlay.razor index deb044b1..e4d4851d 100644 --- a/AudioCuesheetEditor/Shared/Inputs/FileDropOverlay.razor +++ b/AudioCuesheetEditor/Shared/Inputs/FileDropOverlay.razor @@ -22,11 +22,11 @@ along with Foobar. If not, see @inject IJSRuntime _jsRuntime @inject IStringLocalizer _localizer @inject ImportManager _importManager -@inject DialogManager _dialogManager +@inject IDialogManager _dialogManager @inject IFileInputManager _fileInputManager -