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..29d0a26f 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] @@ -193,7 +193,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..47ed8465 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] @@ -192,7 +192,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/Services/AudioCuesheet/AudiofileManagerTests.cs b/AudioCuesheetEditor.Tests/Services/AudioCuesheet/AudiofileManagerTests.cs index 73565f58..15a565fa 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] @@ -120,17 +122,18 @@ public async Task SetPropertiesAsync_WithBrowserFile_ShouldSetPropertiesAsync() 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.GetAudioCodec(filename, "audio/mpeg")).Returns(codec); _fileInputManager.Setup(f => f.GetObjectUrlAsync(inputId)).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 @@ -141,8 +144,8 @@ public async Task SetPropertiesAsync_WithBrowserFile_ShouldSetPropertiesAsync() 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); + Assert.AreEqual(1, audiofileChangedFired); + _fileInputManager.Verify(f => f.GetAudioCodec(filename, "audio/mpeg"), Times.Once); _fileInputManager.Verify(f => f.GetObjectUrlAsync(inputId), Times.Once); _jsRuntime.Verify(js => js.InvokeAsync("getAudioDurationFromFile", It.Is(o => o[0] as string == audiofile.ObjectURL)), Times.Once); } @@ -159,7 +162,7 @@ public async Task SetProperty_AudiofileWithDuration_SetsLastTrackEndAlsoAsync() 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.GetAudioCodec(filename, "audio/mpeg")).Returns(codec); _fileInputManager.Setup(f => f.GetObjectUrlAsync(inputId)).ReturnsAsync(objectUrl); _jsRuntime.Setup(js => js.InvokeAsync("getAudioDurationFromFile", It.IsAny())).Returns(new ValueTask(90.0)); var track1 = new Track() @@ -177,12 +180,12 @@ 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 @@ -194,14 +197,58 @@ 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); + Assert.AreEqual(1, audiofileChangedFired); + _fileInputManager.Verify(f => f.GetAudioCodec(filename, "audio/mpeg"), Times.Once); _fileInputManager.Verify(f => f.GetObjectUrlAsync(inputId), 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 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(filename, "audio/mpeg")).Returns(codec); + _fileInputManager.Setup(f => f.GetObjectUrlAsync(inputId)).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, inputId); + + // 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(inputId), 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 +256,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 +284,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 +298,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 +310,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 +347,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 +362,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 +376,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 +390,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 +425,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 +442,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 +475,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 +511,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 +589,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 +604,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 +676,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 +697,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/IO/FileInputManagerTests.cs b/AudioCuesheetEditor.Tests/Services/IO/FileInputManagerTests.cs index 14022cca..3b920a6d 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() { diff --git a/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs b/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs index 658d9f32..abfb859b 100644 --- a/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs +++ b/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs @@ -368,7 +368,7 @@ public async Task UploadFilesAsync_WithAudiofile_ImportsCorrectly() _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.IsValidAudiofile(file.Name, file.ContentType)).Returns(true); _fileInputManagerMock.Setup(f => f.CreateAudiofileAsync(It.IsAny())).ReturnsAsync(new Audiofile() { Name = file.Name }); IImportfile? sessionStateContainerImportfile = null; 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/Services/AudioCuesheet/AudiofileManager.cs b/AudioCuesheetEditor/Services/AudioCuesheet/AudiofileManager.cs index 1c0bd31d..e2eeb63f 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, string fileInputId) { _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(fileInputId); + 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 /// /// - Task SetPropertiesAsync(Audiofile audiofile, IBrowserFile? browserFile, string fileInputId); + Task SetPropertiesAsync(Audiofile audiofile, IBrowserFile browserFile, string fileInputId); + /// + /// 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/IO/FileInputManager.cs b/AudioCuesheetEditor/Services/IO/FileInputManager.cs index 160a7701..ef25fdd5 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); @@ -51,9 +51,9 @@ public async Task GetObjectUrlAsync(string fileInputId) return await _jsRuntime.InvokeAsync("getObjectURLFromMudFileUpload", fileInputId); } - 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) @@ -93,7 +93,7 @@ public bool CheckFileMimeType(string? fileContentType, string fileName, string m if (fileUpload.ObjectUrl != null) { // Check file mime type - var codec = GetAudioCodec(fileUpload.ContentType, fileUpload.Name); + var codec = GetAudioCodec(fileUpload.Name, fileUpload.ContentType); if (codec != null) { TimeSpan? duration = null; @@ -148,11 +148,11 @@ 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); } diff --git a/AudioCuesheetEditor/Services/IO/IFileInputManager.cs b/AudioCuesheetEditor/Services/IO/IFileInputManager.cs index e5f4ff39..ca2a552a 100644 --- a/AudioCuesheetEditor/Services/IO/IFileInputManager.cs +++ b/AudioCuesheetEditor/Services/IO/IFileInputManager.cs @@ -23,8 +23,8 @@ 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 /// diff --git a/AudioCuesheetEditor/Services/IO/ImportManager.cs b/AudioCuesheetEditor/Services/IO/ImportManager.cs index 6637210f..01ad1937 100644 --- a/AudioCuesheetEditor/Services/IO/ImportManager.cs +++ b/AudioCuesheetEditor/Services/IO/ImportManager.cs @@ -132,7 +132,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 +161,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) @@ -220,6 +220,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; } 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..7ccffe2a 100644 --- a/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor +++ b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor @@ -26,17 +26,9 @@ along with Foobar. If not, see { 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,22 @@ along with Foobar. If not, see { - - - @{ - var fileInputId = $"Audiofile_{Guid.NewGuid()}"; + + + + @GetFileName(file) + @if (_fileInputIds.TryGetValue(file, out var fileInputId) == false) + { + _fileInputIds[file] = $"Audiofile_{Guid.NewGuid()}"; } - x.MimeType))" - FileRenameDisabled="file.Name == null" Error="file.Validate().Status == ValidationStatus.Error" /> + x.MimeType))" FilesChanged="(browserFile) => FileUploaded(browserFile, file)" Hidden> + + + + + + + @@ -81,6 +81,8 @@ along with Foobar. If not, see [Parameter] public EventCallback> SelectedFilesChanged { get; set; } + Dictionary _fileInputIds = new(); + async Task ShowInputDialog(string? initialValue) { var parameters = new DialogParameters @@ -124,4 +126,18 @@ along with Foobar. If not, see } SelectedFilesChanged.InvokeAsync(SelectedFiles); } + + async Task FileUploaded(IBrowserFile? browserFile, Audiofile audiofile) + { + if (browserFile != null) + { + var fileInputId = _fileInputIds[audiofile]; + await _audiofileManager.SetPropertiesAsync(audiofile, browserFile, fileInputId!); + } + } + + 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/Dialogs/InputTextDialog.razor b/AudioCuesheetEditor/Shared/Dialogs/InputTextDialog.razor index 6e1a3aa9..6eddbfcb 100644 --- a/AudioCuesheetEditor/Shared/Dialogs/InputTextDialog.razor +++ b/AudioCuesheetEditor/Shared/Dialogs/InputTextDialog.razor @@ -22,7 +22,7 @@ along with Foobar. If not, see - + @_localizer["Ok"] @@ -30,8 +30,8 @@ along with Foobar. If not, see @code { - HotKeysContext? hotKeysContext; - string? inputValue; + HotKeysContext? _hotKeysContext; + string? _inputValue; [CascadingParameter] private IMudDialogInstance? MudDialog { get; set; } @@ -43,36 +43,37 @@ along with Foobar. If not, see public string? Placeholder { get; set; } [Parameter] - public String? InitialValue { get; set; } + public string? InitialValue { get; set; } protected override void OnInitialized() { base.OnInitialized(); - hotKeysContext = _hotKeys.CreateContext().Add(ModKey.None, Key.Enter, CloseDialogOk); + _hotKeysContext = _hotKeys.CreateContext().Add(ModKey.None, Key.Enter, CloseDialogOk); } protected override void Dispose(bool disposing) { base.Dispose(disposing); - hotKeysContext?.DisposeAsync(); + _hotKeysContext?.DisposeAsync(); } protected override void OnParametersSet() { base.OnParametersSet(); - inputValue = InitialValue; + _inputValue = InitialValue; } void TextFieldKeyDown(KeyboardEventArgs args) { if (args.Key == "Enter") { - CloseDialogOk(); + //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() + void CloseDialogOk(object? state = null) { - MudDialog?.Close(inputValue); + MudDialog?.Close(_inputValue); } } diff --git a/AudioCuesheetEditor/Shared/TrackList/TrackList.razor b/AudioCuesheetEditor/Shared/TrackList/TrackList.razor index 1fd92657..f2085a8a 100644 --- a/AudioCuesheetEditor/Shared/TrackList/TrackList.razor +++ b/AudioCuesheetEditor/Shared/TrackList/TrackList.razor @@ -34,17 +34,6 @@ along with Foobar. If not, see { case ViewMode.DetailView: case ViewMode.ImportView: - var validationResult = Audiofile?.Validate(nameof(Audiofile.Tracks)); - if (validationResult?.Status == ValidationStatus.Error) - { - - @_localizer["Validation errors"] - @foreach (var message in validationResult.ValidationMessages) - { - @message.GetMessageLocalized(_validationMessageLocalizer) - } - - }
. +--> +@inherits BaseLocalizedComponent + +@inject IStringLocalizer _validationMessageLocalizer + +@if (ValidationResult.Status == ValidationStatus.Error) +{ + + @foreach (var message in ValidationResult.ValidationMessages) + { + @message.GetMessageLocalized(_validationMessageLocalizer) + } + +} + +@code { + [EditorRequired] + [Parameter] + public ValidationResult ValidationResult { get; set; } +} diff --git a/AudioCuesheetEditor/Shared/ViewModes/ViewModeImport.razor b/AudioCuesheetEditor/Shared/ViewModes/ViewModeImport.razor index 8cd09948..da6f1522 100644 --- a/AudioCuesheetEditor/Shared/ViewModes/ViewModeImport.razor +++ b/AudioCuesheetEditor/Shared/ViewModes/ViewModeImport.razor @@ -24,7 +24,7 @@ along with Foobar. If not, see @if (IsAnalyzed == false) { - + @@ -40,9 +40,9 @@ else OnClick="ImportData" /> - + - + @@ -53,8 +53,8 @@ else } @code { - string? importText; - Boolean fapMenuOpened = false; + string? _importText; + Boolean _fapMenuOpened = false; public Boolean IsAnalyzed { @@ -77,8 +77,8 @@ else void ImportTextChanged(string? newImportText) { - importText = newImportText; - _importManager.ImportData(importText); + _importText = newImportText; + _importManager.ImportData(_importText); } async Task Analyze() @@ -125,7 +125,7 @@ else { if (_sessionStateContainer.Importfile?.FileType == ImportFileType.Textfile) { - importText = _sessionStateContainer.Importfile.FileContent; + _importText = _sessionStateContainer.Importfile.FileContent; StateHasChanged(); } }); diff --git a/AudioCuesheetEditor/_Imports.razor b/AudioCuesheetEditor/_Imports.razor index 41555c08..d815267e 100644 --- a/AudioCuesheetEditor/_Imports.razor +++ b/AudioCuesheetEditor/_Imports.razor @@ -22,6 +22,7 @@ @using AudioCuesheetEditor.Shared.Audio @using AudioCuesheetEditor.Shared.Record @using AudioCuesheetEditor.Shared.Export +@using AudioCuesheetEditor.Shared.Validation @using AudioCuesheetEditor.Model.AudioCuesheet @using AudioCuesheetEditor.Model.IO @using AudioCuesheetEditor.Model.IO.Export diff --git a/AudioCuesheetEditor/wwwroot/scripts/library.js b/AudioCuesheetEditor/wwwroot/scripts/library.js index 1e376797..364ddfc9 100644 --- a/AudioCuesheetEditor/wwwroot/scripts/library.js +++ b/AudioCuesheetEditor/wwwroot/scripts/library.js @@ -1,36 +1,22 @@ window.addEventListener('beforeunload', beforeunload); -window._audioObjectURLCache = {}; -window.getObjectURLFromMudFileUpload = function (inputOrFieldId) { - if (window._audioObjectURLCache[inputOrFieldId]) { - return window._audioObjectURLCache[inputOrFieldId]; - } +window.getObjectURLFromMudFileUpload = function (fileInputId) { - let inputElem = inputOrFieldId; - if (typeof inputOrFieldId === "string") { - inputElem = document.getElementById(inputOrFieldId) || - document.querySelector(`input[identifier="${inputOrFieldId}"]`) || - document.querySelector(`input[id="${inputOrFieldId}"]`); - } + const inputElem = document.getElementById(fileInputId) || + document.querySelector(`input[identifier="${fileInputId}"]`) || + document.querySelector(`input[id="${fileInputId}"]`); const files = inputElem.files; for (let i = 0; i < files.length; i++) { - const f = files[i]; - if (f && f.type && f.type.startsWith("audio/")) { - const newObjectURL = URL.createObjectURL(f); - window._audioObjectURLCache[inputOrFieldId] = newObjectURL; - return newObjectURL; + const file = files[i]; + if (file && file.type && file.type.startsWith("audio/")) { + return URL.createObjectURL(file); } } return null; }; window.revokeAudioObjectURL = function (objectUrl) { - Object.keys(window._audioObjectURLCache).forEach(key => { - if (window._audioObjectURLCache[key] === objectUrl) { - delete window._audioObjectURLCache[key]; - } - }); URL.revokeObjectURL(objectUrl); };