From 0bdd3ef1ff0cb9ccd2546194cbdaa024509b143c Mon Sep 17 00:00:00 2001 From: NeoCodeMatrix86 <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Fri, 25 Sep 2026 14:54:43 +0200 Subject: [PATCH 01/19] Update Audiofiles.razor --- AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor | 1 + 1 file changed, 1 insertion(+) diff --git a/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor index 29222814..a267201a 100644 --- a/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor +++ b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor @@ -64,6 +64,7 @@ along with Foobar. If not, see @code { + //TODO: Redesign title content [Parameter] [EditorRequired] public Cuesheet? Cuesheet { get; set; } From 08f56c80d59485c1f7e3ac4ae8fd0fe1910257f1 Mon Sep 17 00:00:00 2001 From: NeoCodeMatrix86 <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Mon, 28 Sep 2026 09:08:01 +0200 Subject: [PATCH 02/19] moved clearing to seperate method --- .../AudioCuesheet/AudiofileManagerTests.cs | 12 +++-- .../AudioCuesheet/AudiofileManager.cs | 54 ++++++++++--------- .../AudioCuesheet/IAudiofileManager.cs | 8 ++- 3 files changed, 43 insertions(+), 31 deletions(-) diff --git a/AudioCuesheetEditor.Tests/Services/AudioCuesheet/AudiofileManagerTests.cs b/AudioCuesheetEditor.Tests/Services/AudioCuesheet/AudiofileManagerTests.cs index 73565f58..1349c6d4 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 @@ -98,7 +98,7 @@ public async Task SetPropertiesAsync_EmptyBrowserFile_ShouldClearPropertiesAsync }; 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); @@ -106,6 +106,8 @@ public async Task SetPropertiesAsync_EmptyBrowserFile_ShouldClearPropertiesAsync Assert.IsNull(audiofile.Duration); Assert.IsTrue(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] @@ -201,7 +203,7 @@ public async Task SetProperty_AudiofileWithDuration_SetsLastTrackEndAlsoAsync() } [TestMethod] - public async Task SetPropertiesAsync_NullBrowserFile_WithExistingObjectUrl_ShouldRevokeObjectUrl() + public async Task ClearPropertiesAsync_WithExistingObjectUrl_ShouldRevokeObjectUrl() { // Arrange var audiofile = new Audiofile @@ -218,12 +220,14 @@ public async Task SetPropertiesAsync_NullBrowserFile_WithExistingObjectUrl_Shoul } }; // Act - await _audiofileManager.SetPropertiesAsync(audiofile, null, string.Empty); + await _audiofileManager.ClearPropertiesAsync(audiofile); // Assert Assert.IsTrue(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] diff --git a/AudioCuesheetEditor/Services/AudioCuesheet/AudiofileManager.cs b/AudioCuesheetEditor/Services/AudioCuesheet/AudiofileManager.cs index 1c0bd31d..a0760fe7 100644 --- a/AudioCuesheetEditor/Services/AudioCuesheet/AudiofileManager.cs +++ b/AudioCuesheetEditor/Services/AudioCuesheet/AudiofileManager.cs @@ -17,6 +17,7 @@ using AudioCuesheetEditor.Model.IO.Audio; using AudioCuesheetEditor.Services.IO; using AudioCuesheetEditor.Services.UI; +using AudioCuesheetEditor.Shared.Cuesheet; using Microsoft.AspNetCore.Components.Forms; using Microsoft.JSInterop; using System.Linq.Expressions; @@ -36,36 +37,37 @@ 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) - { - 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); - } - else + var codec = _fileInputManager.GetAudioCodec(browserFile.ContentType, browserFile.Name); + 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); + } + 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 async Task ClearPropertiesAsync(Audiofile audiofile) + { + _traceChangeManager.BulkEdit = true; + 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) - { - var durationSeconds = await _jsRuntime.InvokeAsync("getAudioDurationFromFile", objectUrl); - duration = TimeSpan.FromSeconds(durationSeconds); - } - 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); + 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); _traceChangeManager.BulkEdit = false; } diff --git a/AudioCuesheetEditor/Services/AudioCuesheet/IAudiofileManager.cs b/AudioCuesheetEditor/Services/AudioCuesheet/IAudiofileManager.cs index 672e938f..512a69f2 100644 --- a/AudioCuesheetEditor/Services/AudioCuesheet/IAudiofileManager.cs +++ b/AudioCuesheetEditor/Services/AudioCuesheet/IAudiofileManager.cs @@ -32,7 +32,13 @@ public interface IAudiofileManager /// /// /// - Task SetPropertiesAsync(Audiofile audiofile, IBrowserFile? browserFile, string fileInputId); + Task SetPropertiesAsync(Audiofile audiofile, IBrowserFile browserFile, string fileInputId); + /// + /// Clear properties of an audiofile + /// + /// + /// + Task ClearPropertiesAsync(Audiofile audiofile); /// /// Set property for an audio file /// From 0a86685506f8a6f0dfc99fd43181cd8fbb38b63e Mon Sep 17 00:00:00 2001 From: NeoCodeMatrix86 <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Mon, 28 Sep 2026 09:23:27 +0200 Subject: [PATCH 03/19] refactor audiofiles handling --- .../Shared/Cuesheet/Audiofiles.de.resx | 3 ++ .../Shared/Cuesheet/Audiofiles.razor | 31 +++++++++++++++---- .../Shared/Cuesheet/Audiofiles.resx | 3 ++ 3 files changed, 31 insertions(+), 6 deletions(-) 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 a267201a..c8ee4a9b 100644 --- a/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor +++ b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor @@ -46,14 +46,18 @@ along with Foobar. If not, see { - - + + + @GetFileName(file) @{ var fileInputId = $"Audiofile_{Guid.NewGuid()}"; } - x.MimeType))" - FileRenameDisabled="file.Name == null" Error="file.Validate().Status == ValidationStatus.Error" /> + x.MimeType))" FilesChanged="(browserFile) => FileUploaded(browserFile, file)"> + + + + + @@ -64,7 +68,9 @@ along with Foobar. If not, see @code { - //TODO: Redesign title content + //TODO: Alert when file is invalid + //TODO: Rename file + [Parameter] [EditorRequired] public Cuesheet? Cuesheet { get; set; } @@ -82,6 +88,8 @@ along with Foobar. If not, see [Parameter] public EventCallback> SelectedFilesChanged { get; set; } + Dictionary> _fileUploads = new(); + async Task ShowInputDialog(string? initialValue) { var parameters = new DialogParameters @@ -125,4 +133,15 @@ along with Foobar. If not, see } SelectedFilesChanged.InvokeAsync(SelectedFiles); } + + async Task FileUploaded(IBrowserFile browserFile, Audiofile audiofile) + { + var fileInputId = _fileUploads[audiofile].UserAttributes["identifier"]?.ToString(); + 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 From 70b9c915ba226f2727619a5479ef494a6b7e8d0b Mon Sep 17 00:00:00 2001 From: NeoCodeMatrix86 <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Mon, 28 Sep 2026 10:36:41 +0200 Subject: [PATCH 04/19] add alert for validation errors --- .../Model/Entity/ValidationMessage.de.resx | 3 + .../Model/Entity/ValidationMessage.resx | 3 + .../Shared/Cuesheet/Audiofiles.razor | 17 +-- .../Shared/TrackList/TrackList.razor | 11 -- .../Shared/Validation/Alert.de.resx | 123 ++++++++++++++++++ .../Shared/Validation/Alert.razor | 38 ++++++ .../Shared/Validation/Alert.resx | 123 ++++++++++++++++++ AudioCuesheetEditor/_Imports.razor | 1 + 8 files changed, 296 insertions(+), 23 deletions(-) create mode 100644 AudioCuesheetEditor/Shared/Validation/Alert.de.resx create mode 100644 AudioCuesheetEditor/Shared/Validation/Alert.razor create mode 100644 AudioCuesheetEditor/Shared/Validation/Alert.resx 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/Shared/Cuesheet/Audiofiles.razor b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor index c8ee4a9b..8c534c0b 100644 --- a/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor +++ b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor @@ -27,17 +27,7 @@ along with Foobar. If not, see case ViewMode.DetailView: case ViewMode.ImportView: var validationResult = Cuesheet?.Validate(nameof(Cuesheet.Audiofiles)); - if (validationResult?.Status == ValidationStatus.Error) - { - - @_localizer["Validation errors"] - @foreach (var message in validationResult.ValidationMessages) - { - @message.GetMessageLocalized(_validationMessageLocalizer) - } - -
- } + break; } @@ -46,6 +36,10 @@ along with Foobar. If not, see { + @{ + var validationResult = file.Validate(); + + } @GetFileName(file) @@ -68,7 +62,6 @@ along with Foobar. If not, see @code { - //TODO: Alert when file is invalid //TODO: Rename file [Parameter] 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) - } - - }
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Validierungsfehler + + \ No newline at end of file diff --git a/AudioCuesheetEditor/Shared/Validation/Alert.razor b/AudioCuesheetEditor/Shared/Validation/Alert.razor new file mode 100644 index 00000000..aea720f2 --- /dev/null +++ b/AudioCuesheetEditor/Shared/Validation/Alert.razor @@ -0,0 +1,38 @@ + +@inherits BaseLocalizedComponent + +@inject IStringLocalizer _localizer +@inject IStringLocalizer _validationMessageLocalizer + +@if (ValidationResult.Status == ValidationStatus.Error) +{ + + @_localizer["Validation errors"] + @foreach (var message in ValidationResult.ValidationMessages) + { + @message.GetMessageLocalized(_validationMessageLocalizer) + } + +} + +@code { + [EditorRequired] + [Parameter] + public ValidationResult ValidationResult { get; set; } +} diff --git a/AudioCuesheetEditor/Shared/Validation/Alert.resx b/AudioCuesheetEditor/Shared/Validation/Alert.resx new file mode 100644 index 00000000..3db25c3d --- /dev/null +++ b/AudioCuesheetEditor/Shared/Validation/Alert.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Validation errors + + \ No newline at end of file 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 From 19ea5eb80925e560185369b6ddf903e4fab75d6f Mon Sep 17 00:00:00 2001 From: NeoCodeMatrix86 <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Mon, 28 Sep 2026 11:55:26 +0200 Subject: [PATCH 05/19] rename files --- AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor | 3 ++- AudioCuesheetEditor/Shared/Validation/Alert.razor | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor index 8c534c0b..22988970 100644 --- a/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor +++ b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor @@ -51,6 +51,7 @@ along with Foobar. If not, see + @@ -62,7 +63,7 @@ along with Foobar. If not, see @code { - //TODO: Rename file + //TODO: Multiple uploads fail [Parameter] [EditorRequired] diff --git a/AudioCuesheetEditor/Shared/Validation/Alert.razor b/AudioCuesheetEditor/Shared/Validation/Alert.razor index aea720f2..c0c3280e 100644 --- a/AudioCuesheetEditor/Shared/Validation/Alert.razor +++ b/AudioCuesheetEditor/Shared/Validation/Alert.razor @@ -23,12 +23,11 @@ along with Foobar. If not, see @if (ValidationResult.Status == ValidationStatus.Error) { - @_localizer["Validation errors"] @foreach (var message in ValidationResult.ValidationMessages) { - @message.GetMessageLocalized(_validationMessageLocalizer) + @message.GetMessageLocalized(_validationMessageLocalizer) } - + } @code { From d2e63f8c53f7aa24b1cb24ab3787b3f8481f0125 Mon Sep 17 00:00:00 2001 From: NeoCodeMatrix86 <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Mon, 28 Sep 2026 14:33:04 +0200 Subject: [PATCH 06/19] reset file before uploading another one --- .../AudioCuesheet/AudiofileManagerTests.cs | 130 ++++++++++++------ .../AudioCuesheet/AudiofileManager.cs | 65 ++++++--- .../AudioCuesheet/IAudiofileManager.cs | 3 +- 3 files changed, 140 insertions(+), 58 deletions(-) diff --git a/AudioCuesheetEditor.Tests/Services/AudioCuesheet/AudiofileManagerTests.cs b/AudioCuesheetEditor.Tests/Services/AudioCuesheet/AudiofileManagerTests.cs index 1349c6d4..fd788824 100644 --- a/AudioCuesheetEditor.Tests/Services/AudioCuesheet/AudiofileManagerTests.cs +++ b/AudioCuesheetEditor.Tests/Services/AudioCuesheet/AudiofileManagerTests.cs @@ -88,12 +88,12 @@ public async Task ClearPropertiesAsync_ValidBrowserFile_ShouldClearPropertiesAsy 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; @@ -104,7 +104,7 @@ public async Task ClearPropertiesAsync_ValidBrowserFile_ShouldClearPropertiesAsy 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); @@ -127,12 +127,13 @@ public async Task SetPropertiesAsync_WithBrowserFile_ShouldSetPropertiesAsync() _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 @@ -143,7 +144,7 @@ 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); + Assert.AreEqual(1, audiofileChangedFired); _fileInputManager.Verify(f => f.GetAudioCodec("audio/mpeg", filename), 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); @@ -179,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 @@ -196,12 +197,56 @@ 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); + Assert.AreEqual(1, audiofileChangedFired); _fileInputManager.Verify(f => f.GetAudioCodec("audio/mpeg", filename), 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_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("audio/mpeg", filename)).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("audio/mpeg", filename), 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() { @@ -211,19 +256,20 @@ public async Task ClearPropertiesAsync_WithExistingObjectUrl_ShouldRevokeObjectU 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.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); @@ -238,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 @@ -251,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); } @@ -263,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); } @@ -299,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 @@ -313,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] @@ -327,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(); @@ -340,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); @@ -375,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 @@ -392,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); @@ -425,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); @@ -460,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); @@ -537,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 @@ -552,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); @@ -624,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 @@ -645,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/Services/AudioCuesheet/AudiofileManager.cs b/AudioCuesheetEditor/Services/AudioCuesheet/AudiofileManager.cs index a0760fe7..b13642d1 100644 --- a/AudioCuesheetEditor/Services/AudioCuesheet/AudiofileManager.cs +++ b/AudioCuesheetEditor/Services/AudioCuesheet/AudiofileManager.cs @@ -17,7 +17,6 @@ using AudioCuesheetEditor.Model.IO.Audio; using AudioCuesheetEditor.Services.IO; using AudioCuesheetEditor.Services.UI; -using AudioCuesheetEditor.Shared.Cuesheet; using Microsoft.AspNetCore.Components.Forms; using Microsoft.JSInterop; using System.Linq.Expressions; @@ -40,6 +39,10 @@ public class AudiofileManager(IFileInputManager fileInputManager, ITraceChangeMa public async Task SetPropertiesAsync(Audiofile audiofile, IBrowserFile browserFile, string fileInputId) { _traceChangeManager.BulkEdit = true; + if (String.IsNullOrEmpty(audiofile.ObjectURL) == false) + { + await ClearPropertiesAsync(audiofile, false); + } var codec = _fileInputManager.GetAudioCodec(browserFile.ContentType, browserFile.Name); var objectUrl = await _fileInputManager.GetObjectUrlAsync(fileInputId); TimeSpan? duration = null; @@ -48,34 +51,52 @@ public async Task SetPropertiesAsync(Audiofile audiofile, IBrowserFile browserFi var durationSeconds = await _jsRuntime.InvokeAsync("getAudioDurationFromFile", objectUrl); duration = TimeSpan.FromSeconds(durationSeconds); } - 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); + 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) + public async Task ClearPropertiesAsync(Audiofile audiofile, Boolean setTracing = true) { - _traceChangeManager.BulkEdit = true; + if (setTracing) + { + _traceChangeManager.BulkEdit = true; + } 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); - _traceChangeManager.BulkEdit = false; + 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) + { + AudiofileChanged?.Invoke(this, audiofile); + } + } } /// 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); + } } /// @@ -101,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); + } } /// @@ -123,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) { @@ -146,12 +175,12 @@ void SetValue(Audiofile audiofile, Expression /// + /// Parameter controlling if tracing should be handled by this service or by calling services /// - Task ClearPropertiesAsync(Audiofile audiofile); + Task ClearPropertiesAsync(Audiofile audiofile, Boolean setTracing = true); /// /// Set property for an audio file /// From 13ef198215dd2b9ea34fa006f30353f54fda92fd Mon Sep 17 00:00:00 2001 From: NeoCodeMatrix86 <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Tue, 29 Sep 2026 11:34:34 +0200 Subject: [PATCH 07/19] fix multiple file upload --- .../Shared/Cuesheet/Audiofiles.razor | 25 +++++++++-------- .../wwwroot/scripts/library.js | 28 +++++-------------- 2 files changed, 21 insertions(+), 32 deletions(-) diff --git a/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor index 22988970..211321c9 100644 --- a/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor +++ b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor @@ -43,14 +43,16 @@ along with Foobar. If not, see @GetFileName(file) - @{ - var fileInputId = $"Audiofile_{Guid.NewGuid()}"; + @if (_fileInputIds.TryGetValue(file, out var fileInputId) == false) + { + _fileInputIds[file] = $"Audiofile_{Guid.NewGuid()}"; } - x.MimeType))" FilesChanged="(browserFile) => FileUploaded(browserFile, file)"> - + x.MimeType))" FilesChanged="(browserFile) => FileUploaded(browserFile, file)" Hidden> + + + - @@ -63,8 +65,6 @@ along with Foobar. If not, see @code { - //TODO: Multiple uploads fail - [Parameter] [EditorRequired] public Cuesheet? Cuesheet { get; set; } @@ -82,7 +82,7 @@ along with Foobar. If not, see [Parameter] public EventCallback> SelectedFilesChanged { get; set; } - Dictionary> _fileUploads = new(); + Dictionary _fileInputIds = new(); async Task ShowInputDialog(string? initialValue) { @@ -128,10 +128,13 @@ along with Foobar. If not, see SelectedFilesChanged.InvokeAsync(SelectedFiles); } - async Task FileUploaded(IBrowserFile browserFile, Audiofile audiofile) + async Task FileUploaded(IBrowserFile? browserFile, Audiofile audiofile) { - var fileInputId = _fileUploads[audiofile].UserAttributes["identifier"]?.ToString(); - await _audiofileManager.SetPropertiesAsync(audiofile, browserFile, fileInputId!); + if (browserFile != null) + { + var fileInputId = _fileInputIds[audiofile]; + await _audiofileManager.SetPropertiesAsync(audiofile, browserFile, fileInputId!); + } } String GetFileName(Audiofile audiofile) 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); }; From e9bbc786629f54e7c04bb30121a067af9c8cb3ee Mon Sep 17 00:00:00 2001 From: NeoCodeMatrix86 <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Tue, 29 Sep 2026 12:56:44 +0200 Subject: [PATCH 08/19] fix basic tests --- AudioCuesheetEditor.End2EndTests/Models/DetailView.cs | 5 ++--- AudioCuesheetEditor.End2EndTests/Tests/Desktop/BasicTest.cs | 2 +- .../Tests/Smartphone/BasicTest.cs | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) 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..05c131ff 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] diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTest.cs index 212587fc..d6edfd36 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] From dbd6a95044d24c81861ddd5453c85262bb5965a2 Mon Sep 17 00:00:00 2001 From: NeoCodeMatrix86 <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Tue, 29 Sep 2026 16:20:08 +0200 Subject: [PATCH 09/19] fix import tests --- AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs | 5 +++-- .../Tests/Smartphone/ImportTest.cs | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs index 6e443a41..351586a2 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 { @@ -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/ImportTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTest.cs index f5ff4614..22c05e60 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTest.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTest.cs @@ -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] From 5d78249a802ac7733732eb0be7da0236b22543a5 Mon Sep 17 00:00:00 2001 From: NeoCodeMatrix86 <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Tue, 29 Sep 2026 16:36:13 +0200 Subject: [PATCH 10/19] Update ViewModeImport.razor --- AudioCuesheetEditor/Shared/ViewModes/ViewModeImport.razor | 1 + 1 file changed, 1 insertion(+) diff --git a/AudioCuesheetEditor/Shared/ViewModes/ViewModeImport.razor b/AudioCuesheetEditor/Shared/ViewModes/ViewModeImport.razor index 8cd09948..3a46ff6c 100644 --- a/AudioCuesheetEditor/Shared/ViewModes/ViewModeImport.razor +++ b/AudioCuesheetEditor/Shared/ViewModes/ViewModeImport.razor @@ -53,6 +53,7 @@ else } @code { + //TODO: importing an audiofile doesn't set the audio codec string? importText; Boolean fapMenuOpened = false; From bcd544d7b3971d81af6b2511b2274598350b26c9 Mon Sep 17 00:00:00 2001 From: NeoCodeMatrix86 <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Tue, 29 Sep 2026 16:36:18 +0200 Subject: [PATCH 11/19] Update Audiofiles.razor --- AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor index 211321c9..4d7f3a69 100644 --- a/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor +++ b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor @@ -26,8 +26,7 @@ along with Foobar. If not, see { case ViewMode.DetailView: case ViewMode.ImportView: - var validationResult = Cuesheet?.Validate(nameof(Cuesheet.Audiofiles)); - + break; } @@ -36,10 +35,7 @@ along with Foobar. If not, see { - @{ - var validationResult = file.Validate(); - - } + @GetFileName(file) From e62cdca1649bd2d77dde080e1066ebbd1bc4c231 Mon Sep 17 00:00:00 2001 From: NeoCodeMatrix86 <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Wed, 30 Sep 2026 08:30:44 +0200 Subject: [PATCH 12/19] remove localization --- .../Shared/Validation/Alert.de.resx | 123 ------------------ .../Shared/Validation/Alert.razor | 1 - .../Shared/Validation/Alert.resx | 123 ------------------ 3 files changed, 247 deletions(-) delete mode 100644 AudioCuesheetEditor/Shared/Validation/Alert.de.resx delete mode 100644 AudioCuesheetEditor/Shared/Validation/Alert.resx diff --git a/AudioCuesheetEditor/Shared/Validation/Alert.de.resx b/AudioCuesheetEditor/Shared/Validation/Alert.de.resx deleted file mode 100644 index 28e93047..00000000 --- a/AudioCuesheetEditor/Shared/Validation/Alert.de.resx +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Validierungsfehler - - \ No newline at end of file diff --git a/AudioCuesheetEditor/Shared/Validation/Alert.razor b/AudioCuesheetEditor/Shared/Validation/Alert.razor index c0c3280e..4741d4f2 100644 --- a/AudioCuesheetEditor/Shared/Validation/Alert.razor +++ b/AudioCuesheetEditor/Shared/Validation/Alert.razor @@ -17,7 +17,6 @@ along with Foobar. If not, see --> @inherits BaseLocalizedComponent -@inject IStringLocalizer _localizer @inject IStringLocalizer _validationMessageLocalizer @if (ValidationResult.Status == ValidationStatus.Error) diff --git a/AudioCuesheetEditor/Shared/Validation/Alert.resx b/AudioCuesheetEditor/Shared/Validation/Alert.resx deleted file mode 100644 index 3db25c3d..00000000 --- a/AudioCuesheetEditor/Shared/Validation/Alert.resx +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Validation errors - - \ No newline at end of file From 0124ebca7aeee3fbcf95b97c2f12fb8409a5c4ad Mon Sep 17 00:00:00 2001 From: NeoCodeMatrix86 <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Wed, 30 Sep 2026 08:36:27 +0200 Subject: [PATCH 13/19] Update Audiofiles.razor --- AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor index 4d7f3a69..7ccffe2a 100644 --- a/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor +++ b/AudioCuesheetEditor/Shared/Cuesheet/Audiofiles.razor @@ -26,7 +26,10 @@ along with Foobar. If not, see { case ViewMode.DetailView: case ViewMode.ImportView: - + if (Cuesheet != null) + { + + } break; } From 673f451245c6943246e8b04cbfb245a54a8ae6be Mon Sep 17 00:00:00 2001 From: NeoCodeMatrix86 <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Wed, 30 Sep 2026 08:51:36 +0200 Subject: [PATCH 14/19] set codec during import --- .../Services/IO/FileInputManagerTests.cs | 24 +++++++++++++++---- .../Services/IO/FileInputManager.cs | 10 ++++---- .../Services/IO/IFileInputManager.cs | 4 ++-- .../Services/IO/ImportManager.cs | 1 + 4 files changed, 27 insertions(+), 12 deletions(-) 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/Services/IO/FileInputManager.cs b/AudioCuesheetEditor/Services/IO/FileInputManager.cs index 160a7701..656b868a 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) @@ -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..163ff163 100644 --- a/AudioCuesheetEditor/Services/IO/ImportManager.cs +++ b/AudioCuesheetEditor/Services/IO/ImportManager.cs @@ -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; } From 6997b5cbcd46a62a6a67a7958a793ebe36d2b496 Mon Sep 17 00:00:00 2001 From: NeoCodeMatrix86 <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Wed, 30 Sep 2026 08:56:50 +0200 Subject: [PATCH 15/19] fix import tests --- AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs | 2 +- AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTest.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs index 351586a2..00dfb6ef 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs @@ -212,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(); } diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTest.cs index 22c05e60..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(); } From c5c2f02cfb355e13e64964ca57ffa27fd1e46fa2 Mon Sep 17 00:00:00 2001 From: NeoCodeMatrix86 <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Wed, 30 Sep 2026 09:19:01 +0200 Subject: [PATCH 16/19] Update ViewModeImport.razor --- .../Shared/ViewModes/ViewModeImport.razor | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/AudioCuesheetEditor/Shared/ViewModes/ViewModeImport.razor b/AudioCuesheetEditor/Shared/ViewModes/ViewModeImport.razor index 3a46ff6c..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,9 +53,8 @@ else } @code { - //TODO: importing an audiofile doesn't set the audio codec - string? importText; - Boolean fapMenuOpened = false; + string? _importText; + Boolean _fapMenuOpened = false; public Boolean IsAnalyzed { @@ -78,8 +77,8 @@ else void ImportTextChanged(string? newImportText) { - importText = newImportText; - _importManager.ImportData(importText); + _importText = newImportText; + _importManager.ImportData(_importText); } async Task Analyze() @@ -126,7 +125,7 @@ else { if (_sessionStateContainer.Importfile?.FileType == ImportFileType.Textfile) { - importText = _sessionStateContainer.Importfile.FileContent; + _importText = _sessionStateContainer.Importfile.FileContent; StateHasChanged(); } }); From 2c1e4d912f4eacabbbc05d12726d4e20143db67d Mon Sep 17 00:00:00 2001 From: NeoCodeMatrix86 <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Wed, 30 Sep 2026 09:56:01 +0200 Subject: [PATCH 17/19] Update InputTextDialog.razor --- .../Shared/Dialogs/InputTextDialog.razor | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) 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); } } From 0b1166d0b2df9feaa2d9eaaf0c93a63a910798ae Mon Sep 17 00:00:00 2001 From: NeoCodeMatrix86 <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Wed, 30 Sep 2026 10:01:05 +0200 Subject: [PATCH 18/19] fix basic tests --- AudioCuesheetEditor.End2EndTests/Tests/Desktop/BasicTest.cs | 2 +- AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTest.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/BasicTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/BasicTest.cs index 05c131ff..db93ad22 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/BasicTest.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/BasicTest.cs @@ -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" })).ToMatchAriaSnapshotAsync("- group:\r\n - checkbox\r\n - heading \"Test 123\" [level=6]\r\n - button \"Upload file\"\r\n - button \"Rename file\"\r\n - button \"Clear file\""); } } } diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTest.cs index d6edfd36..b11249d3 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTest.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTest.cs @@ -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" })).ToMatchAriaSnapshotAsync("- group:\r\n - checkbox\r\n - heading \"Test 123\" [level=6]\r\n - button \"Upload file\"\r\n - button \"Rename file\"\r\n - button \"Clear file\""); } } } From 5fe0f1b72a39692c7469a5d2acf9adcef154f349 Mon Sep 17 00:00:00 2001 From: NeoCodeMatrix86 <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Wed, 30 Sep 2026 10:27:00 +0200 Subject: [PATCH 19/19] fix tests --- .../Tests/Desktop/BasicTest.cs | 2 +- .../Tests/Smartphone/BasicTest.cs | 2 +- .../Services/AudioCuesheet/AudiofileManagerTests.cs | 12 ++++++------ .../Services/IO/ImportManagerTests.cs | 2 +- .../Services/AudioCuesheet/AudiofileManager.cs | 2 +- AudioCuesheetEditor/Services/IO/FileInputManager.cs | 2 +- AudioCuesheetEditor/Services/IO/ImportManager.cs | 4 ++-- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/BasicTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/BasicTest.cs index db93ad22..29d0a26f 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/BasicTest.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/BasicTest.cs @@ -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.Group).Filter(new() { HasText = "Test" })).ToMatchAriaSnapshotAsync("- group:\r\n - checkbox\r\n - heading \"Test 123\" [level=6]\r\n - button \"Upload file\"\r\n - button \"Rename file\"\r\n - button \"Clear file\""); + 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/BasicTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTest.cs index b11249d3..47ed8465 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTest.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTest.cs @@ -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.Group).Filter(new() { HasText = "Test" })).ToMatchAriaSnapshotAsync("- group:\r\n - checkbox\r\n - heading \"Test 123\" [level=6]\r\n - button \"Upload file\"\r\n - button \"Rename file\"\r\n - button \"Clear file\""); + 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.Tests/Services/AudioCuesheet/AudiofileManagerTests.cs b/AudioCuesheetEditor.Tests/Services/AudioCuesheet/AudiofileManagerTests.cs index fd788824..15a565fa 100644 --- a/AudioCuesheetEditor.Tests/Services/AudioCuesheet/AudiofileManagerTests.cs +++ b/AudioCuesheetEditor.Tests/Services/AudioCuesheet/AudiofileManagerTests.cs @@ -122,7 +122,7 @@ 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)); @@ -145,7 +145,7 @@ public async Task SetPropertiesAsync_WithBrowserFile_ShouldSetPropertiesAsync() Assert.AreEqual(objectUrl, audiofile.ObjectURL); Assert.AreEqual(TimeSpan.FromSeconds(90), audiofile.Duration); Assert.AreEqual(1, audiofileChangedFired); - _fileInputManager.Verify(f => f.GetAudioCodec("audio/mpeg", filename), Times.Once); + _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); } @@ -162,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() @@ -198,7 +198,7 @@ public async Task SetProperty_AudiofileWithDuration_SetsLastTrackEndAlsoAsync() Assert.AreEqual(TimeSpan.FromSeconds(90), audiofile.Duration); Assert.AreEqual(TimeSpan.FromSeconds(90), track2.End); Assert.AreEqual(1, audiofileChangedFired); - _fileInputManager.Verify(f => f.GetAudioCodec("audio/mpeg", filename), Times.Once); + _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); } @@ -215,7 +215,7 @@ public async Task SetPropertiesAsync_PreviouslySetAudiofile_ShouldClearFirstAndS 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)); @@ -240,7 +240,7 @@ public async Task SetPropertiesAsync_PreviouslySetAudiofile_ShouldClearFirstAndS Assert.AreEqual(objectUrl, audiofile.ObjectURL); Assert.AreEqual(TimeSpan.FromSeconds(90), audiofile.Duration); Assert.AreEqual(1, audiofileChangedFired); - _fileInputManager.Verify(f => f.GetAudioCodec("audio/mpeg", filename), Times.Once); + _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); 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/Services/AudioCuesheet/AudiofileManager.cs b/AudioCuesheetEditor/Services/AudioCuesheet/AudiofileManager.cs index b13642d1..e2eeb63f 100644 --- a/AudioCuesheetEditor/Services/AudioCuesheet/AudiofileManager.cs +++ b/AudioCuesheetEditor/Services/AudioCuesheet/AudiofileManager.cs @@ -43,7 +43,7 @@ public async Task SetPropertiesAsync(Audiofile audiofile, IBrowserFile browserFi { await ClearPropertiesAsync(audiofile, false); } - var codec = _fileInputManager.GetAudioCodec(browserFile.ContentType, browserFile.Name); + var codec = _fileInputManager.GetAudioCodec(browserFile.Name, browserFile.ContentType); var objectUrl = await _fileInputManager.GetObjectUrlAsync(fileInputId); TimeSpan? duration = null; if (String.IsNullOrEmpty(objectUrl) == false) diff --git a/AudioCuesheetEditor/Services/IO/FileInputManager.cs b/AudioCuesheetEditor/Services/IO/FileInputManager.cs index 656b868a..ef25fdd5 100644 --- a/AudioCuesheetEditor/Services/IO/FileInputManager.cs +++ b/AudioCuesheetEditor/Services/IO/FileInputManager.cs @@ -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; diff --git a/AudioCuesheetEditor/Services/IO/ImportManager.cs b/AudioCuesheetEditor/Services/IO/ImportManager.cs index 163ff163..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)