From 6a06e85094db8b3c4e20fe6a023c9942d684456d Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Mon, 16 Feb 2026 13:14:10 +0100 Subject: [PATCH 01/48] update version --- .../AudioCuesheetEditor.End2EndTests.csproj | 4 ++-- .../AudioCuesheetEditor.Tests.csproj | 4 ++-- AudioCuesheetEditor/AudioCuesheetEditor.csproj | 18 +++++++++--------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj b/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj index 6453cf79..4c5c4ccd 100644 --- a/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj +++ b/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj @@ -16,8 +16,8 @@ - - + + diff --git a/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj b/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj index 39ad8f63..0e10bc14 100644 --- a/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj +++ b/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj @@ -12,11 +12,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/AudioCuesheetEditor/AudioCuesheetEditor.csproj b/AudioCuesheetEditor/AudioCuesheetEditor.csproj index fa4071f0..ec6a9afa 100644 --- a/AudioCuesheetEditor/AudioCuesheetEditor.csproj +++ b/AudioCuesheetEditor/AudioCuesheetEditor.csproj @@ -7,7 +7,7 @@ enable https://github.com/NeoCoderMatrix86/AudioCuesheetEditor 3.0 - 10.3.1 + 10.4.0 false true true @@ -21,17 +21,17 @@ - - - + + + - - - + + + - - + + From 68c120690e5f7c2923d1b3c78f626588d7e03f12 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Mon, 16 Feb 2026 16:06:02 +0100 Subject: [PATCH 02/48] best practise corrections --- AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs | 1 - .../Tests/Smartphone/ImportTestSmartphone.cs | 1 - .../Services/IO/CuesheetExportServiceTests.cs | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs index 5554c20b..5d490e53 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs @@ -2149,7 +2149,6 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta public async Task Import_ShouldImportTracks_WhenUsingText() { var importView = new ImportView(TestPage, DeviceName != null); - var detailView = new DetailView(TestPage, DeviceName != null); await importView.GotoAsync(); var text = String.Join(Environment.NewLine, File.ReadAllLines("Sample_Inputfile.txt")); await importView.ImportTextAsync(text); diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTestSmartphone.cs b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTestSmartphone.cs index 5b538dd2..6b8b4b60 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTestSmartphone.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTestSmartphone.cs @@ -3574,7 +3574,6 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta public async Task Import_ShouldImportTracks_WhenUsingText() { var importView = new ImportView(TestPage, DeviceName != null); - var detailView = new DetailView(TestPage, DeviceName != null); await importView.GotoAsync(); var text = String.Join(Environment.NewLine, File.ReadAllLines("Sample_Inputfile.txt")); await importView.ImportTextAsync(text); diff --git a/AudioCuesheetEditor.Tests/Services/IO/CuesheetExportServiceTests.cs b/AudioCuesheetEditor.Tests/Services/IO/CuesheetExportServiceTests.cs index f103792d..968fc387 100644 --- a/AudioCuesheetEditor.Tests/Services/IO/CuesheetExportServiceTests.cs +++ b/AudioCuesheetEditor.Tests/Services/IO/CuesheetExportServiceTests.cs @@ -49,7 +49,7 @@ public void CanGenerateExportfiles_InvalidExtension_ReturnsValidationMessage() var result = cuesheetExportService.CanGenerateExportfiles(invalidFilename); // Assert - Assert.IsTrue(result.Any(vm => vm.Message.Contains("File extension is not"))); + Assert.Contains(vm => vm.Message.Contains("File extension is not"), result); } [TestMethod] From c8fecb05abe4cb2e217169a282f77087729bc8e1 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Tue, 17 Feb 2026 16:11:07 +0100 Subject: [PATCH 03/48] added TODOs --- AudioCuesheetEditor/Pages/Index.razor | 1 + AudioCuesheetEditor/Services/IO/ImportManager.cs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/AudioCuesheetEditor/Pages/Index.razor b/AudioCuesheetEditor/Pages/Index.razor index 8fe46021..c171fc31 100644 --- a/AudioCuesheetEditor/Pages/Index.razor +++ b/AudioCuesheetEditor/Pages/Index.razor @@ -93,6 +93,7 @@ along with Foobar. If not, see void ImportManager_UploadFilesFinished(object? sender, IEnumerable invalidFiles) { + //TODO: We should add a dialog asking if the importfile should be imported InvokeAsync(async () => { try diff --git a/AudioCuesheetEditor/Services/IO/ImportManager.cs b/AudioCuesheetEditor/Services/IO/ImportManager.cs index a1323d13..c4307df6 100644 --- a/AudioCuesheetEditor/Services/IO/ImportManager.cs +++ b/AudioCuesheetEditor/Services/IO/ImportManager.cs @@ -87,6 +87,8 @@ public async Task AnalyseImportfile() break; case ImportFileType.Cuesheet: _traceChangeManager.BulkEdit = true; + //TODO: Set _sessionStateContainer.Cuesheet to a new empty cuesheet + //TODO: Tests CopyCuesheet(_sessionStateContainer.Cuesheet, _sessionStateContainer.Importfile.AnalyzedCuesheet); _traceChangeManager.BulkEdit = false; break; From d85b9b58a8fe37e1c9532f6a24c67c0547be9b50 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Tue, 17 Feb 2026 16:12:26 +0100 Subject: [PATCH 04/48] fixed TODOs --- AudioCuesheetEditor/Pages/Index.razor | 1 - AudioCuesheetEditor/Shared/AppBar.razor | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/AudioCuesheetEditor/Pages/Index.razor b/AudioCuesheetEditor/Pages/Index.razor index c171fc31..8fe46021 100644 --- a/AudioCuesheetEditor/Pages/Index.razor +++ b/AudioCuesheetEditor/Pages/Index.razor @@ -93,7 +93,6 @@ along with Foobar. If not, see void ImportManager_UploadFilesFinished(object? sender, IEnumerable invalidFiles) { - //TODO: We should add a dialog asking if the importfile should be imported InvokeAsync(async () => { try diff --git a/AudioCuesheetEditor/Shared/AppBar.razor b/AudioCuesheetEditor/Shared/AppBar.razor index d106a201..43d6558d 100644 --- a/AudioCuesheetEditor/Shared/AppBar.razor +++ b/AudioCuesheetEditor/Shared/AppBar.razor @@ -189,6 +189,7 @@ along with Foobar. If not, see async Task OpenFileClicked() { + //TODO: Check for changes to cuesheet and if there are any, ask user via dialog for confirmation if (mudFileUpload != null) { await mudFileUpload.OpenFilePickerAsync(); From 19f8cb1f39710f79913309e7c76691688ca7a52d Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Wed, 18 Feb 2026 08:31:06 +0100 Subject: [PATCH 05/48] Update ImportManagerTests.cs --- .../Services/IO/ImportManagerTests.cs | 263 +++++++++--------- 1 file changed, 128 insertions(+), 135 deletions(-) diff --git a/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs b/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs index f5893914..9b7606c9 100644 --- a/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs +++ b/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs @@ -17,6 +17,7 @@ using AudioCuesheetEditor.Model.AudioCuesheet; using AudioCuesheetEditor.Model.AudioCuesheet.Import; using AudioCuesheetEditor.Model.IO; +using AudioCuesheetEditor.Model.IO.Audio; using AudioCuesheetEditor.Model.IO.Import; using AudioCuesheetEditor.Model.UI; using AudioCuesheetEditor.Services.IO; @@ -39,15 +40,27 @@ namespace AudioCuesheetEditor.Tests.Services.IO [TestClass()] public class ImportManagerTests { + private readonly ImportManager _service; + private readonly Mock _sessionStateContainerMock; + private readonly Mock _traceChangeManagerMock; + private readonly Mock _fileInputManagerMock; + private readonly Mock _textImportServiceMock; + + public ImportManagerTests() + { + _traceChangeManagerMock = new(); + _sessionStateContainerMock = new(); + _fileInputManagerMock = new(); + _textImportServiceMock = new(); + var loggerMock = new Mock>(); + _service = new ImportManager(_sessionStateContainerMock.Object, _traceChangeManagerMock.Object, _fileInputManagerMock.Object, _textImportServiceMock.Object, loggerMock.Object); + } + [TestMethod()] public async Task AnalyseImportfile_WithTextfile_SetsImportCuesheet() { // Arrange var fileContent = "This is just a test"; - var traceChangeManagerMock = new Mock(); - var sessionStateContainer = new SessionStateContainer(traceChangeManagerMock.Object); - var fileInputManagerMock = new Mock(); - var textImportServiceMock = new Mock(); var importCuesheet = new ImportCuesheet() { Artist = "Test Cuesheet Artist", @@ -67,42 +80,41 @@ public async Task AnalyseImportfile_WithTextfile_SetsImportCuesheet() PreGap = new TimeSpan(0, 0, 2), PostGap = new TimeSpan(0, 0, 4) }); - var importfile = new Importfile() + var importFile = new Importfile() { FileContent = fileContent, FileType = ImportFileType.Textfile, AnalyzedCuesheet = importCuesheet }; - textImportServiceMock.Setup(x => x.AnalyseAsync(fileContent)).ReturnsAsync(importfile); - var loggerMock = new Mock>(); - var importManager = new ImportManager(sessionStateContainer, traceChangeManagerMock.Object, fileInputManagerMock.Object, textImportServiceMock.Object, loggerMock.Object); - sessionStateContainer.Importfile = new Importfile() - { - FileContent = "This is just a test", - FileType = ImportFileType.Textfile - }; + _textImportServiceMock.Setup(x => x.AnalyseAsync(fileContent)).ReturnsAsync(importFile); + _sessionStateContainerMock.SetupGet(x => x.Importfile).Returns(importFile); + Cuesheet? sessionStateContainerImportCuesheet = null; + _sessionStateContainerMock.SetupSet(x => x.ImportCuesheet = It.IsAny()).Callback(cs => sessionStateContainerImportCuesheet = cs); + _sessionStateContainerMock.SetupGet(x => x.ImportCuesheet).Returns(() => sessionStateContainerImportCuesheet); + IImportfile? sessionStateContainerImportfile = null; + _sessionStateContainerMock.SetupSet(x => x.Importfile = It.IsAny()).Callback(x => sessionStateContainerImportfile = x); // Act - await importManager.AnalyseImportfile(); + await _service.AnalyseImportfile(); // Assert - Assert.AreEqual(importfile, sessionStateContainer.Importfile); - Assert.IsNotNull(sessionStateContainer.ImportCuesheet); - Assert.AreEqual(importCuesheet.Artist, sessionStateContainer.ImportCuesheet.Artist); - Assert.AreEqual(importCuesheet.Title, sessionStateContainer.ImportCuesheet.Title); - Assert.IsNotNull(sessionStateContainer.ImportCuesheet.Audiofile); - Assert.AreEqual(importCuesheet.Audiofile, sessionStateContainer.ImportCuesheet.Audiofile.Name); - Assert.AreEqual(importCuesheet.Cataloguenumber, sessionStateContainer.ImportCuesheet.Cataloguenumber); - Assert.IsNotNull(sessionStateContainer.ImportCuesheet.CDTextfile); - Assert.AreEqual(importCuesheet.CDTextfile, sessionStateContainer.ImportCuesheet.CDTextfile.Name); - Assert.AreEqual(importCuesheet.Tracks.First().Artist, sessionStateContainer.ImportCuesheet.Tracks.First().Artist); - Assert.AreEqual(importCuesheet.Tracks.First().Title, sessionStateContainer.ImportCuesheet.Tracks.First().Title); - Assert.AreEqual(importCuesheet.Tracks.First().Begin, sessionStateContainer.ImportCuesheet.Tracks.First().Begin); - Assert.AreEqual(importCuesheet.Tracks.First().End, sessionStateContainer.ImportCuesheet.Tracks.First().End); - CollectionAssert.AreEquivalent(importCuesheet.Tracks.First().Flags.ToList(), sessionStateContainer.ImportCuesheet.Tracks.First().Flags.ToList()); - Assert.AreEqual(importCuesheet.Tracks.First().Position, sessionStateContainer.ImportCuesheet.Tracks.First().Position); - Assert.AreEqual(importCuesheet.Tracks.First().PreGap, sessionStateContainer.ImportCuesheet.Tracks.First().PreGap); - Assert.AreEqual(importCuesheet.Tracks.First().PostGap, sessionStateContainer.ImportCuesheet.Tracks.First().PostGap); - traceChangeManagerMock.Verify(x => x.TraceChanges(It.IsAny())); - traceChangeManagerMock.Verify(x => x.TraceChanges(It.IsAny())); + Assert.AreEqual(importFile, sessionStateContainerImportfile); + Assert.IsNotNull(sessionStateContainerImportCuesheet); + Assert.AreEqual(importCuesheet.Artist, sessionStateContainerImportCuesheet.Artist); + Assert.AreEqual(importCuesheet.Title, sessionStateContainerImportCuesheet.Title); + Assert.IsNotNull(sessionStateContainerImportCuesheet.Audiofile); + Assert.AreEqual(importCuesheet.Audiofile, sessionStateContainerImportCuesheet.Audiofile.Name); + Assert.AreEqual(importCuesheet.Cataloguenumber, sessionStateContainerImportCuesheet.Cataloguenumber); + Assert.IsNotNull(sessionStateContainerImportCuesheet.CDTextfile); + Assert.AreEqual(importCuesheet.CDTextfile, sessionStateContainerImportCuesheet.CDTextfile.Name); + Assert.AreEqual(importCuesheet.Tracks.First().Artist, sessionStateContainerImportCuesheet.Tracks.First().Artist); + Assert.AreEqual(importCuesheet.Tracks.First().Title, sessionStateContainerImportCuesheet.Tracks.First().Title); + Assert.AreEqual(importCuesheet.Tracks.First().Begin, sessionStateContainerImportCuesheet.Tracks.First().Begin); + Assert.AreEqual(importCuesheet.Tracks.First().End, sessionStateContainerImportCuesheet.Tracks.First().End); + CollectionAssert.AreEquivalent(importCuesheet.Tracks.First().Flags.ToList(), sessionStateContainerImportCuesheet.Tracks.First().Flags.ToList()); + Assert.AreEqual(importCuesheet.Tracks.First().Position, sessionStateContainerImportCuesheet.Tracks.First().Position); + Assert.AreEqual(importCuesheet.Tracks.First().PreGap, sessionStateContainerImportCuesheet.Tracks.First().PreGap); + Assert.AreEqual(importCuesheet.Tracks.First().PostGap, sessionStateContainerImportCuesheet.Tracks.First().PostGap); + _traceChangeManagerMock.Verify(x => x.TraceChanges(It.IsAny())); + _traceChangeManagerMock.Verify(x => x.TraceChanges(It.IsAny())); } [TestMethod()] @@ -110,24 +122,22 @@ public async Task AnalyseImportfile_WithoutAnalysedCuesheet_DoesNothing() { // Arrange var fileContent = "This is just a test"; - var traceChangeManager = new TraceChangeManager(TestHelper.CreateLogger()); - var sessionStateContainer = new SessionStateContainer(traceChangeManager); - var fileInputManagerMock = new Mock(); - var textImportServiceMock = new Mock(); - var importfile = new Importfile() + var importFile = new Importfile() { FileContent = fileContent, FileType = ImportFileType.Textfile, }; - textImportServiceMock.Setup(x => x.AnalyseAsync(fileContent)).ReturnsAsync(importfile); - var loggerMock = new Mock>(); - var importManager = new ImportManager(sessionStateContainer, traceChangeManager, fileInputManagerMock.Object, textImportServiceMock.Object, loggerMock.Object); - sessionStateContainer.Importfile = importfile; + _textImportServiceMock.Setup(x => x.AnalyseAsync(fileContent)).ReturnsAsync(importFile); + _sessionStateContainerMock.SetupGet(x => x.Importfile).Returns(importFile); + Cuesheet? sessionStateContainerImportCuesheet = null; + _sessionStateContainerMock.SetupSet(x => x.ImportCuesheet = It.IsAny()).Callback(cs => sessionStateContainerImportCuesheet = cs); + IImportfile? sessionStateContainerImportfile = null; + _sessionStateContainerMock.SetupSet(x => x.Importfile = It.IsAny()).Callback(x => sessionStateContainerImportfile = x); // Act - await importManager.AnalyseImportfile(); + await _service.AnalyseImportfile(); // Assert - Assert.AreEqual(importfile, sessionStateContainer.Importfile); - Assert.IsNull(sessionStateContainer.ImportCuesheet); + Assert.AreEqual(importFile, sessionStateContainerImportfile); + Assert.IsNull(sessionStateContainerImportCuesheet); } [TestMethod] @@ -136,24 +146,20 @@ public async Task UploadFilesAsync_ProjectFile_ImportsCorrectly() // Arrange var fileContent = "This is the content"; var file = CreateBrowserFileMock("test.projectfile", fileContent); - var traceChangeManager = new TraceChangeManager(TestHelper.CreateLogger()); - var sessionStateContainer = new SessionStateContainer(traceChangeManager); - var fileInputManagerMock = new Mock(); - var textImportServiceMock = new Mock(); - fileInputManagerMock.Setup(f => f.CheckFileMimeType(file, FileMimeTypes.Projectfile, It.IsAny>())).Returns(true); - fileInputManagerMock.Setup(f => f.CheckFileMimeType(file, FileMimeTypes.Cuesheet, It.IsAny>())).Returns(false); - fileInputManagerMock.Setup(f => f.IsValidForImportView(file)).Returns(false); + _fileInputManagerMock.Setup(f => f.CheckFileMimeType(file, FileMimeTypes.Projectfile, It.IsAny>())).Returns(true); + _fileInputManagerMock.Setup(f => f.CheckFileMimeType(file, FileMimeTypes.Cuesheet, It.IsAny>())).Returns(false); + _fileInputManagerMock.Setup(f => f.IsValidForImportView(file)).Returns(false); - var loggerMock = new Mock>(); - var importManager = new ImportManager(sessionStateContainer, traceChangeManager, fileInputManagerMock.Object, textImportServiceMock.Object, loggerMock.Object); + IImportfile? sessionStateContainerImportfile = null; + _sessionStateContainerMock.SetupSet(x => x.Importfile = It.IsAny()).Callback(x => sessionStateContainerImportfile = x); // Act - await importManager.UploadFilesAsync([file]); + await _service.UploadFilesAsync([file]); // Assert - Assert.IsNotNull(sessionStateContainer.Importfile); - Assert.AreEqual(fileContent, sessionStateContainer.Importfile.FileContent); - Assert.AreEqual(fileContent, sessionStateContainer.Importfile.FileContentRecognized); - Assert.AreEqual(ImportFileType.ProjectFile, sessionStateContainer.Importfile.FileType); + Assert.IsNotNull(sessionStateContainerImportfile); + Assert.AreEqual(fileContent, sessionStateContainerImportfile.FileContent); + Assert.AreEqual(fileContent, sessionStateContainerImportfile.FileContentRecognized); + Assert.AreEqual(ImportFileType.ProjectFile, sessionStateContainerImportfile.FileType); } [TestMethod] @@ -162,24 +168,21 @@ public async Task UploadFilesAsync_CuesheetFile_ImportsCorrectly() // Arrange var fileContent = "Cuesheet file content"; var file = CreateBrowserFileMock("test.cue", fileContent); - var traceChangeManager = new TraceChangeManager(TestHelper.CreateLogger()); - var sessionStateContainer = new SessionStateContainer(traceChangeManager); - var fileInputManagerMock = new Mock(); - var textImportServiceMock = new Mock(); - fileInputManagerMock.Setup(f => f.CheckFileMimeType(file, FileMimeTypes.Projectfile, It.IsAny>())).Returns(false); - fileInputManagerMock.Setup(f => f.CheckFileMimeType(file, FileMimeTypes.Cuesheet, It.IsAny>())).Returns(true); - fileInputManagerMock.Setup(f => f.IsValidForImportView(file)).Returns(false); + + _fileInputManagerMock.Setup(f => f.CheckFileMimeType(file, FileMimeTypes.Projectfile, It.IsAny>())).Returns(false); + _fileInputManagerMock.Setup(f => f.CheckFileMimeType(file, FileMimeTypes.Cuesheet, It.IsAny>())).Returns(true); + _fileInputManagerMock.Setup(f => f.IsValidForImportView(file)).Returns(false); - var loggerMock = new Mock>(); - var importManager = new ImportManager(sessionStateContainer, traceChangeManager, fileInputManagerMock.Object, textImportServiceMock.Object, loggerMock.Object); + IImportfile? sessionStateContainerImportfile = null; + _sessionStateContainerMock.SetupSet(x => x.Importfile = It.IsAny()).Callback(x => sessionStateContainerImportfile = x); // Act - await importManager.UploadFilesAsync([file]); + await _service.UploadFilesAsync([file]); // Assert - Assert.IsNotNull(sessionStateContainer.Importfile); - Assert.AreEqual(fileContent, sessionStateContainer.Importfile.FileContent); - Assert.AreEqual(fileContent, sessionStateContainer.Importfile.FileContentRecognized); - Assert.AreEqual(ImportFileType.Cuesheet, sessionStateContainer.Importfile.FileType); + Assert.IsNotNull(sessionStateContainerImportfile); + Assert.AreEqual(fileContent, sessionStateContainerImportfile.FileContent); + Assert.AreEqual(fileContent, sessionStateContainerImportfile.FileContentRecognized); + Assert.AreEqual(ImportFileType.Cuesheet, sessionStateContainerImportfile.FileType); } [TestMethod] @@ -188,24 +191,21 @@ public async Task UploadFilesAsync_TextFile_ImportsCorrectly() // Arrange var fileContent = "TextFileContent"; var file = CreateBrowserFileMock("test.txt", fileContent); - var traceChangeManager = new TraceChangeManager(TestHelper.CreateLogger()); - var sessionStateContainer = new SessionStateContainer(traceChangeManager); - var fileInputManagerMock = new Mock(); - var textImportServiceMock = new Mock(); - fileInputManagerMock.Setup(f => f.CheckFileMimeType(file, FileMimeTypes.Projectfile, It.IsAny>())).Returns(false); - fileInputManagerMock.Setup(f => f.CheckFileMimeType(file, FileMimeTypes.Cuesheet, It.IsAny>())).Returns(false); - fileInputManagerMock.Setup(f => f.IsValidForImportView(file)).Returns(true); + + _fileInputManagerMock.Setup(f => f.CheckFileMimeType(file, FileMimeTypes.Projectfile, It.IsAny>())).Returns(false); + _fileInputManagerMock.Setup(f => f.CheckFileMimeType(file, FileMimeTypes.Cuesheet, It.IsAny>())).Returns(false); + _fileInputManagerMock.Setup(f => f.IsValidForImportView(file)).Returns(true); - var loggerMock = new Mock>(); - var importManager = new ImportManager(sessionStateContainer, traceChangeManager, fileInputManagerMock.Object, textImportServiceMock.Object, loggerMock.Object); + IImportfile? sessionStateContainerImportfile = null; + _sessionStateContainerMock.SetupSet(x => x.Importfile = It.IsAny()).Callback(x => sessionStateContainerImportfile = x); // Act - await importManager.UploadFilesAsync([file]); + await _service.UploadFilesAsync([file]); // Assert - Assert.IsNotNull(sessionStateContainer.Importfile); - Assert.AreEqual(fileContent, sessionStateContainer.Importfile.FileContent); - Assert.AreEqual(fileContent, sessionStateContainer.Importfile.FileContentRecognized); - Assert.AreEqual(ImportFileType.Textfile, sessionStateContainer.Importfile.FileType); + Assert.IsNotNull(sessionStateContainerImportfile); + Assert.AreEqual(fileContent, sessionStateContainerImportfile.FileContent); + Assert.AreEqual(fileContent, sessionStateContainerImportfile.FileContentRecognized); + Assert.AreEqual(ImportFileType.Textfile, sessionStateContainerImportfile.FileType); } [TestMethod] @@ -213,88 +213,81 @@ public async Task UploadFilesAsync_WithAudiofile_ImportsCorrectly() { // Arrange var file = CreateBrowserFileMock("test.mp3"); - var traceChangeManager = new TraceChangeManager(TestHelper.CreateLogger()); - var sessionStateContainer = new SessionStateContainer(traceChangeManager); - var fileInputManagerMock = new Mock(); - var textImportServiceMock = new Mock(); - fileInputManagerMock.Setup(f => f.CheckFileMimeType(file, FileMimeTypes.Projectfile, It.IsAny>())).Returns(false); - fileInputManagerMock.Setup(f => f.CheckFileMimeType(file, FileMimeTypes.Cuesheet, It.IsAny>())).Returns(false); - fileInputManagerMock.Setup(f => f.IsValidForImportView(file)).Returns(false); - fileInputManagerMock.Setup(f => f.IsValidAudiofile(file)).Returns(true); - fileInputManagerMock.Setup(f => f.CreateAudiofileAsync(It.IsAny(), It.IsAny(), It.IsAny>?>())).ReturnsAsync(new AudioCuesheetEditor.Model.IO.Audio.Audiofile(file.Name)); + + _fileInputManagerMock.Setup(f => f.CheckFileMimeType(file, FileMimeTypes.Projectfile, It.IsAny>())).Returns(false); + _fileInputManagerMock.Setup(f => f.CheckFileMimeType(file, FileMimeTypes.Cuesheet, It.IsAny>())).Returns(false); + _fileInputManagerMock.Setup(f => f.IsValidForImportView(file)).Returns(false); + _fileInputManagerMock.Setup(f => f.IsValidAudiofile(file)).Returns(true); + _fileInputManagerMock.Setup(f => f.CreateAudiofileAsync(It.IsAny(), It.IsAny(), It.IsAny>?>())).ReturnsAsync(new AudioCuesheetEditor.Model.IO.Audio.Audiofile(file.Name)); - var loggerMock = new Mock>(); - var importManager = new ImportManager(sessionStateContainer, traceChangeManager, fileInputManagerMock.Object, textImportServiceMock.Object, loggerMock.Object); + IImportfile? sessionStateContainerImportfile = null; + _sessionStateContainerMock.SetupSet(x => x.Importfile = It.IsAny()).Callback(x => sessionStateContainerImportfile = x); + Audiofile? sessionStateContainerImportAudiofile = null; + _sessionStateContainerMock.SetupSet(x => x.ImportAudiofile = It.IsAny()).Callback(x => sessionStateContainerImportAudiofile = x); // Act - await importManager.UploadFilesAsync([file]); + await _service.UploadFilesAsync([file]); // Assert - Assert.IsNull(sessionStateContainer.Importfile); - Assert.IsNotNull(sessionStateContainer.ImportAudiofile); + Assert.IsNull(sessionStateContainerImportfile); + Assert.IsNotNull(sessionStateContainerImportAudiofile); } [TestMethod] public void ImportCuesheet_WithImportCuesheetAvailable_ImportsCuesheetData() { // Arrange - var traceChangeManagerMock = new Mock(); - var sessionStateContainer = new SessionStateContainer(traceChangeManagerMock.Object); - var analyzedCuesheet = new Cuesheet() + var sessionStateContainerImportCuesheet = new Cuesheet() { Artist = "Artist 123", Title = "Title 456" }; - analyzedCuesheet.AddTrack(new() + sessionStateContainerImportCuesheet.AddTrack(new() { Artist = "Track Artist 1", Title = "Track Title 1", End = new TimeSpan(0, 4, 23), }); - analyzedCuesheet.AddTrack(new() + sessionStateContainerImportCuesheet.AddTrack(new() { Artist = "Track Artist 2", Title = "Track Title 2", End = new TimeSpan(0, 8, 54), }); - sessionStateContainer.ImportCuesheet = analyzedCuesheet; - var fileInputManagerMock = new Mock(); - var textImportServiceMock = new Mock(); - var loggerMock = new Mock>(); - var importManager = new ImportManager(sessionStateContainer, traceChangeManagerMock.Object, fileInputManagerMock.Object, textImportServiceMock.Object, loggerMock.Object); + _sessionStateContainerMock.SetupGet(x => x.ImportCuesheet).Returns(sessionStateContainerImportCuesheet); + Cuesheet sessionStateContainerCuesheet = new(); + _sessionStateContainerMock.SetupSet(x => x.Cuesheet = It.IsAny()).Callback(cuesheet => sessionStateContainerCuesheet = cuesheet); + _sessionStateContainerMock.SetupGet(x => x.Cuesheet).Returns(() => sessionStateContainerCuesheet); // Act - importManager.ImportCuesheet(); + _service.ImportCuesheet(); // Assert - Assert.AreEqual(analyzedCuesheet.Artist, sessionStateContainer.Cuesheet.Artist); - Assert.AreEqual(analyzedCuesheet.Title, sessionStateContainer.Cuesheet.Title); - Assert.AreEqual(analyzedCuesheet.Tracks.First().Artist, sessionStateContainer.Cuesheet.Tracks.First().Artist); - Assert.AreEqual(analyzedCuesheet.Tracks.First().Title, sessionStateContainer.Cuesheet.Tracks.First().Title); - Assert.AreEqual(analyzedCuesheet.Tracks.First().Begin, sessionStateContainer.Cuesheet.Tracks.First().Begin); - Assert.AreEqual(analyzedCuesheet.Tracks.First().End, sessionStateContainer.Cuesheet.Tracks.First().End); - Assert.AreEqual(analyzedCuesheet.Tracks.Last().Artist, sessionStateContainer.Cuesheet.Tracks.Last().Artist); - Assert.AreEqual(analyzedCuesheet.Tracks.Last().Title, sessionStateContainer.Cuesheet.Tracks.Last().Title); - Assert.AreEqual(analyzedCuesheet.Tracks.Last().Begin, sessionStateContainer.Cuesheet.Tracks.Last().Begin); - Assert.AreEqual(analyzedCuesheet.Tracks.Last().End, sessionStateContainer.Cuesheet.Tracks.Last().End); - traceChangeManagerMock.Verify(x => x.RemoveTracedChanges(It.IsAny>())); + Assert.IsNotNull(sessionStateContainerCuesheet); + Assert.AreEqual(sessionStateContainerImportCuesheet.Artist, sessionStateContainerCuesheet.Artist); + Assert.AreEqual(sessionStateContainerImportCuesheet.Title, sessionStateContainerCuesheet.Title); + Assert.AreEqual(sessionStateContainerImportCuesheet.Tracks.First().Artist, sessionStateContainerCuesheet.Tracks.First().Artist); + Assert.AreEqual(sessionStateContainerImportCuesheet.Tracks.First().Title, sessionStateContainerCuesheet.Tracks.First().Title); + Assert.AreEqual(sessionStateContainerImportCuesheet.Tracks.First().Begin, sessionStateContainerCuesheet.Tracks.First().Begin); + Assert.AreEqual(sessionStateContainerImportCuesheet.Tracks.First().End, sessionStateContainerCuesheet.Tracks.First().End); + Assert.AreEqual(sessionStateContainerImportCuesheet.Tracks.Last().Artist, sessionStateContainerCuesheet.Tracks.Last().Artist); + Assert.AreEqual(sessionStateContainerImportCuesheet.Tracks.Last().Title, sessionStateContainerCuesheet.Tracks.Last().Title); + Assert.AreEqual(sessionStateContainerImportCuesheet.Tracks.Last().Begin, sessionStateContainerCuesheet.Tracks.Last().Begin); + Assert.AreEqual(sessionStateContainerImportCuesheet.Tracks.Last().End, sessionStateContainerCuesheet.Tracks.Last().End); + _traceChangeManagerMock.Verify(x => x.RemoveTracedChanges(It.IsAny>())); } [TestMethod] public void ImportData_ValidData_SetsImportfile() { // Arrange - var traceChangeManagerMock = new Mock(); - var sessionStateContainer = new SessionStateContainer(traceChangeManagerMock.Object); - var importdata = nameof(ImportData_ValidData_SetsImportfile); - var fileInputManagerMock = new Mock(); - var textImportServiceMock = new Mock(); - var loggerMock = new Mock>(); - var importManager = new ImportManager(sessionStateContainer, traceChangeManagerMock.Object, fileInputManagerMock.Object, textImportServiceMock.Object, loggerMock.Object); + var importData = nameof(ImportData_ValidData_SetsImportfile); + IImportfile? sessionStateContainerImportfile = null; + _sessionStateContainerMock.SetupSet(x => x.Importfile = It.IsAny()).Callback(x => sessionStateContainerImportfile = x); // Act - importManager.ImportData(importdata); + _service.ImportData(importData); // Assert - Assert.IsNotNull(sessionStateContainer.Importfile); - Assert.AreEqual(importdata, sessionStateContainer.Importfile.FileContent); - Assert.AreEqual(importdata, sessionStateContainer.Importfile.FileContentRecognized); - Assert.AreEqual(ImportFileType.Textfile, sessionStateContainer.Importfile.FileType); + Assert.IsNotNull(sessionStateContainerImportfile); + Assert.AreEqual(importData, sessionStateContainerImportfile.FileContent); + Assert.AreEqual(importData, sessionStateContainerImportfile.FileContentRecognized); + Assert.AreEqual(ImportFileType.Textfile, sessionStateContainerImportfile.FileType); } private static IBrowserFile CreateBrowserFileMock(string name, string content = "TestContent") From 9344f78d71ac6b8414b2a3655c085f3704a2ba93 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Wed, 18 Feb 2026 08:56:14 +0100 Subject: [PATCH 06/48] set new cuesheet when importing cuesheet --- .../Services/IO/ImportManagerTests.cs | 72 ++++++++++++++++++- .../Services/IO/ImportManager.cs | 3 +- 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs b/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs index 9b7606c9..5f0fe857 100644 --- a/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs +++ b/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs @@ -22,7 +22,6 @@ using AudioCuesheetEditor.Model.UI; using AudioCuesheetEditor.Services.IO; using AudioCuesheetEditor.Services.UI; -using AudioCuesheetEditor.Tests.Utility; using Microsoft.AspNetCore.Components.Forms; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -140,6 +139,77 @@ public async Task AnalyseImportfile_WithoutAnalysedCuesheet_DoesNothing() Assert.IsNull(sessionStateContainerImportCuesheet); } + [TestMethod()] + public async Task AnalyseImportfile_ExistingCuesheet_OverwritesCuesheet() + { + // Arrange + var sessionStateContainerCuesheet = new Cuesheet() + { + Artist = "Test Cuesheet Artist", + Title = "Test Cuesheet Title", + Cataloguenumber = "Test Cuesheet Cataloguenumber" + }; + sessionStateContainerCuesheet.AddTrack(new() + { + Artist = "Test Track Artist 1", + Title = "Test Track Title 1", + Begin = new TimeSpan(0, 3, 20), + End = new TimeSpan(0, 7, 43), + Flags = [Flag.DCP], + Position = 1, + PreGap = new TimeSpan(0, 0, 2), + PostGap = new TimeSpan(0, 0, 4) + }); + var importCuesheet = new ImportCuesheet() + { + Artist = "Test ImportCuesheet Artist", + Title = "Test ImportCuesheet Title", + Audiofile = "Test ImportCuesheet Audiofile", + Cataloguenumber = "Test ImportCuesheet Cataloguenumber", + CDTextfile = "Test ImportCuesheet CDTextfile" + }; + importCuesheet.Tracks.Add(new() + { + Artist = "Test ImportCuesheet Track Artist 1", + Title = "Test ImportCuesheet Track Title 1", + Begin = new TimeSpan(0, 0, 10), + End = new TimeSpan(0, 5, 23), + Flags = [Flag.SCMS], + Position = 1, + PreGap = new TimeSpan(0, 0, 4), + PostGap = new TimeSpan(0, 0, 6) + }); + var sessionStateContainerImportFile = new Importfile() + { + FileContent = "Some file content!", + FileType = ImportFileType.Cuesheet, + AnalyzedCuesheet = importCuesheet + }; + + _sessionStateContainerMock.SetupGet(x => x.Cuesheet).Returns(() => sessionStateContainerCuesheet); + _sessionStateContainerMock.SetupSet(x => x.Cuesheet = It.IsAny()).Callback(cuesheet => sessionStateContainerCuesheet = cuesheet); + _sessionStateContainerMock.SetupGet(x => x.Importfile).Returns(() => sessionStateContainerImportFile); + + // Act + await _service.AnalyseImportfile(); + // Assert + Assert.AreEqual(importCuesheet.Artist, sessionStateContainerCuesheet.Artist); + Assert.AreEqual(importCuesheet.Title, sessionStateContainerCuesheet.Title); + Assert.IsNotNull(sessionStateContainerCuesheet.Audiofile); + Assert.AreEqual(importCuesheet.Audiofile, sessionStateContainerCuesheet.Audiofile.Name); + Assert.IsNotNull(sessionStateContainerCuesheet.CDTextfile); + Assert.AreEqual(importCuesheet.CDTextfile, sessionStateContainerCuesheet.CDTextfile.Name); + Assert.AreEqual(importCuesheet.Tracks.Count, sessionStateContainerCuesheet.Tracks.Count); + Assert.AreEqual(importCuesheet.Tracks.First().Artist, sessionStateContainerCuesheet.Tracks.First().Artist); + Assert.AreEqual(importCuesheet.Tracks.First().Begin, sessionStateContainerCuesheet.Tracks.First().Begin); + Assert.AreEqual(importCuesheet.Tracks.First().End, sessionStateContainerCuesheet.Tracks.First().End); + CollectionAssert.AreEquivalent(importCuesheet.Tracks.First().Flags.ToList(), sessionStateContainerCuesheet.Tracks.First().Flags.ToList()); + Assert.AreEqual(importCuesheet.Tracks.First().Position, sessionStateContainerCuesheet.Tracks.First().Position); + Assert.AreEqual(importCuesheet.Tracks.First().PostGap, sessionStateContainerCuesheet.Tracks.First().PostGap); + Assert.AreEqual(importCuesheet.Tracks.First().PreGap, sessionStateContainerCuesheet.Tracks.First().PreGap); + Assert.AreEqual(importCuesheet.Tracks.First().Title, sessionStateContainerCuesheet.Tracks.First().Title); + } + [TestMethod] public async Task UploadFilesAsync_ProjectFile_ImportsCorrectly() { diff --git a/AudioCuesheetEditor/Services/IO/ImportManager.cs b/AudioCuesheetEditor/Services/IO/ImportManager.cs index c4307df6..0d1a8745 100644 --- a/AudioCuesheetEditor/Services/IO/ImportManager.cs +++ b/AudioCuesheetEditor/Services/IO/ImportManager.cs @@ -87,8 +87,7 @@ public async Task AnalyseImportfile() break; case ImportFileType.Cuesheet: _traceChangeManager.BulkEdit = true; - //TODO: Set _sessionStateContainer.Cuesheet to a new empty cuesheet - //TODO: Tests + _sessionStateContainer.Cuesheet = new(); CopyCuesheet(_sessionStateContainer.Cuesheet, _sessionStateContainer.Importfile.AnalyzedCuesheet); _traceChangeManager.BulkEdit = false; break; From 4427e3ca99c00a521e3aa049d078237357adc548 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Wed, 18 Feb 2026 09:09:39 +0100 Subject: [PATCH 07/48] Ask for user confirmation upon open file --- AudioCuesheetEditor/Shared/AppBar.razor | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/AudioCuesheetEditor/Shared/AppBar.razor b/AudioCuesheetEditor/Shared/AppBar.razor index 43d6558d..be44a017 100644 --- a/AudioCuesheetEditor/Shared/AppBar.razor +++ b/AudioCuesheetEditor/Shared/AppBar.razor @@ -189,7 +189,29 @@ along with Foobar. If not, see async Task OpenFileClicked() { - //TODO: Check for changes to cuesheet and if there are any, ask user via dialog for confirmation + //Check if there are already any changes + if ((TraceChangeManager.CanUndo) || (TraceChangeManager.CanRedo)) + { + var parameters = new DialogParameters + { + { x => x.ConfirmText, _localizer["Are you sure you want reset the cuesheet? All information you have not saved are lost!"] }, + }; + var options = new DialogOptions() { BackdropClick = false }; + var dialog = await _dialogService.ShowAsync(_localizer["Confirm"], parameters, options); + var result = await dialog.Result; + if (result?.Canceled == false) + { + await OpenFileUpload(); + } + } + else + { + await OpenFileUpload(); + } + } + + async Task OpenFileUpload() + { if (mudFileUpload != null) { await mudFileUpload.OpenFilePickerAsync(); From 6c8ab9cb35adbc564862d299444a7e69813f1a82 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Wed, 18 Feb 2026 09:14:54 +0100 Subject: [PATCH 08/48] Rename ConfirmationDialog --- .../Services/IO/ImportManagerTests.cs | 2 +- AudioCuesheetEditor/Shared/AppBar.razor | 12 ++++++------ .../Shared/Cuesheet/EditSections.razor | 4 ++-- ...firmDialog.de.resx => ConfirmationDialog.de.resx} | 0 ...{ConfirmDialog.razor => ConfirmationDialog.razor} | 2 +- .../{ConfirmDialog.resx => ConfirmationDialog.resx} | 0 .../Shared/Import/Importprofiles.razor | 4 ++-- AudioCuesheetEditor/Shared/TrackList/TrackList.razor | 4 ++-- 8 files changed, 14 insertions(+), 14 deletions(-) rename AudioCuesheetEditor/Shared/Dialogs/{ConfirmDialog.de.resx => ConfirmationDialog.de.resx} (100%) rename AudioCuesheetEditor/Shared/Dialogs/{ConfirmDialog.razor => ConfirmationDialog.razor} (95%) rename AudioCuesheetEditor/Shared/Dialogs/{ConfirmDialog.resx => ConfirmationDialog.resx} (100%) diff --git a/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs b/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs index 5f0fe857..a1481c15 100644 --- a/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs +++ b/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs @@ -199,7 +199,7 @@ public async Task AnalyseImportfile_ExistingCuesheet_OverwritesCuesheet() Assert.AreEqual(importCuesheet.Audiofile, sessionStateContainerCuesheet.Audiofile.Name); Assert.IsNotNull(sessionStateContainerCuesheet.CDTextfile); Assert.AreEqual(importCuesheet.CDTextfile, sessionStateContainerCuesheet.CDTextfile.Name); - Assert.AreEqual(importCuesheet.Tracks.Count, sessionStateContainerCuesheet.Tracks.Count); + Assert.HasCount(importCuesheet.Tracks.Count, sessionStateContainerCuesheet.Tracks); Assert.AreEqual(importCuesheet.Tracks.First().Artist, sessionStateContainerCuesheet.Tracks.First().Artist); Assert.AreEqual(importCuesheet.Tracks.First().Begin, sessionStateContainerCuesheet.Tracks.First().Begin); Assert.AreEqual(importCuesheet.Tracks.First().End, sessionStateContainerCuesheet.Tracks.First().End); diff --git a/AudioCuesheetEditor/Shared/AppBar.razor b/AudioCuesheetEditor/Shared/AppBar.razor index be44a017..9631153d 100644 --- a/AudioCuesheetEditor/Shared/AppBar.razor +++ b/AudioCuesheetEditor/Shared/AppBar.razor @@ -156,12 +156,12 @@ along with Foobar. If not, see async Task ResetCuesheetClicked() { - var parameters = new DialogParameters + var parameters = new DialogParameters { { x => x.ConfirmText, _localizer["Are you sure you want reset the cuesheet? All information you have not saved are lost!"] }, }; var options = new DialogOptions() { BackdropClick = false }; - var dialog = await _dialogService.ShowAsync(_localizer["Confirm"], parameters, options); + var dialog = await _dialogService.ShowAsync(_localizer["Confirm"], parameters, options); var result = await dialog.Result; if (result?.Canceled == false) { @@ -172,12 +172,12 @@ along with Foobar. If not, see async Task ResetApplicationClicked() { - var parameters = new DialogParameters + var parameters = new DialogParameters { { x => x.ConfirmText, _localizer["Are you sure you want reset the application? All information you have not saved are lost!"] }, }; var options = new DialogOptions() { BackdropClick = false }; - var dialog = await _dialogService.ShowAsync(_localizer["Confirm"], parameters, options); + var dialog = await _dialogService.ShowAsync(_localizer["Confirm"], parameters, options); var result = await dialog.Result; if (result?.Canceled == false) { @@ -192,12 +192,12 @@ along with Foobar. If not, see //Check if there are already any changes if ((TraceChangeManager.CanUndo) || (TraceChangeManager.CanRedo)) { - var parameters = new DialogParameters + var parameters = new DialogParameters { { x => x.ConfirmText, _localizer["Are you sure you want reset the cuesheet? All information you have not saved are lost!"] }, }; var options = new DialogOptions() { BackdropClick = false }; - var dialog = await _dialogService.ShowAsync(_localizer["Confirm"], parameters, options); + var dialog = await _dialogService.ShowAsync(_localizer["Confirm"], parameters, options); var result = await dialog.Result; if (result?.Canceled == false) { diff --git a/AudioCuesheetEditor/Shared/Cuesheet/EditSections.razor b/AudioCuesheetEditor/Shared/Cuesheet/EditSections.razor index 9bf716f4..fa21e07c 100644 --- a/AudioCuesheetEditor/Shared/Cuesheet/EditSections.razor +++ b/AudioCuesheetEditor/Shared/Cuesheet/EditSections.razor @@ -127,11 +127,11 @@ along with Foobar. If not, see async Task DeleteAllSections() { - var parameters = new DialogParameters + var parameters = new DialogParameters { { x => x.ConfirmText, _localizer["Are you sure you want to remove all sections?"] }, }; - var dialog = await _dialogService.ShowAsync(_localizer["Confirm"], parameters); + var dialog = await _dialogService.ShowAsync(_localizer["Confirm"], parameters); var result = await dialog.Result; if (result?.Canceled == false) { diff --git a/AudioCuesheetEditor/Shared/Dialogs/ConfirmDialog.de.resx b/AudioCuesheetEditor/Shared/Dialogs/ConfirmationDialog.de.resx similarity index 100% rename from AudioCuesheetEditor/Shared/Dialogs/ConfirmDialog.de.resx rename to AudioCuesheetEditor/Shared/Dialogs/ConfirmationDialog.de.resx diff --git a/AudioCuesheetEditor/Shared/Dialogs/ConfirmDialog.razor b/AudioCuesheetEditor/Shared/Dialogs/ConfirmationDialog.razor similarity index 95% rename from AudioCuesheetEditor/Shared/Dialogs/ConfirmDialog.razor rename to AudioCuesheetEditor/Shared/Dialogs/ConfirmationDialog.razor index ac4a9859..4aafb337 100644 --- a/AudioCuesheetEditor/Shared/Dialogs/ConfirmDialog.razor +++ b/AudioCuesheetEditor/Shared/Dialogs/ConfirmationDialog.razor @@ -17,7 +17,7 @@ along with Foobar. If not, see --> @inherits BaseLocalizedComponent -@inject IStringLocalizer _localizer +@inject IStringLocalizer _localizer diff --git a/AudioCuesheetEditor/Shared/Dialogs/ConfirmDialog.resx b/AudioCuesheetEditor/Shared/Dialogs/ConfirmationDialog.resx similarity index 100% rename from AudioCuesheetEditor/Shared/Dialogs/ConfirmDialog.resx rename to AudioCuesheetEditor/Shared/Dialogs/ConfirmationDialog.resx diff --git a/AudioCuesheetEditor/Shared/Import/Importprofiles.razor b/AudioCuesheetEditor/Shared/Import/Importprofiles.razor index 5933864f..ca9c9da3 100644 --- a/AudioCuesheetEditor/Shared/Import/Importprofiles.razor +++ b/AudioCuesheetEditor/Shared/Import/Importprofiles.razor @@ -165,11 +165,11 @@ along with Foobar. If not, see async Task ResetProfiles() { - var parameters = new DialogParameters + var parameters = new DialogParameters { { x => x.ConfirmText, _localizer["Are you sure you want to reset the import profiles to factory default?"] }, }; - var dialog = await _dialogService.ShowAsync(_localizer["Confirm"], parameters); + var dialog = await _dialogService.ShowAsync(_localizer["Confirm"], parameters); var result = await dialog.Result; if (result?.Canceled == false) { diff --git a/AudioCuesheetEditor/Shared/TrackList/TrackList.razor b/AudioCuesheetEditor/Shared/TrackList/TrackList.razor index fe1c94a8..6475cdfa 100644 --- a/AudioCuesheetEditor/Shared/TrackList/TrackList.razor +++ b/AudioCuesheetEditor/Shared/TrackList/TrackList.razor @@ -198,11 +198,11 @@ along with Foobar. If not, see async Task OnDeleteAllTracksClicked() { - var parameters = new DialogParameters + var parameters = new DialogParameters { { x => x.ConfirmText, _localizer["Are you sure you want to remove all tracks?"] }, }; - var dialog = await _dialogService.ShowAsync(_localizer["Confirm"], parameters); + var dialog = await _dialogService.ShowAsync(_localizer["Confirm"], parameters); var result = await dialog.Result; if (result?.Canceled == false) { From 4a9e7463a597a817edda69020273942f8483f9b7 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Thu, 19 Feb 2026 10:03:10 +0100 Subject: [PATCH 09/48] update .NET --- .github/workflows/build_pipeline.yml | 14 +++++------ .github/workflows/run_tests.yml | 16 ++++++------ .../AudioCuesheetEditor.End2EndTests.csproj | 2 +- .../AudioCuesheetEditor.Tests.csproj | 4 +-- .../AudioCuesheetEditor.csproj | 10 ++++---- AudioCuesheetEditor/Program.cs | 25 +------------------ 6 files changed, 24 insertions(+), 47 deletions(-) diff --git a/.github/workflows/build_pipeline.yml b/.github/workflows/build_pipeline.yml index f6948c68..9cc55ee8 100644 --- a/.github/workflows/build_pipeline.yml +++ b/.github/workflows/build_pipeline.yml @@ -15,22 +15,22 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - # version can be found here https://dotnet.microsoft.com/en-us/download/dotnet/9.0 + # version can be found here https://dotnet.microsoft.com/en-us/download/dotnet/10.0 - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: - dotnet-version: 9.0.x + dotnet-version: 10.0.x - name: Install wasm-tools - run: dotnet workload install wasm-tools-net9 + run: dotnet workload install wasm-tools - name: Build release run: dotnet publish AudioCuesheetEditor --configuration Release --output release - name: Upload Build Artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: AudioCuesheetEditor-Release path: ./release @@ -45,7 +45,7 @@ jobs: }} steps: - name: Download Build Artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v7 with: name: AudioCuesheetEditor-Release - name: Deploy to Netlify diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 0bab000c..03c1fa8f 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -9,25 +9,25 @@ jobs: run-tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.2.2 + - uses: actions/checkout@v6 - name: Set timestamp variable id: timestamp run: echo "datetime=$(date +'%Y-%m-%d_%H-%M-%S')" >> $GITHUB_OUTPUT - # version can be found here https://dotnet.microsoft.com/en-us/download/dotnet/9.0 + # version can be found here https://dotnet.microsoft.com/en-us/download/dotnet/10.0 - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: - dotnet-version: 9.0.x + dotnet-version: 10.0.x - name: Install wasm-tools - run: dotnet workload install wasm-tools-net9 + run: dotnet workload install wasm-tools - name: Build & Install run: dotnet build - name: Ensure browsers are installed - run: pwsh AudioCuesheetEditor.End2EndTests/bin/Debug/net9.0/playwright.ps1 install --with-deps + run: pwsh AudioCuesheetEditor.End2EndTests/bin/Debug/net10.0/playwright.ps1 install --with-deps - name: Start App run: dotnet run --project AudioCuesheetEditor & @@ -59,8 +59,8 @@ jobs: - name: Upload traces if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: playwright-traces-${{ steps.timestamp.outputs.datetime }} - path: AudioCuesheetEditor.End2EndTests/bin/Debug/net9.0/playwright-traces/ + path: AudioCuesheetEditor.End2EndTests/bin/Debug/net10.0/playwright-traces/ if-no-files-found: ignore diff --git a/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj b/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj index 4c5c4ccd..85d73e01 100644 --- a/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj +++ b/AudioCuesheetEditor.End2EndTests/AudioCuesheetEditor.End2EndTests.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 latest enable enable diff --git a/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj b/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj index 0e10bc14..dc6ac4ec 100644 --- a/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj +++ b/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj @@ -1,9 +1,9 @@ - net9.0 + net10.0 latest - enable + enable false true Exe diff --git a/AudioCuesheetEditor/AudioCuesheetEditor.csproj b/AudioCuesheetEditor/AudioCuesheetEditor.csproj index ec6a9afa..efe02493 100644 --- a/AudioCuesheetEditor/AudioCuesheetEditor.csproj +++ b/AudioCuesheetEditor/AudioCuesheetEditor.csproj @@ -1,7 +1,7 @@ - net9.0 + net10.0 true enable enable @@ -22,13 +22,13 @@ - - + + - - + + diff --git a/AudioCuesheetEditor/Program.cs b/AudioCuesheetEditor/Program.cs index dc5ef590..ce09002c 100644 --- a/AudioCuesheetEditor/Program.cs +++ b/AudioCuesheetEditor/Program.cs @@ -27,8 +27,6 @@ using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using MudBlazor.Services; -using System.Globalization; -using System.Reflection; using Toolbelt.Blazor.Extensions.DependencyInjection; var builder = WebAssemblyHostBuilder.CreateDefault(args); @@ -73,29 +71,8 @@ builder.Services.AddHotKeys2(); -// TODO: Remove this when https://github.com/dotnet/aspnetcore/issues/56824 is fixed - -// Get current localization culture -var currentCulture = CultureInfo.DefaultThreadCurrentCulture; - -// Get WASM culture provider via reflection -var type = Assembly.GetAssembly(typeof(WebAssemblyHost))!.GetType("Microsoft.AspNetCore.Components.WebAssembly.Hosting.WebAssemblyCultureProvider"); -var instance = type - !.GetProperty("Instance", BindingFlags.Public | BindingFlags.Static) - ?.GetValue(null); - -// Swap out the "current culture" for the UI (localization) culture -CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("de-DE"); -// Load the satellite assemblies -await (ValueTask)instance! - .GetType() - .GetMethod("LoadCurrentCultureResourcesAsync", BindingFlags.Public | BindingFlags.Instance)! - .Invoke(instance, [])!; -// Swap the culture back -CultureInfo.DefaultThreadCurrentCulture = currentCulture; - var host = builder.Build(); await host.SetCultureFromConfigurationAsync(); -await host.RunAsync(); +await host.RunAsync(); \ No newline at end of file From 150db2eaff3b0fc8c2fa6c48da236040088f8023 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Thu, 19 Feb 2026 13:18:28 +0100 Subject: [PATCH 10/48] fix null reference --- .../Services/IO/FileInputManager.cs | 8 +++++++- .../Services/IO/ImportManager.cs | 20 +++++++++++++++---- .../Shared/TrackList/ArtistColumn.razor | 2 +- .../Shared/TrackList/TitleColumn.razor | 2 +- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/AudioCuesheetEditor/Services/IO/FileInputManager.cs b/AudioCuesheetEditor/Services/IO/FileInputManager.cs index 78154ac4..afcc6854 100644 --- a/AudioCuesheetEditor/Services/IO/FileInputManager.cs +++ b/AudioCuesheetEditor/Services/IO/FileInputManager.cs @@ -17,6 +17,7 @@ using AudioCuesheetEditor.Model.IO; using AudioCuesheetEditor.Model.IO.Audio; using Microsoft.AspNetCore.Components.Forms; +using Microsoft.AspNetCore.Components.WebAssembly.Http; using Microsoft.JSInterop; namespace AudioCuesheetEditor.Services.IO @@ -93,7 +94,12 @@ public Boolean CheckFileMimeType(IBrowserFile file, String mimeType, IEnumerable audiofile = new Audiofile(browserFile.Name, audioFileObjectURL, codec); if (String.IsNullOrEmpty(audioFileObjectURL) == false) { - var loadContentStreamTask = _httpClient.GetStreamAsync(audioFileObjectURL) + var request = new HttpRequestMessage(HttpMethod.Get, audioFileObjectURL); + //TODO: Enable when https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/issues/524 gets done + request.SetBrowserRequestStreamingEnabled(false); + + var response = await _httpClient.SendAsync(request); + var loadContentStreamTask = response.Content.ReadAsStreamAsync() .ContinueWith(x => audiofile.ContentStream = x.Result); if (afterContentStreamLoaded != null) { diff --git a/AudioCuesheetEditor/Services/IO/ImportManager.cs b/AudioCuesheetEditor/Services/IO/ImportManager.cs index 0d1a8745..15a32fb3 100644 --- a/AudioCuesheetEditor/Services/IO/ImportManager.cs +++ b/AudioCuesheetEditor/Services/IO/ImportManager.cs @@ -52,7 +52,10 @@ public void ImportData(String? data) FileType = ImportFileType.Textfile }; stopwatch.Stop(); - _logger.LogDebug("ImportData duration: {stopwatch.Elapsed}", stopwatch.Elapsed); + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("ImportData duration: {stopwatch.Elapsed}", stopwatch.Elapsed); + } } public async Task AnalyseImportfile() @@ -94,7 +97,10 @@ public async Task AnalyseImportfile() } } stopwatch.Stop(); - _logger.LogDebug("ImportTextAsync duration: {stopwatch.Elapsed}", stopwatch.Elapsed); + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("ImportTextAsync duration: {stopwatch.Elapsed}", stopwatch.Elapsed); + } } public void ImportCuesheet() @@ -109,7 +115,10 @@ public void ImportCuesheet() } _sessionStateContainer.ResetImport(); stopwatch.Stop(); - _logger.LogDebug("ImportCuesheet duration: {stopwatch.Elapsed}", stopwatch.Elapsed); + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("ImportCuesheet duration: {stopwatch.Elapsed}", stopwatch.Elapsed); + } } public async Task UploadFilesAsync(IEnumerable files, String? fileInputId = null) @@ -175,7 +184,10 @@ public async Task UploadFilesAsync(IEnumerable files, String? file } UploadFilesFinished?.Invoke(this, invalidFiles); stopwatch.Stop(); - _logger.LogDebug("UploadFilesAsync duration: {stopwatch.Elapsed}", stopwatch.Elapsed); + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("UploadFilesAsync duration: {stopwatch.Elapsed}", stopwatch.Elapsed); + } } private static async Task ReadFileContentAsync(IBrowserFile file) diff --git a/AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor b/AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor index b82322b0..d5e36555 100644 --- a/AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor +++ b/AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor @@ -20,7 +20,7 @@ along with Foobar. If not, see @inject AutocompleteManager _autocompleteManager @inject ValidationService _validationService - diff --git a/AudioCuesheetEditor/Shared/TrackList/TitleColumn.razor b/AudioCuesheetEditor/Shared/TrackList/TitleColumn.razor index ccb84295..d97e43ab 100644 --- a/AudioCuesheetEditor/Shared/TrackList/TitleColumn.razor +++ b/AudioCuesheetEditor/Shared/TrackList/TitleColumn.razor @@ -20,7 +20,7 @@ along with Foobar. If not, see @inject AutocompleteManager _autocompleteManager @inject ValidationService _validationService - From ef5be4857a691e7c1268927fd65064adefe90ed5 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Thu, 19 Feb 2026 16:57:44 +0100 Subject: [PATCH 11/48] Update EditTrackModal.razor --- AudioCuesheetEditor/Shared/Dialogs/EditTrackModal.razor | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AudioCuesheetEditor/Shared/Dialogs/EditTrackModal.razor b/AudioCuesheetEditor/Shared/Dialogs/EditTrackModal.razor index 14886c52..721e7fe9 100644 --- a/AudioCuesheetEditor/Shared/Dialogs/EditTrackModal.razor +++ b/AudioCuesheetEditor/Shared/Dialogs/EditTrackModal.razor @@ -38,7 +38,7 @@ along with Foobar. If not, see Name = EditedTrack.Artist }; } - @@ -57,7 +57,7 @@ along with Foobar. If not, see Title = EditedTrack.Title }; } - From ec92f9ad3b674f205865bc3441f6d788c32a94cc Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Thu, 19 Feb 2026 16:57:58 +0100 Subject: [PATCH 12/48] Update FileInputManager.cs --- AudioCuesheetEditor/Services/IO/FileInputManager.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/AudioCuesheetEditor/Services/IO/FileInputManager.cs b/AudioCuesheetEditor/Services/IO/FileInputManager.cs index afcc6854..a47eb4f5 100644 --- a/AudioCuesheetEditor/Services/IO/FileInputManager.cs +++ b/AudioCuesheetEditor/Services/IO/FileInputManager.cs @@ -55,7 +55,10 @@ public bool IsValidAudiofile(IBrowserFile browserFile) public Boolean CheckFileMimeType(IBrowserFile file, String mimeType, IEnumerable fileExtensions) { - _logger.LogDebug("CheckFileMimeType called with file: file.Name: '{FileName}', file.ContentType: '{ContentType}', mimeType: '{MimeType}', fileExtensions: '{fileExtensions}'", file.Name, file.ContentType, mimeType, fileExtensions); + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("CheckFileMimeType called with file: file.Name: '{FileName}', file.ContentType: '{ContentType}', mimeType: '{MimeType}', fileExtensions: '{fileExtensions}'", file.Name, file.ContentType, mimeType, fileExtensions); + } Boolean fileMimeTypeMatches = false; if ((file != null) && (String.IsNullOrEmpty(mimeType) == false)) { @@ -63,7 +66,7 @@ public Boolean CheckFileMimeType(IBrowserFile file, String mimeType, IEnumerable { if (mimeType.EndsWith("/*")) { - var mainType = mimeType.Substring(0, mimeType.Length - 1); + var mainType = mimeType[..^1]; fileMimeTypeMatches = file.ContentType.StartsWith(mainType, StringComparison.CurrentCultureIgnoreCase); } else From 469e460955fdca4ce9d6a27362e4881dbe1b3ba2 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Fri, 20 Feb 2026 10:03:15 +0100 Subject: [PATCH 13/48] Update AddTrack.razor --- AudioCuesheetEditor/Shared/Record/AddTrack.razor | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AudioCuesheetEditor/Shared/Record/AddTrack.razor b/AudioCuesheetEditor/Shared/Record/AddTrack.razor index ba00b751..102317a2 100644 --- a/AudioCuesheetEditor/Shared/Record/AddTrack.razor +++ b/AudioCuesheetEditor/Shared/Record/AddTrack.razor @@ -31,7 +31,7 @@ along with Foobar. If not, see Name = currentRecordingTrack.Artist }; } - @@ -53,7 +53,7 @@ along with Foobar. If not, see Title = currentRecordingTrack.Title }; } - From a10a4d2bfb96cdfbc5f9c52beae1bfa101df5097 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Fri, 20 Feb 2026 10:08:34 +0100 Subject: [PATCH 14/48] Update .editorconfig --- .editorconfig | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.editorconfig b/.editorconfig index 9d73dbfd..d735b55b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,7 +1 @@ -[*.cs] - -# IDE0016: throw-Ausdruck verwenden -csharp_style_throw_expression = false:suggestion - -# IDE0057: Bereichsoperator verwenden -csharp_style_prefer_range_operator = false:suggestion +[*.cs] \ No newline at end of file From b28d73de218bf356c460a573438a76e333aa7a8a Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Fri, 20 Feb 2026 10:11:04 +0100 Subject: [PATCH 15/48] Update index.html --- AudioCuesheetEditor/wwwroot/index.html | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/AudioCuesheetEditor/wwwroot/index.html b/AudioCuesheetEditor/wwwroot/index.html index 0f4b486f..9edfd235 100644 --- a/AudioCuesheetEditor/wwwroot/index.html +++ b/AudioCuesheetEditor/wwwroot/index.html @@ -1,9 +1,9 @@ - + - + AudioCuesheetEditor @@ -32,14 +32,13 @@ position: relative; } -
- +

Loading application, please stand by ...

@@ -55,7 +54,7 @@ 🗙
- + From 749553b5c502877b14c43fe70a2c612ae18738ba Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Fri, 20 Feb 2026 15:18:36 +0100 Subject: [PATCH 16/48] use culture for switching culture --- .../Services/UI/LocalizationService.cs | 16 ++++++++-------- AudioCuesheetEditor/Shared/AppBar.razor | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/AudioCuesheetEditor/Services/UI/LocalizationService.cs b/AudioCuesheetEditor/Services/UI/LocalizationService.cs index c48c8588..96934225 100644 --- a/AudioCuesheetEditor/Services/UI/LocalizationService.cs +++ b/AudioCuesheetEditor/Services/UI/LocalizationService.cs @@ -46,27 +46,27 @@ public async Task SetCultureFromConfigurationAsync() { var options = await _localStorageOptionsProvider.GetOptionsAsync(); - ChangeLanguage(options.Culture.Name); + ChangeLanguage(options.Culture); } - public async Task ChangeLanguageAsync(string name) + public async Task ChangeLanguageAsync(CultureInfo culture) { - if (ChangeLanguage(name)) + if (ChangeLanguage(culture)) { - await _localStorageOptionsProvider.SaveOptionsValueAsync(x => x.CultureName!, name); + await _localStorageOptionsProvider.SaveOptionsValueAsync(x => x.CultureName!, culture.Name); LocalizationChanged?.Invoke(this, new EventArgs()); } } - private static Boolean ChangeLanguage(string name) + private static Boolean ChangeLanguage(CultureInfo newCulture) { - var newCulture = AvailableCultures.SingleOrDefault(c => c.Name == name); - if (newCulture != null) + var contains = AvailableCultures.Contains(newCulture); + if (contains) { CultureInfo.DefaultThreadCurrentUICulture = newCulture; CultureInfo.CurrentUICulture = newCulture; } - return newCulture != null; + return contains; } } } diff --git a/AudioCuesheetEditor/Shared/AppBar.razor b/AudioCuesheetEditor/Shared/AppBar.razor index 9631153d..4fbda6c8 100644 --- a/AudioCuesheetEditor/Shared/AppBar.razor +++ b/AudioCuesheetEditor/Shared/AppBar.razor @@ -59,7 +59,7 @@ along with Foobar. If not, see @foreach (var culture in LocalizationService.AvailableCultures) { - @IsoCountryCodeToFlagEmoji(culture) @culture.DisplayName + @IsoCountryCodeToFlagEmoji(culture) @culture.DisplayName } @@ -115,9 +115,9 @@ along with Foobar. If not, see [Parameter] public Boolean DisplayReset { get; set; } - async Task SelectedCultureChanged(string name) + async Task SelectedCultureChanged(CultureInfo culture) { - await base.LocalizationService.ChangeLanguageAsync(name); + await base.LocalizationService.ChangeLanguageAsync(culture); } String GetStyle(CultureInfo cultureInfo) From a45051ba1fde408a7468fd4096eebd01c7849956 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:00:21 +0100 Subject: [PATCH 17/48] reload after switching language --- .../Services/UI/LocalizationService.cs | 13 ++-- AudioCuesheetEditor/Shared/AppBar.razor | 10 +++- .../Shared/BaseLocalizedComponent.cs | 7 --- .../BaseLocalizedLayoutComponentBase.cs | 59 ------------------- .../Shared/Layouts/MainLayout.razor | 5 +- .../Layouts/MainLayoutWithoutMenu.razor | 3 +- 6 files changed, 19 insertions(+), 78 deletions(-) delete mode 100644 AudioCuesheetEditor/Shared/Layouts/BaseLocalizedLayoutComponentBase.cs diff --git a/AudioCuesheetEditor/Services/UI/LocalizationService.cs b/AudioCuesheetEditor/Services/UI/LocalizationService.cs index 96934225..f158f979 100644 --- a/AudioCuesheetEditor/Services/UI/LocalizationService.cs +++ b/AudioCuesheetEditor/Services/UI/LocalizationService.cs @@ -15,13 +15,17 @@ //. using AudioCuesheetEditor.Data.Options; using AudioCuesheetEditor.Model.Options; +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; using System.Globalization; namespace AudioCuesheetEditor.Services.UI { - public class LocalizationService(ILocalStorageOptionsProvider localStorageOptionsProvider) + public class LocalizationService(ILocalStorageOptionsProvider localStorageOptionsProvider, NavigationManager navigationManager, IJSRuntime jsRuntime) { private readonly ILocalStorageOptionsProvider _localStorageOptionsProvider = localStorageOptionsProvider; + private readonly NavigationManager _navigationManager = navigationManager; + private readonly IJSRuntime _jsRuntime = jsRuntime; public const String DefaultCulture = "en-US"; @@ -38,8 +42,6 @@ public static IReadOnlyCollection AvailableCultures } } - public event EventHandler? LocalizationChanged; - public static CultureInfo SelectedCulture => CultureInfo.DefaultThreadCurrentUICulture ?? CultureInfo.CurrentUICulture; public async Task SetCultureFromConfigurationAsync() @@ -54,7 +56,8 @@ public async Task ChangeLanguageAsync(CultureInfo culture) if (ChangeLanguage(culture)) { await _localStorageOptionsProvider.SaveOptionsValueAsync(x => x.CultureName!, culture.Name); - LocalizationChanged?.Invoke(this, new EventArgs()); + await _jsRuntime.InvokeVoidAsync("removeBeforeunload"); + _navigationManager.NavigateTo(_navigationManager.Uri, true); } } @@ -64,7 +67,7 @@ private static Boolean ChangeLanguage(CultureInfo newCulture) if (contains) { CultureInfo.DefaultThreadCurrentUICulture = newCulture; - CultureInfo.CurrentUICulture = newCulture; + CultureInfo.DefaultThreadCurrentCulture = newCulture; } return contains; } diff --git a/AudioCuesheetEditor/Shared/AppBar.razor b/AudioCuesheetEditor/Shared/AppBar.razor index 4fbda6c8..771d40f1 100644 --- a/AudioCuesheetEditor/Shared/AppBar.razor +++ b/AudioCuesheetEditor/Shared/AppBar.razor @@ -69,7 +69,10 @@ along with Foobar. If not, see } @_localizer["Help"] @_localizer["About"] - @_localizer["Hotkeys"] + @if (DisplayHotkeys) + { + @_localizer["Hotkeys"] + } @_localizer["Preview environment"] @if (DisplayReset) { @@ -115,6 +118,9 @@ along with Foobar. If not, see [Parameter] public Boolean DisplayReset { get; set; } + [Parameter] + public Boolean DisplayHotkeys { get; set; } + async Task SelectedCultureChanged(CultureInfo culture) { await base.LocalizationService.ChangeLanguageAsync(culture); @@ -223,7 +229,7 @@ along with Foobar. If not, see await _importManager.UploadFilesAsync([file]); } - async Task DisplayHotkeys() + async Task ShowHotkeysDialog() { var options = new DialogOptions() { BackdropClick = false, CloseOnEscapeKey = true, CloseButton = true, FullWidth = true, MaxWidth = MaxWidth.Large }; await _dialogService.ShowAsync(_localizer["Hotkeys"], options); diff --git a/AudioCuesheetEditor/Shared/BaseLocalizedComponent.cs b/AudioCuesheetEditor/Shared/BaseLocalizedComponent.cs index 5a31acad..ddb56da7 100644 --- a/AudioCuesheetEditor/Shared/BaseLocalizedComponent.cs +++ b/AudioCuesheetEditor/Shared/BaseLocalizedComponent.cs @@ -33,7 +33,6 @@ public abstract class BaseLocalizedComponent : ComponentBase, IDisposable protected override void OnInitialized() { base.OnInitialized(); - LocalizationService.LocalizationChanged += LocalizationService_LocalizationChanged; TraceChangeManager.TracedObjectHistoryChanged += TraceChangeManager_TracedObjectHistoryChanged; TraceChangeManager.UndoDone += TraceChangeManager_UndoDone; TraceChangeManager.RedoDone += TraceChangeManager_RedoDone; @@ -54,18 +53,12 @@ void TraceChangeManager_TracedObjectHistoryChanged(object? sender, EventArgs e) StateHasChanged(); } - void LocalizationService_LocalizationChanged(object? sender, EventArgs args) - { - StateHasChanged(); - } - protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { - LocalizationService.LocalizationChanged -= LocalizationService_LocalizationChanged; TraceChangeManager.TracedObjectHistoryChanged -= TraceChangeManager_TracedObjectHistoryChanged; TraceChangeManager.UndoDone -= TraceChangeManager_UndoDone; TraceChangeManager.RedoDone -= TraceChangeManager_RedoDone; diff --git a/AudioCuesheetEditor/Shared/Layouts/BaseLocalizedLayoutComponentBase.cs b/AudioCuesheetEditor/Shared/Layouts/BaseLocalizedLayoutComponentBase.cs deleted file mode 100644 index 63470c21..00000000 --- a/AudioCuesheetEditor/Shared/Layouts/BaseLocalizedLayoutComponentBase.cs +++ /dev/null @@ -1,59 +0,0 @@ -//This file is part of AudioCuesheetEditor. - -//AudioCuesheetEditor is free software: you can redistribute it and/or modify -//it under the terms of the GNU General Public License as published by -//the Free Software Foundation, either version 3 of the License, or -//(at your option) any later version. - -//AudioCuesheetEditor is distributed in the hope that it will be useful, -//but WITHOUT ANY WARRANTY; without even the implied warranty of -//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -//GNU General Public License for more details. - -//You should have received a copy of the GNU General Public License -//along with Foobar. If not, see -//. -using AudioCuesheetEditor.Services.UI; -using Microsoft.AspNetCore.Components; - -namespace AudioCuesheetEditor.Shared.Layouts -{ - public class BaseLocalizedLayoutComponentBase: LayoutComponentBase, IDisposable - { - private bool disposedValue; - - [Inject] - protected LocalizationService LocalizationService { get; set; } = default!; - - protected override void OnInitialized() - { - base.OnInitialized(); - LocalizationService.LocalizationChanged += LocalizationService_LocalizationChanged; - } - - void LocalizationService_LocalizationChanged(object? sender, EventArgs args) - { - StateHasChanged(); - } - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - LocalizationService.LocalizationChanged -= LocalizationService_LocalizationChanged; - } - - disposedValue = true; - } - } - - public void Dispose() - { - // Ändern Sie diesen Code nicht. Fügen Sie Bereinigungscode in der Methode "Dispose(bool disposing)" ein. - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - } -} diff --git a/AudioCuesheetEditor/Shared/Layouts/MainLayout.razor b/AudioCuesheetEditor/Shared/Layouts/MainLayout.razor index ba89985e..2a9a319d 100644 --- a/AudioCuesheetEditor/Shared/Layouts/MainLayout.razor +++ b/AudioCuesheetEditor/Shared/Layouts/MainLayout.razor @@ -15,8 +15,7 @@ You should have received a copy of the GNU General Public License along with Foobar. If not, see . --> - -@inherits BaseLocalizedLayoutComponentBase +@inherits LayoutComponentBase @inject NavigationManager _navigationManager @inject IStringLocalizer _localizer @@ -26,7 +25,7 @@ along with Foobar. If not, see - + @Body diff --git a/AudioCuesheetEditor/Shared/Layouts/MainLayoutWithoutMenu.razor b/AudioCuesheetEditor/Shared/Layouts/MainLayoutWithoutMenu.razor index 8791ef1c..1fa5d25d 100644 --- a/AudioCuesheetEditor/Shared/Layouts/MainLayoutWithoutMenu.razor +++ b/AudioCuesheetEditor/Shared/Layouts/MainLayoutWithoutMenu.razor @@ -15,8 +15,7 @@ You should have received a copy of the GNU General Public License along with Foobar. If not, see . --> - -@inherits BaseLocalizedLayoutComponentBase +@inherits LayoutComponentBase @inject NavigationManager _navigationManager @inject IStringLocalizer _localizer From 7d5a6be892c36c38f2d9fb50b0da26407fdabd1c Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:03:20 +0100 Subject: [PATCH 18/48] Update AppBar.cs --- AudioCuesheetEditor.End2EndTests/Models/AppBar.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AudioCuesheetEditor.End2EndTests/Models/AppBar.cs b/AudioCuesheetEditor.End2EndTests/Models/AppBar.cs index 6129ffd9..69bd206c 100644 --- a/AudioCuesheetEditor.End2EndTests/Models/AppBar.cs +++ b/AudioCuesheetEditor.End2EndTests/Models/AppBar.cs @@ -48,6 +48,8 @@ internal async Task ChangeLanguageAsync(string language) { await _page.GetByRole(AriaRole.Button, new() { Name = "Change language" }).ClickAsync(); await _page.GetByText(language).ClickAsync(); + await _page.WaitForLoadStateAsync(LoadState.NetworkIdle); + await _page.WaitForFunctionAsync(@"() => window.Blazor !== undefined"); } internal async Task UndoAsync() From ab4f9b5624b5d87bc843894ea00281594915a5f1 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:45:49 +0100 Subject: [PATCH 19/48] fix tracks table --- AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor | 3 ++- AudioCuesheetEditor/Shared/TrackList/TitleColumn.razor | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor b/AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor index d5e36555..3ad6d073 100644 --- a/AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor +++ b/AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor @@ -20,7 +20,7 @@ along with Foobar. If not, see @inject AutocompleteManager _autocompleteManager @inject ValidationService _validationService - @@ -34,6 +34,7 @@ along with Foobar. If not, see @code { + //TODO: Check if ToStringFunc really gets a nullable or not [Parameter] [EditorRequired] public Track Track { get; set; } = default!; diff --git a/AudioCuesheetEditor/Shared/TrackList/TitleColumn.razor b/AudioCuesheetEditor/Shared/TrackList/TitleColumn.razor index d97e43ab..0e8d5261 100644 --- a/AudioCuesheetEditor/Shared/TrackList/TitleColumn.razor +++ b/AudioCuesheetEditor/Shared/TrackList/TitleColumn.razor @@ -20,7 +20,7 @@ along with Foobar. If not, see @inject AutocompleteManager _autocompleteManager @inject ValidationService _validationService - @@ -34,6 +34,7 @@ along with Foobar. If not, see @code { + //TODO: Check if ToStringFunc really gets a nullable or not [Parameter] [EditorRequired] public Track Track { get; set; } = default!; From a6143a6231ee1afb8a121930f7f14cfad0ca3412 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Fri, 27 Feb 2026 13:04:06 +0100 Subject: [PATCH 20/48] Update Track.cs --- AudioCuesheetEditor/Model/AudioCuesheet/Track.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AudioCuesheetEditor/Model/AudioCuesheet/Track.cs b/AudioCuesheetEditor/Model/AudioCuesheet/Track.cs index 60e42688..09b511b7 100644 --- a/AudioCuesheetEditor/Model/AudioCuesheet/Track.cs +++ b/AudioCuesheetEditor/Model/AudioCuesheet/Track.cs @@ -60,7 +60,7 @@ public Track() { } /// Create object with copied values from input /// /// Object to copy values from - /// /// Copy cuesheet reference from track also? + /// Copy cuesheet reference from track also? public Track(ITrack track, Boolean copyCuesheetReference = true) : this() { CopyValues(track, copyCuesheetReference); From 74326626a4b8a6de11c9b271477ba600bd41f9b9 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Fri, 27 Feb 2026 15:03:04 +0100 Subject: [PATCH 21/48] fix tests --- AudioCuesheetEditor.End2EndTests/MSTestSettings.cs | 2 +- AudioCuesheetEditor.Tests/MSTestSettings.cs | 2 +- AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor | 3 +-- AudioCuesheetEditor/Shared/TrackList/TitleColumn.razor | 3 +-- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/AudioCuesheetEditor.End2EndTests/MSTestSettings.cs b/AudioCuesheetEditor.End2EndTests/MSTestSettings.cs index aaf278c8..2492be6d 100644 --- a/AudioCuesheetEditor.End2EndTests/MSTestSettings.cs +++ b/AudioCuesheetEditor.End2EndTests/MSTestSettings.cs @@ -1 +1 @@ -[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] +[assembly: Parallelize()] diff --git a/AudioCuesheetEditor.Tests/MSTestSettings.cs b/AudioCuesheetEditor.Tests/MSTestSettings.cs index e466aa12..8a30f591 100644 --- a/AudioCuesheetEditor.Tests/MSTestSettings.cs +++ b/AudioCuesheetEditor.Tests/MSTestSettings.cs @@ -1,3 +1,3 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] +[assembly: Parallelize()] diff --git a/AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor b/AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor index 3ad6d073..154acdbd 100644 --- a/AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor +++ b/AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor @@ -20,7 +20,7 @@ along with Foobar. If not, see @inject AutocompleteManager _autocompleteManager @inject ValidationService _validationService - @@ -34,7 +34,6 @@ along with Foobar. If not, see @code { - //TODO: Check if ToStringFunc really gets a nullable or not [Parameter] [EditorRequired] public Track Track { get; set; } = default!; diff --git a/AudioCuesheetEditor/Shared/TrackList/TitleColumn.razor b/AudioCuesheetEditor/Shared/TrackList/TitleColumn.razor index 0e8d5261..4d787e9c 100644 --- a/AudioCuesheetEditor/Shared/TrackList/TitleColumn.razor +++ b/AudioCuesheetEditor/Shared/TrackList/TitleColumn.razor @@ -20,7 +20,7 @@ along with Foobar. If not, see @inject AutocompleteManager _autocompleteManager @inject ValidationService _validationService - @@ -34,7 +34,6 @@ along with Foobar. If not, see @code { - //TODO: Check if ToStringFunc really gets a nullable or not [Parameter] [EditorRequired] public Track Track { get; set; } = default!; From f5f4ad67354d00633052bf33a68283082dc4b513 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Fri, 27 Feb 2026 15:11:19 +0100 Subject: [PATCH 22/48] reset to method level parlellization --- AudioCuesheetEditor.End2EndTests/MSTestSettings.cs | 2 +- AudioCuesheetEditor.Tests/MSTestSettings.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AudioCuesheetEditor.End2EndTests/MSTestSettings.cs b/AudioCuesheetEditor.End2EndTests/MSTestSettings.cs index 2492be6d..aaf278c8 100644 --- a/AudioCuesheetEditor.End2EndTests/MSTestSettings.cs +++ b/AudioCuesheetEditor.End2EndTests/MSTestSettings.cs @@ -1 +1 @@ -[assembly: Parallelize()] +[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] diff --git a/AudioCuesheetEditor.Tests/MSTestSettings.cs b/AudioCuesheetEditor.Tests/MSTestSettings.cs index 8a30f591..e466aa12 100644 --- a/AudioCuesheetEditor.Tests/MSTestSettings.cs +++ b/AudioCuesheetEditor.Tests/MSTestSettings.cs @@ -1,3 +1,3 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -[assembly: Parallelize()] +[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] From fba8d10267a9a27d9d0ae84f0365f243ec968cf8 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Tue, 3 Mar 2026 10:19:21 +0100 Subject: [PATCH 23/48] Update MSTestSettings.cs --- AudioCuesheetEditor.End2EndTests/MSTestSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AudioCuesheetEditor.End2EndTests/MSTestSettings.cs b/AudioCuesheetEditor.End2EndTests/MSTestSettings.cs index aaf278c8..2492be6d 100644 --- a/AudioCuesheetEditor.End2EndTests/MSTestSettings.cs +++ b/AudioCuesheetEditor.End2EndTests/MSTestSettings.cs @@ -1 +1 @@ -[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] +[assembly: Parallelize()] From f05f0ee1688243f949508c5a372dff2484de2180 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Tue, 3 Mar 2026 14:41:28 +0100 Subject: [PATCH 24/48] Update ArtistColumn.razor --- AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor b/AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor index 154acdbd..b82322b0 100644 --- a/AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor +++ b/AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor @@ -20,7 +20,7 @@ along with Foobar. If not, see @inject AutocompleteManager _autocompleteManager @inject ValidationService _validationService - From ae3f9fdc8182e8fd487ea38ec73ced31d6ba4135 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:07:43 +0100 Subject: [PATCH 25/48] Update ArtistColumn.razor --- AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor b/AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor index b82322b0..154acdbd 100644 --- a/AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor +++ b/AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor @@ -20,7 +20,7 @@ along with Foobar. If not, see @inject AutocompleteManager _autocompleteManager @inject ValidationService _validationService - From 8130f2a59e051e40fbf30c6c85f0691e54bfd5c9 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Tue, 3 Mar 2026 15:31:47 +0100 Subject: [PATCH 26/48] fix autocomplete handling --- .../Shared/TrackList/ArtistColumn.razor | 48 +++++++----- .../Shared/TrackList/TitleColumn.razor | 74 +++++++++++-------- 2 files changed, 71 insertions(+), 51 deletions(-) diff --git a/AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor b/AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor index 154acdbd..6e949688 100644 --- a/AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor +++ b/AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor @@ -20,9 +20,9 @@ along with Foobar. If not, see @inject AutocompleteManager _autocompleteManager @inject ValidationService _validationService - + @context.Name @if (context.Disambiguation != null) @@ -38,39 +38,49 @@ along with Foobar. If not, see [EditorRequired] public Track Track { get; set; } = default!; - MusicBrainzArtist? autocompleteArtist; - string? artist; - Timer? debounceTimer; + MusicBrainzArtist? _autocompleteArtist; + string? _artist; + CancellationTokenSource? _cancellationTokenSource; protected override void OnParametersSet() { base.OnParametersSet(); - autocompleteArtist = new() { Name = Track.Artist }; - artist = autocompleteArtist.Name; + SetComponentValues(new() { Name = Track.Artist }); } - void ValueChanged(MusicBrainzArtist? newValue) + void SetComponentValues(MusicBrainzArtist? newValue) { - autocompleteArtist = newValue; - artist = autocompleteArtist?.Name; - StartChangeTimer(); + if (newValue?.Name != _autocompleteArtist?.Name) + { + _autocompleteArtist = newValue; + _artist = _autocompleteArtist?.Name; + } } - void OnBlur(FocusEventArgs args) + void ValueChanged(MusicBrainzArtist? newValue) { + SetComponentValues(newValue); StartChangeTimer(); } void StartChangeTimer() { - debounceTimer = new Timer(100); - debounceTimer.Elapsed += ChangeArtist; - debounceTimer.AutoReset = false; - debounceTimer.Start(); + _cancellationTokenSource?.Cancel(); + _cancellationTokenSource = new CancellationTokenSource(); + + _ = DebounceAsync(_cancellationTokenSource.Token); } - void ChangeArtist(object? sender, System.Timers.ElapsedEventArgs e) + async Task DebounceAsync(CancellationToken token) { - Track.Artist = artist; + try + { + await Task.Delay(100, token); + Track.Artist = _artist; + } + catch (TaskCanceledException) + { + //Nothing to do here + } } } diff --git a/AudioCuesheetEditor/Shared/TrackList/TitleColumn.razor b/AudioCuesheetEditor/Shared/TrackList/TitleColumn.razor index 4d787e9c..68b190ff 100644 --- a/AudioCuesheetEditor/Shared/TrackList/TitleColumn.razor +++ b/AudioCuesheetEditor/Shared/TrackList/TitleColumn.razor @@ -21,8 +21,8 @@ along with Foobar. If not, see @inject ValidationService _validationService + @bind-Text="_title" Value="_autocompleteTrack" ValueChanged="TitleSelected" ResetValueOnEmptyText Clearable ShowProgressIndicator + Validation="(string? newTitle) => _validationService.Validate(Track, nameof(Track.Title))" MaxItems="null" CoerceText="false" OnBlur="() => StartChangeTimer()"> @autocompleteContext.Title @if (autocompleteContext.Disambiguation != null) @@ -42,55 +42,65 @@ along with Foobar. If not, see [EditorRequired] public ViewMode CurrentViewMode { get; set; } - MusicBrainzTrack? autocompleteTrack; - string? title; - Timer? debounceTimer; + MusicBrainzTrack? _autocompleteTrack; + string? _title; + CancellationTokenSource? _cancellationTokenSource; protected override void OnParametersSet() { base.OnParametersSet(); - autocompleteTrack = new() { Artist = Track.Artist, Title = Track.Title }; - title = autocompleteTrack?.Title; + SetComponentValues(new() { Artist = Track.Artist, Title = Track.Title }); } - void TitleSelected(MusicBrainzTrack? newValue) + void SetComponentValues(MusicBrainzTrack? newValue) { - autocompleteTrack = newValue; - title = autocompleteTrack?.Title; - StartChangeTimer(); + if ((newValue?.Artist != _autocompleteTrack?.Artist) || (newValue?.Title != _autocompleteTrack?.Title)) + { + _autocompleteTrack = newValue; + _title = _autocompleteTrack?.Title; + } } - void OnBlur(FocusEventArgs args) + void TitleSelected(MusicBrainzTrack? newValue) { + SetComponentValues(newValue); StartChangeTimer(); } void StartChangeTimer() { - debounceTimer = new Timer(100); - debounceTimer.Elapsed += ChangeTitle; - debounceTimer.AutoReset = false; - debounceTimer.Start(); + _cancellationTokenSource?.Cancel(); + _cancellationTokenSource = new CancellationTokenSource(); + + _ = DebounceAsync(_cancellationTokenSource.Token); } - void ChangeTitle(object? sender, System.Timers.ElapsedEventArgs e) + async Task DebounceAsync(CancellationToken token) { - base.TraceChangeManager.BulkEdit = true; - Track.Title = title; - switch (CurrentViewMode) + try + { + await Task.Delay(100, token); + base.TraceChangeManager.BulkEdit = true; + Track.Title = _title; + switch (CurrentViewMode) + { + case ViewMode.DetailView: + case ViewMode.ImportView: + if ((String.IsNullOrEmpty(Track.Artist)) && (String.IsNullOrEmpty(_autocompleteTrack?.Artist) == false)) + { + Track.Artist = _autocompleteTrack.Artist; + } + if ((Track.Length.HasValue == false) && (_autocompleteTrack?.Length.HasValue == true)) + { + Track.Length = _autocompleteTrack?.Length; + } + break; + } + base.TraceChangeManager.BulkEdit = false; + } + catch (TaskCanceledException) { - case ViewMode.DetailView: - case ViewMode.ImportView: - if ((String.IsNullOrEmpty(Track.Artist)) && (String.IsNullOrEmpty(autocompleteTrack?.Artist) == false)) - { - Track.Artist = autocompleteTrack.Artist; - } - if ((Track.Length.HasValue == false) && (autocompleteTrack?.Length.HasValue == true)) - { - Track.Length = autocompleteTrack?.Length; - } - break; + //Nothing to do here } - base.TraceChangeManager.BulkEdit = false; } } From 40f1b6744342fe21f9670733748fc12322b31aa0 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Wed, 4 Mar 2026 09:12:30 +0100 Subject: [PATCH 27/48] fix browser issues --- .../Models/AppBar.cs | 16 +++++----------- .../Tests/Desktop/ExportTest.cs | 2 +- .../Tests/Smartphone/ExportTestSmartphone.cs | 2 +- AudioCuesheetEditor/Shared/AppBar.razor | 2 +- .../Shared/Audio/AudioPlayer.razor | 10 +++++----- .../Shared/Cuesheet/EditSections.razor | 2 +- .../Shared/Dialogs/ConfirmationDialog.razor | 4 ++-- .../Shared/Inputs/FileInput.razor | 10 +++++----- 8 files changed, 21 insertions(+), 27 deletions(-) diff --git a/AudioCuesheetEditor.End2EndTests/Models/AppBar.cs b/AudioCuesheetEditor.End2EndTests/Models/AppBar.cs index 69bd206c..ee614bf3 100644 --- a/AudioCuesheetEditor.End2EndTests/Models/AppBar.cs +++ b/AudioCuesheetEditor.End2EndTests/Models/AppBar.cs @@ -18,13 +18,13 @@ namespace AudioCuesheetEditor.End2EndTests.Models { - partial class AppBar + partial class AppBar(IPage page) { [GeneratedRegex("^Open$")] private static partial Regex OpenRegex(); - private readonly IPage _page; - private readonly ILocator _menuButton; + private readonly IPage _page = page; + internal ILocator MenuButton => _page.GetByRole(AriaRole.Toolbar).GetByRole(AriaRole.Button, new() { Name = "More" }); internal ILocator UndoButton => _page.GetByRole(AriaRole.Button, new() { Name = "undo" }); @@ -32,15 +32,9 @@ partial class AppBar internal ILocator HomeButton => _page.Locator(".mud-button-root").First; - internal AppBar(IPage page) - { - _page = page; - _menuButton = _page.GetByRole(AriaRole.Button, new() { Name = "More" }); - } - internal async Task OpenSettingsAsync() { - await _menuButton.ClickAsync(); + await MenuButton.ClickAsync(); await _page.GetByText("Settings").ClickAsync(); } @@ -83,7 +77,7 @@ internal async Task OpenExportDialogAsync(string exportType, string fileMenuName internal async Task OpenDisplayHotkeysAsync() { - await _page.GetByRole(AriaRole.Button, new() { Name = "More" }).ClickAsync(); + await MenuButton.ClickAsync(); await _page.GetByText("Hotkeys").ClickAsync(); } } diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ExportTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ExportTest.cs index 85555e5b..58d53768 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ExportTest.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ExportTest.cs @@ -83,7 +83,7 @@ public async Task DownloadText_GeneratesTextFile_WhenCuesheetIsValidAsync() await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3"); await detailView.EditTrackAsync("Track Artist 1", "Track Title 1"); await bar.OpenExportDialogAsync("Textfile"); - await TestPage.GetByRole(AriaRole.Button, new() { Name = "Next" }).ClickAsync(); + await TestPage.GetByRole(AriaRole.Button, new() { Name = "Next", Exact = true }).ClickAsync(); var downloadTask = TestPage.WaitForDownloadAsync(); await TestPage.GetByRole(AriaRole.Button, new() { Name = "Download-YouTube.txt" }).ClickAsync(); var download = await downloadTask; diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ExportTestSmartphone.cs b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ExportTestSmartphone.cs index 385e3aa3..6bee002f 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ExportTestSmartphone.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ExportTestSmartphone.cs @@ -83,7 +83,7 @@ public async Task DownloadText_GeneratesTextFile_WhenCuesheetIsValidAsync() await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3"); await detailView.EditTrackAsync("Track Artist 1", "Track Title 1"); await bar.OpenExportDialogAsync("Textfile"); - await TestPage.GetByRole(AriaRole.Button, new() { Name = "Next" }).ClickAsync(); + await TestPage.GetByRole(AriaRole.Button, new() { Name = "Next", Exact = true }).ClickAsync(); var downloadTask = TestPage.WaitForDownloadAsync(); await TestPage.GetByRole(AriaRole.Button, new() { Name = "Download-YouTube.txt" }).ClickAsync(); var download = await downloadTask; diff --git a/AudioCuesheetEditor/Shared/AppBar.razor b/AudioCuesheetEditor/Shared/AppBar.razor index 771d40f1..746f5c60 100644 --- a/AudioCuesheetEditor/Shared/AppBar.razor +++ b/AudioCuesheetEditor/Shared/AppBar.razor @@ -27,7 +27,7 @@ along with Foobar. If not, see @inject ImportManager _importManager - + AudioCuesheetEditor diff --git a/AudioCuesheetEditor/Shared/Audio/AudioPlayer.razor b/AudioCuesheetEditor/Shared/Audio/AudioPlayer.razor index 11224223..5533938b 100644 --- a/AudioCuesheetEditor/Shared/Audio/AudioPlayer.razor +++ b/AudioCuesheetEditor/Shared/Audio/AudioPlayer.razor @@ -38,7 +38,7 @@ along with Foobar. If not, see { @String.Format("--{0}--{1}--", CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator, CultureInfo.CurrentCulture.DateTimeFormat.TimeSeparator) } - + @GetSliderTimeValue() @@ -54,17 +54,17 @@ along with Foobar. If not, see - + - + - + - + diff --git a/AudioCuesheetEditor/Shared/Cuesheet/EditSections.razor b/AudioCuesheetEditor/Shared/Cuesheet/EditSections.razor index fa21e07c..d1abd5cb 100644 --- a/AudioCuesheetEditor/Shared/Cuesheet/EditSections.razor +++ b/AudioCuesheetEditor/Shared/Cuesheet/EditSections.razor @@ -26,7 +26,7 @@ along with Foobar. If not, see - + diff --git a/AudioCuesheetEditor/Shared/Dialogs/ConfirmationDialog.razor b/AudioCuesheetEditor/Shared/Dialogs/ConfirmationDialog.razor index 4aafb337..98b7d01a 100644 --- a/AudioCuesheetEditor/Shared/Dialogs/ConfirmationDialog.razor +++ b/AudioCuesheetEditor/Shared/Dialogs/ConfirmationDialog.razor @@ -24,8 +24,8 @@ along with Foobar. If not, see @ConfirmText - @_localizer["Yes"] - @_localizer["No"] + @_localizer["Yes"] + @_localizer["No"] diff --git a/AudioCuesheetEditor/Shared/Inputs/FileInput.razor b/AudioCuesheetEditor/Shared/Inputs/FileInput.razor index 91ebeb91..65769f6c 100644 --- a/AudioCuesheetEditor/Shared/Inputs/FileInput.razor +++ b/AudioCuesheetEditor/Shared/Inputs/FileInput.razor @@ -21,19 +21,19 @@ along with Foobar. If not, see - @_localizer["Search"] + @_localizer["Search"] - + @if (DisplayMenu) { - - @_localizer["Rename file"] + + @_localizer["Rename file"] } From fbb3a92aebcbc83cd3d93d1758fcbb7b793e16fd Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Wed, 4 Mar 2026 13:29:41 +0100 Subject: [PATCH 28/48] Update Readme.md --- Readme.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/Readme.md b/Readme.md index 23511888..2a69f968 100644 --- a/Readme.md +++ b/Readme.md @@ -1,36 +1,40 @@ -## Introduction +# Introduction -### What is AudioCuesheetEditor? +## What is AudioCuesheetEditor? AudioCuesheetEditor is a Blazor based web port of AudioCuesheetEditor (https://sourceforge.net/projects/audiocuesheet/). Basically it is a program for handling audio cuesheet files and everything around. -### Description +## Description AudioCuesheetEditor is a Blazor based web application for writing audio cuesheets. There is much validation that helps the user to write a valid cuesheet. You can import external data (like text files, xml files, etc.) and analyse them directly in GUI. There are also much export variations like CSV, but you can customize export freely. -## Usage +# Usage Simply open the link https://audiocuesheeteditor.netlify.app/ on any browser -## Environments +# Environments -### Production +## Production [![Build & Deploy](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/actions/workflows/build_pipeline.yml/badge.svg?branch=master)](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/actions/workflows/build_pipeline.yml) The current stable version can be found here: https://audiocuesheeteditor.netlify.app/ -### Preview +## Preview [![Build & Deploy](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/actions/workflows/build_pipeline.yml/badge.svg?branch=development)](https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/actions/workflows/build_pipeline.yml) The next release candidate version can be found here: https://preview-audiocuesheeteditor.netlify.app/ -## Contributing +# Contributing You can contribute to the project by opening up issues or adding feature requests. To do so, you simply open https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/issues and add a new issue. If you want, you can also contribute by developing a feature or fixing a bug. More can be found here: https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/blob/master/CONTRIBUTING.md -## License +## Requirements + +This application is build using Blazor WASM Standalone and needs .NET 10 to run. Tests are running using MSTest and Playwright. + +# License GNU GENERAL PUBLIC LICENSE Version 3 From d535581cb21b08dde4a6321dc3ae7a558fdb6321 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Wed, 4 Mar 2026 13:33:43 +0100 Subject: [PATCH 29/48] Update AudioCuesheetEditor.csproj --- AudioCuesheetEditor/AudioCuesheetEditor.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AudioCuesheetEditor/AudioCuesheetEditor.csproj b/AudioCuesheetEditor/AudioCuesheetEditor.csproj index efe02493..d1d88f83 100644 --- a/AudioCuesheetEditor/AudioCuesheetEditor.csproj +++ b/AudioCuesheetEditor/AudioCuesheetEditor.csproj @@ -7,7 +7,7 @@ enable https://github.com/NeoCoderMatrix86/AudioCuesheetEditor 3.0 - 10.4.0 + 11.0.0 false true true From 5ca3079512645c8a277fb59457b3ade106db08ec Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Wed, 4 Mar 2026 13:40:00 +0100 Subject: [PATCH 30/48] Update AudioCuesheetEditor.csproj --- AudioCuesheetEditor/AudioCuesheetEditor.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AudioCuesheetEditor/AudioCuesheetEditor.csproj b/AudioCuesheetEditor/AudioCuesheetEditor.csproj index d1d88f83..d7bb3822 100644 --- a/AudioCuesheetEditor/AudioCuesheetEditor.csproj +++ b/AudioCuesheetEditor/AudioCuesheetEditor.csproj @@ -26,7 +26,7 @@ - + From 7436a633e67a761ed2aa2413deb9557dbfa8b3ab Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:07:01 +0100 Subject: [PATCH 31/48] update libraries --- .../AudioCuesheetEditor.csproj | 2 +- AudioCuesheetEditor/Shared/AppBar.razor | 5 +++- .../Dialogs/EditMultipleTracksModal.razor | 20 ++++++------- .../Shared/Dialogs/SettingsDialog.razor | 29 +++++++++---------- .../Shared/Import/Importprofiles.razor | 27 +++++++++++------ .../Shared/Inputs/FileDropOverlay.razor | 1 + .../Shared/Inputs/FileInput.razor | 9 +++--- .../Shared/Inputs/TextField.razor | 7 +++-- .../Shared/TrackList/TrackList.razor | 4 +-- AudioCuesheetEditor/wwwroot/css/app.css | 4 +++ 10 files changed, 63 insertions(+), 45 deletions(-) diff --git a/AudioCuesheetEditor/AudioCuesheetEditor.csproj b/AudioCuesheetEditor/AudioCuesheetEditor.csproj index d7bb3822..9dd43125 100644 --- a/AudioCuesheetEditor/AudioCuesheetEditor.csproj +++ b/AudioCuesheetEditor/AudioCuesheetEditor.csproj @@ -29,7 +29,7 @@ - + diff --git a/AudioCuesheetEditor/Shared/AppBar.razor b/AudioCuesheetEditor/Shared/AppBar.razor index 746f5c60..2a5d3638 100644 --- a/AudioCuesheetEditor/Shared/AppBar.razor +++ b/AudioCuesheetEditor/Shared/AppBar.razor @@ -46,7 +46,10 @@ along with Foobar. If not, see @if (DisplayFileMenu) { - + + + + @_localizer["Open"] diff --git a/AudioCuesheetEditor/Shared/Dialogs/EditMultipleTracksModal.razor b/AudioCuesheetEditor/Shared/Dialogs/EditMultipleTracksModal.razor index 9c8a87f7..e029d6f8 100644 --- a/AudioCuesheetEditor/Shared/Dialogs/EditMultipleTracksModal.razor +++ b/AudioCuesheetEditor/Shared/Dialogs/EditMultipleTracksModal.razor @@ -33,7 +33,7 @@ along with Foobar. If not, see @_localizer["Value"] - + @@ -41,7 +41,7 @@ along with Foobar. If not, see - + @@ -54,7 +54,7 @@ along with Foobar. If not, see - + @@ -82,7 +82,7 @@ along with Foobar. If not, see - + @@ -111,7 +111,7 @@ along with Foobar. If not, see - + @@ -125,7 +125,7 @@ along with Foobar. If not, see - + @@ -139,7 +139,7 @@ along with Foobar. If not, see - + @@ -153,7 +153,7 @@ along with Foobar. If not, see - + @@ -170,7 +170,7 @@ along with Foobar. If not, see - + @@ -184,7 +184,7 @@ along with Foobar. If not, see - + diff --git a/AudioCuesheetEditor/Shared/Dialogs/SettingsDialog.razor b/AudioCuesheetEditor/Shared/Dialogs/SettingsDialog.razor index b512dbe6..db6755b5 100644 --- a/AudioCuesheetEditor/Shared/Dialogs/SettingsDialog.razor +++ b/AudioCuesheetEditor/Shared/Dialogs/SettingsDialog.razor @@ -23,21 +23,21 @@ along with Foobar. If not, see @_localizer["Input"] - - - + + @foreach(var scheme in TimeSpanFormat.AvailableTimespanScheme) { @_localizer[scheme] } @_localizer["Display"] - - + @foreach (var level in Enum.GetValues()) { @level @@ -47,14 +47,13 @@ along with Foobar. If not, see @code { - MudTextField? timeInputFormatTextField; - ApplicationOptions? applicationOptions; - MudMenu? timespanFormatMenu; + ApplicationOptions? _applicationOptions; + MudMenu? _timespanFormatMenu; protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); - applicationOptions = await LocalStorageOptionsProvider.GetOptionsAsync(); + _applicationOptions = await LocalStorageOptionsProvider.GetOptionsAsync(); LocalStorageOptionsProvider.OptionSaved += LocalStorageOptionsProvider_OptionSaved; } @@ -66,7 +65,7 @@ along with Foobar. If not, see async Task TimeInputFormatChangedAsync(string newValue) { - TimeSpanFormat? timeSpanFormat = applicationOptions?.TimeSpanFormat; + TimeSpanFormat? timeSpanFormat = _applicationOptions?.TimeSpanFormat; if (string.IsNullOrEmpty(newValue)) { timeSpanFormat = null; @@ -87,9 +86,9 @@ along with Foobar. If not, see await LocalStorageOptionsProvider.SaveOptionsValueAsync(x => x.DisplayTimeSpanFormat, newValue); } - void AppendPlaceholderToTimeInputFormatTextField(string placeholder) + async Task AppendPlaceholderToTimeInputFormatTextField(string placeholder) { - timeInputFormatTextField?.SetText($"{timeInputFormatTextField.Text}{placeholder}"); + await TimeInputFormatChangedAsync($"{_applicationOptions?.TimeSpanFormat?.Scheme}{placeholder}"); } async Task LogLevelChanged(LogLevel logLevel) @@ -101,7 +100,7 @@ along with Foobar. If not, see { if (option is ApplicationOptions applicationOption) { - applicationOptions = applicationOption; + _applicationOptions = applicationOption; StateHasChanged(); } } diff --git a/AudioCuesheetEditor/Shared/Import/Importprofiles.razor b/AudioCuesheetEditor/Shared/Import/Importprofiles.razor index ca9c9da3..ffbd2063 100644 --- a/AudioCuesheetEditor/Shared/Import/Importprofiles.razor +++ b/AudioCuesheetEditor/Shared/Import/Importprofiles.razor @@ -51,7 +51,7 @@ along with Foobar. If not, see Validation="(string newValue) => _validationService.Validate(importOptions.SelectedImportProfile, nameof(Importprofile.Name))" Label="@_localizer["Profile name"]" Placeholder="@_localizer["Enter the name for this profile here"]" Variant="Variant.Outlined" /> - @foreach (var scheme in Importprofile.AvailableSchemeCuesheet) { - @_localizer[scheme] + @_localizer[scheme] } - @foreach (var scheme in Importprofile.AvailableSchemesTrack) { - @_localizer[scheme] + @_localizer[scheme] } - @foreach (var scheme in TimeSpanFormat.AvailableTimespanScheme) { - @_localizer[scheme] + @_localizer[scheme] } @@ -91,7 +91,6 @@ along with Foobar. If not, see @code { ImportOptions? importOptions; MudMenu? schemeCuesheetMenu, schemeTracksMenu, timeSpanFormatMenu; - MudTextField? schemeCuesheetTextField, schemeTracksTextField, timeSpanFormatTextField; protected override async Task OnInitializedAsync() { @@ -106,9 +105,19 @@ along with Foobar. If not, see base.LocalStorageOptionsProvider.OptionSaved -= LocalStorageOptionsProvider_OptionSaved; } - void AppendPlaceholderToTextField(MudTextField? mudTextField, string placeholder) + async Task AppendPlaceholderToSchemeCuesheet(string placeholder) { - mudTextField?.SetText($"{mudTextField.Text}{placeholder}"); + await SchemeCuesheetChangedAsync($"{importOptions?.SelectedImportProfile?.SchemeCuesheet}{placeholder}"); + } + + async Task AppendPlaceholderToSchemeTracks(string placeholder) + { + await SchemeCuesheetChangedAsync($"{importOptions?.SelectedImportProfile?.SchemeTracks}{placeholder}"); + } + + async Task AppendPlaceholderToImportTimeInputFormat(string placeholder) + { + await SchemeCuesheetChangedAsync($"{importOptions?.SelectedImportProfile?.TimeSpanFormat?.Scheme}{placeholder}"); } async Task SelectedImportProfileChangedAsync(Importprofile? newSelectedProfile) diff --git a/AudioCuesheetEditor/Shared/Inputs/FileDropOverlay.razor b/AudioCuesheetEditor/Shared/Inputs/FileDropOverlay.razor index f36353d9..42c3a9a7 100644 --- a/AudioCuesheetEditor/Shared/Inputs/FileDropOverlay.razor +++ b/AudioCuesheetEditor/Shared/Inputs/FileDropOverlay.razor @@ -38,6 +38,7 @@ along with Foobar. If not, see @code { + //TODO: Get this component working with the new MudFileUpload private DotNetObjectReference? _dotNetRef; private bool _showOverlay; private bool _disposed; diff --git a/AudioCuesheetEditor/Shared/Inputs/FileInput.razor b/AudioCuesheetEditor/Shared/Inputs/FileInput.razor index 65769f6c..860ee8d8 100644 --- a/AudioCuesheetEditor/Shared/Inputs/FileInput.razor +++ b/AudioCuesheetEditor/Shared/Inputs/FileInput.razor @@ -20,11 +20,12 @@ along with Foobar. If not, see @inject IStringLocalizer _localizer - @_localizer["Search"] diff --git a/AudioCuesheetEditor/Shared/Inputs/TextField.razor b/AudioCuesheetEditor/Shared/Inputs/TextField.razor index 0dd4df3e..dfc5d589 100644 --- a/AudioCuesheetEditor/Shared/Inputs/TextField.razor +++ b/AudioCuesheetEditor/Shared/Inputs/TextField.razor @@ -24,9 +24,10 @@ along with Foobar. If not, see - - @_localizer["Upload file"] - + + @_localizer["Upload file"] + + diff --git a/AudioCuesheetEditor/Shared/TrackList/TrackList.razor b/AudioCuesheetEditor/Shared/TrackList/TrackList.razor index 6475cdfa..a445f42b 100644 --- a/AudioCuesheetEditor/Shared/TrackList/TrackList.razor +++ b/AudioCuesheetEditor/Shared/TrackList/TrackList.razor @@ -298,7 +298,7 @@ along with Foobar. If not, see track.Position = newPosition; if (form != null) { - await form.Validate(); + await form.ValidateAsync(); } } @@ -307,7 +307,7 @@ along with Foobar. If not, see await _applicationOptionsTimeSpanParser.TimespanTextChanged(track, expression, value); if (form != null) { - await form.Validate(); + await form.ValidateAsync(); } } diff --git a/AudioCuesheetEditor/wwwroot/css/app.css b/AudioCuesheetEditor/wwwroot/css/app.css index f72131ce..3603a00d 100644 --- a/AudioCuesheetEditor/wwwroot/css/app.css +++ b/AudioCuesheetEditor/wwwroot/css/app.css @@ -61,4 +61,8 @@ a, .btn-link { opacity: 0; cursor: pointer; z-index: 10; +} + +.Opacity0 { + opacity: 0; } \ No newline at end of file From 48e318011d418a63a889da13f74f6669b9873713 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:19:24 +0100 Subject: [PATCH 32/48] fix file input --- AudioCuesheetEditor/Shared/Inputs/FileInput.razor | 2 +- AudioCuesheetEditor/wwwroot/css/app.css | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/AudioCuesheetEditor/Shared/Inputs/FileInput.razor b/AudioCuesheetEditor/Shared/Inputs/FileInput.razor index 860ee8d8..d121c19e 100644 --- a/AudioCuesheetEditor/Shared/Inputs/FileInput.razor +++ b/AudioCuesheetEditor/Shared/Inputs/FileInput.razor @@ -20,7 +20,7 @@ along with Foobar. If not, see @inject IStringLocalizer _localizer - @code { - //TODO: Get this component working with the new MudFileUpload private DotNetObjectReference? _dotNetRef; private bool _showOverlay; private bool _disposed; - private string _dragClass = DefaultDragClass; - - private const string DefaultDragClass = "d-flex align-center justify-center position-relative"; - private readonly string fileUploadId = $"FileDropOverlay_{Guid.NewGuid()}"; + + private readonly string _fileUploadId = $"FileDropOverlay_{Guid.NewGuid()}"; protected override async Task OnInitializedAsync() { @@ -70,33 +67,27 @@ along with Foobar. If not, see if (_showOverlay) { _showOverlay = false; - ClearDragClass(); StateHasChanged(); } return Task.CompletedTask; } - async Task OnInputFileChanged(InputFileChangeEventArgs e) + async Task InputFilesChanged(IReadOnlyList files) { try { await _dialogManager.ShowLoadingDialogAsync(); - await _importManager.UploadFilesAsync(e.GetMultipleFiles(), fileUploadId); + await _importManager.UploadFilesAsync(files, _fileUploadId); - ClearDragClass(); _showOverlay = false; await _jsRuntime.InvokeVoidAsync("globalFileDrag.reset", _dotNetRef); } finally { - _dialogManager.HideLoadingDialog(); + _dialogManager.HideLoadingDialog(); } } - void SetDragClass() => _dragClass = $"{DefaultDragClass} mud-border-primary"; - - void ClearDragClass() => _dragClass = DefaultDragClass; - public async ValueTask DisposeAsync() { if (_disposed) From 6b404681dd9a574724c113a067947cc883ad89c5 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:55:59 +0100 Subject: [PATCH 35/48] fix tests --- .../Models/DetailView.cs | 6 ++---- .../Models/RecordView.cs | 1 - .../Tests/Desktop/BasicTest.cs | 12 ++++++------ .../Tests/Desktop/ExportTest.cs | 6 +++--- .../Tests/Desktop/ImportTest.cs | 6 +++--- .../Tests/Desktop/RecordTest.cs | 2 +- .../Tests/Desktop/TracingTest.cs | 4 ++-- .../Tests/Smartphone/BasicTestSmartphone.cs | 12 ++++++------ .../Tests/Smartphone/ExportTestSmartphone.cs | 6 +++--- .../Tests/Smartphone/ImportTestSmartphone.cs | 6 +++--- .../Tests/Smartphone/RecordTestSmartphone.cs | 2 +- .../Tests/Smartphone/TracingTestSmartphone.cs | 4 ++-- 12 files changed, 32 insertions(+), 35 deletions(-) diff --git a/AudioCuesheetEditor.End2EndTests/Models/DetailView.cs b/AudioCuesheetEditor.End2EndTests/Models/DetailView.cs index ab75d268..4c9b9466 100644 --- a/AudioCuesheetEditor.End2EndTests/Models/DetailView.cs +++ b/AudioCuesheetEditor.End2EndTests/Models/DetailView.cs @@ -17,12 +17,11 @@ namespace AudioCuesheetEditor.End2EndTests.Models { - internal class DetailView(IPage page, bool mobile) + internal class DetailView(IPage page) { internal const string BaseUrl = "http://localhost:5132/"; private readonly IPage _page = page; - private readonly bool _isMobile = mobile; internal ILocator AudiofileInput => _page.GetByRole(AriaRole.Group).Filter(new() { HasText = "AudiofileAudiofile" }).Locator("input[type=\"file\"]"); @@ -115,8 +114,7 @@ internal async Task RenameAudiofileAsync(string filename) internal async Task OpenRenameAudiofileDialogAsync() { - int buttonIndex = _isMobile ? 2 : 3; - await _page.GetByRole(AriaRole.Group).Filter(new() { HasText = "AudiofileAudiofile" }).GetByRole(AriaRole.Button).Nth(buttonIndex).ClickAsync(); + await _page.GetByRole(AriaRole.Group).Filter(new() { HasText = "AudiofileAudiofile" }).GetByLabel("More").ClickAsync(); await _page.GetByText("Rename file").ClickAsync(); } } diff --git a/AudioCuesheetEditor.End2EndTests/Models/RecordView.cs b/AudioCuesheetEditor.End2EndTests/Models/RecordView.cs index 7b83f47e..dd2bc472 100644 --- a/AudioCuesheetEditor.End2EndTests/Models/RecordView.cs +++ b/AudioCuesheetEditor.End2EndTests/Models/RecordView.cs @@ -50,7 +50,6 @@ internal async Task AddRecordingTrackAsync(string artist, string title) await _page.GetByRole(AriaRole.Textbox, new() { Name = "Title", Exact = true }).FillAsync(title); await _page.GetByRole(AriaRole.Textbox, new() { Name = "Title", Exact = true }).PressAsync("Tab"); await _page.GetByRole(AriaRole.Button, new() { Name = "Add track" }).ClickAsync(); - await _page.Locator(".mud-overlay").ClickAsync(); } } } diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/BasicTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/BasicTest.cs index c307daf9..26ca4f12 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/BasicTest.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/BasicTest.cs @@ -24,7 +24,7 @@ public class BasicTest : PlaywrightTestBase [TestMethod] public async Task Application_HasTitle_WhenBrowsingIndex() { - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); await detailView.GotoAsync(); var appBar = new AppBar(TestPage); await Expect(appBar.HomeButton).ToBeVisibleAsync(); @@ -43,7 +43,7 @@ public async Task AboutHeader_HasTitle_WhenBrowsingAbout() [TestMethod] public async Task Audiofile_ShouldBeRenamed_WhenEditingFilename() { - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); await detailView.GotoAsync(); await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3"); await detailView.RenameAudiofileAsync("Kalimba test 123.mp3"); @@ -54,7 +54,7 @@ public async Task Audiofile_ShouldBeRenamed_WhenEditingFilename() public async Task OpenSettings_ShouldDisplaySettings_WhenSelectingSettings() { var bar = new AppBar(TestPage); - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); await detailView.GotoAsync(); await bar.OpenSettingsAsync(); await Expect(TestPage.GetByRole(AriaRole.Heading, new() { Name = "Settings" })).ToBeVisibleAsync(); @@ -65,7 +65,7 @@ public async Task ChangeLanguage_ShouldSwitchLanguage_WhenGermanIsSelected() { var bar = new AppBar(TestPage); var exportDialog = new ExportDialog(TestPage); - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); await detailView.GotoAsync(); await bar.ChangeLanguageAsync("German (Germany)"); await Expect(TestPage.GetByRole(AriaRole.Heading, new() { Name = "Abschnitte" })).ToBeVisibleAsync(); @@ -86,7 +86,7 @@ public async Task ChangeLanguage_ShouldSwitchLanguage_WhenGermanIsSelected() public async Task TrackTableControls_ShouldBeEnabled_WhenSelectingFirstTrackAsync() { var bar = new AppBar(TestPage); - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); await detailView.GotoAsync(); await detailView.AddTrackAsync(); await detailView.AddTrackAsync(); @@ -108,7 +108,7 @@ await Expect(TestPage.GetByLabel("Track table controls")).ToMatchAriaSnapshotAsy public async Task KeyboardCommands_ShouldControlDialogs_WhenUsingEnterOrEscapeAsync() { var bar = new AppBar(TestPage); - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); await detailView.GotoAsync(); await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached }); await bar.OpenExportDialogAsync("Cuesheet"); diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ExportTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ExportTest.cs index 58d53768..03108b43 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ExportTest.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ExportTest.cs @@ -25,7 +25,7 @@ public class ExportTest : PlaywrightTestBase public async Task DownloadCuesheet_GeneratesCuesheetFile_WhenCuesheetIsValid() { var bar = new AppBar(TestPage); - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); await detailView.GotoAsync(); await detailView.AddTrackAsync(); await detailView.CuesheetArtistInput.FillAsync("Cuesheet Artist 1"); @@ -54,7 +54,7 @@ TRACK 01 AUDIO public async Task DownloadProject_GeneratesProjectFile_WhenCuesheetIsValidAsync() { var bar = new AppBar(TestPage); - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); await detailView.GotoAsync(); await detailView.AddTrackAsync(); await detailView.CuesheetArtistInput.FillAsync("Cuesheet Artist 1"); @@ -75,7 +75,7 @@ public async Task DownloadProject_GeneratesProjectFile_WhenCuesheetIsValidAsync( public async Task DownloadText_GeneratesTextFile_WhenCuesheetIsValidAsync() { var bar = new AppBar(TestPage); - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); await detailView.GotoAsync(); await detailView.AddTrackAsync(); await detailView.CuesheetArtistInput.FillAsync("Cuesheet Artist 1"); diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs index 5d490e53..f7f63a60 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs @@ -25,7 +25,7 @@ public class ImportTest : PlaywrightTestBase public async Task Import_ShouldImportTracks_WhenUsingSampleInputfile() { var importView = new ImportView(TestPage, DeviceName != null); - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); await importView.GotoAsync(); await importView.ImportFileAsync("Sample_Inputfile.txt"); await importView.Analyze(); @@ -1690,7 +1690,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta [TestMethod] public async Task Open_ShouldImportProject_WhenUsingProjectfile() { - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); var appBar = new AppBar(TestPage); await detailView.GotoAsync(); await appBar.OpenFileAsync("Sample_Project.ace"); @@ -1919,7 +1919,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta [TestMethod] public async Task Open_ShouldImportCuesheet_WhenUsingCuesheetfile() { - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); var appBar = new AppBar(TestPage); await detailView.GotoAsync(); await appBar.OpenFileAsync("Sample_Cuesheet.cue"); diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/RecordTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/RecordTest.cs index 8bb7d16d..da4133d1 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/RecordTest.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/RecordTest.cs @@ -26,7 +26,7 @@ public async Task Record_ShouldRecordTracks_WhenTracksAdded() { var recordView = new RecordView(TestPage); var viewModes = new ViewModes(TestPage, DeviceName != null); - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); await recordView.GotoAsync(); await recordView.StartRecordingAsync(); await recordView.AddRecordingTrackAsync("Test Track 1 Artist", "Test Track 1 Title"); diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/TracingTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/TracingTest.cs index 94359479..0067adb2 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/TracingTest.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/TracingTest.cs @@ -25,7 +25,7 @@ public class TracingTest : PlaywrightTestBase public async Task UndoRedo_ShouldRestoreTrackState_WhenUndoAndRedoAreUsed() { var bar = new AppBar(TestPage); - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); await detailView.GotoAsync(); await detailView.AddTrackAsync(); await detailView.EditTrackAsync("Test Artist 1"); @@ -247,7 +247,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta public async Task UndoRedo_ShouldRestoreTrackState_WhenModalEdit() { var bar = new AppBar(TestPage); - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); await detailView.GotoAsync(); await detailView.AddTrackAsync(); await detailView.SelectTracksAsync([1]); diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTestSmartphone.cs b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTestSmartphone.cs index 33b743f5..92a725ed 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTestSmartphone.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/BasicTestSmartphone.cs @@ -26,7 +26,7 @@ public class BasicTestSmartphone : PlaywrightTestBase [TestMethod] public async Task Application_HasTitle_WhenBrowsingIndex() { - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); await detailView.GotoAsync(); var appBar = new AppBar(TestPage); await Expect(appBar.HomeButton).ToBeVisibleAsync(); @@ -45,7 +45,7 @@ public async Task AboutHeader_HasTitle_WhenBrowsingAbout() [TestMethod] public async Task Audiofile_ShouldBeRenamed_WhenEditingFilename() { - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); await detailView.GotoAsync(); await detailView.AudiofileInput.SetInputFilesAsync("Kalimba.mp3"); await detailView.RenameAudiofileAsync("Kalimba test 123.mp3"); @@ -56,7 +56,7 @@ public async Task Audiofile_ShouldBeRenamed_WhenEditingFilename() public async Task OpenSettings_ShouldDisplaySettings_WhenSelectingSettings() { var bar = new AppBar(TestPage); - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); await detailView.GotoAsync(); await bar.OpenSettingsAsync(); await Expect(TestPage.GetByRole(AriaRole.Heading, new() { Name = "Settings" })).ToBeVisibleAsync(); @@ -67,7 +67,7 @@ public async Task ChangeLanguage_ShouldSwitchLanguage_WhenGermanIsSelected() { var bar = new AppBar(TestPage); var exportDialog = new ExportDialog(TestPage); - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); await detailView.GotoAsync(); await bar.ChangeLanguageAsync("German (Germany)"); await Expect(TestPage.GetByRole(AriaRole.Heading, new() { Name = "Abschnitte" })).ToBeVisibleAsync(); @@ -88,7 +88,7 @@ public async Task ChangeLanguage_ShouldSwitchLanguage_WhenGermanIsSelected() public async Task TrackTableControls_ShouldBeEnabled_WhenSelectingFirstTrackAsync() { var bar = new AppBar(TestPage); - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); await detailView.GotoAsync(); await detailView.AddTrackAsync(); await detailView.AddTrackAsync(); @@ -107,7 +107,7 @@ await Expect(TestPage.GetByLabel("Track table controls")).ToMatchAriaSnapshotAsy public async Task KeyboardCommands_ShouldControlDialogs_WhenUsingEnterOrEscapeAsync() { var bar = new AppBar(TestPage); - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); await detailView.GotoAsync(); await TestPage.GetByRole(AriaRole.Dialog).WaitForAsync(new() { State = WaitForSelectorState.Detached }); await bar.OpenExportDialogAsync("Cuesheet"); diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ExportTestSmartphone.cs b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ExportTestSmartphone.cs index 6bee002f..eafe2b1f 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ExportTestSmartphone.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ExportTestSmartphone.cs @@ -25,7 +25,7 @@ public class ExportTestSmartphone : PlaywrightTestBase public async Task DownloadCuesheet_GeneratesCuesheetFile_WhenCuesheetIsValid() { var bar = new AppBar(TestPage); - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); await detailView.GotoAsync(); await detailView.AddTrackAsync(); await detailView.CuesheetArtistInput.FillAsync("Cuesheet Artist 1"); @@ -54,7 +54,7 @@ TRACK 01 AUDIO public async Task DownloadProject_GeneratesProjectFile_WhenCuesheetIsValidAsync() { var bar = new AppBar(TestPage); - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); await detailView.GotoAsync(); await detailView.AddTrackAsync(); await detailView.CuesheetArtistInput.FillAsync("Cuesheet Artist 1"); @@ -75,7 +75,7 @@ public async Task DownloadProject_GeneratesProjectFile_WhenCuesheetIsValidAsync( public async Task DownloadText_GeneratesTextFile_WhenCuesheetIsValidAsync() { var bar = new AppBar(TestPage); - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); await detailView.GotoAsync(); await detailView.AddTrackAsync(); await detailView.CuesheetArtistInput.FillAsync("Cuesheet Artist 1"); diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTestSmartphone.cs b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTestSmartphone.cs index 6b8b4b60..57f346d5 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTestSmartphone.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTestSmartphone.cs @@ -27,7 +27,7 @@ public class ImportTestSmartphone : PlaywrightTestBase public async Task Import_ShouldImportTracks_WhenUsingSampleInputfile() { var importView = new ImportView(TestPage, DeviceName != null); - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); await importView.GotoAsync(); await importView.ImportFileAsync("Sample_Inputfile.txt"); await importView.Analyze(); @@ -3067,7 +3067,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta [TestMethod] public async Task Open_ShouldImportProject_WhenUsingProjectfile() { - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); var appBar = new AppBar(TestPage); await detailView.GotoAsync(); await appBar.OpenFileAsync("Sample_Project.ace"); @@ -3320,7 +3320,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta [TestMethod] public async Task Open_ShouldImportCuesheet_WhenUsingCuesheetfile() { - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); var appBar = new AppBar(TestPage); await detailView.GotoAsync(); await appBar.OpenFileAsync("Sample_Cuesheet.cue"); diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/RecordTestSmartphone.cs b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/RecordTestSmartphone.cs index e86df736..66a61e4a 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/RecordTestSmartphone.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/RecordTestSmartphone.cs @@ -27,7 +27,7 @@ public async Task Record_ShouldRecordTracks_WhenTracksAdded() { var recordView = new RecordView(TestPage); var viewModes = new ViewModes(TestPage, DeviceName != null); - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); await recordView.GotoAsync(); await recordView.StartRecordingAsync(); await recordView.AddRecordingTrackAsync("Test Track 1 Artist", "Test Track 1 Title"); diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/TracingTestSmartphone.cs b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/TracingTestSmartphone.cs index 5ad6a5b6..e0198f87 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/TracingTestSmartphone.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/TracingTestSmartphone.cs @@ -27,7 +27,7 @@ public class TracingTestSmartphone : PlaywrightTestBase public async Task UndoRedo_ShouldRestoreTrackState_WhenUndoAndRedoAreUsed() { var bar = new AppBar(TestPage); - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); await detailView.GotoAsync(); await detailView.AddTrackAsync(); await detailView.EditTrackAsync("Test Artist 1"); @@ -143,7 +143,7 @@ await Expect(TestPage.GetByRole(AriaRole.Table)).ToMatchAriaSnapshotAsync(@"- ta public async Task UndoRedo_ShouldRestoreTrackState_WhenModalEdit() { var bar = new AppBar(TestPage); - var detailView = new DetailView(TestPage, DeviceName != null); + var detailView = new DetailView(TestPage); await detailView.GotoAsync(); await detailView.AddTrackAsync(); await detailView.SelectTracksAsync([1]); From 4c7329b061e7c77678d3687626c6618d5664453f Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Thu, 5 Mar 2026 08:49:44 +0100 Subject: [PATCH 36/48] Update FileDropOverlay.razor --- AudioCuesheetEditor/Shared/Inputs/FileDropOverlay.razor | 1 + 1 file changed, 1 insertion(+) diff --git a/AudioCuesheetEditor/Shared/Inputs/FileDropOverlay.razor b/AudioCuesheetEditor/Shared/Inputs/FileDropOverlay.razor index 1cd00c61..d2552472 100644 --- a/AudioCuesheetEditor/Shared/Inputs/FileDropOverlay.razor +++ b/AudioCuesheetEditor/Shared/Inputs/FileDropOverlay.razor @@ -34,6 +34,7 @@ along with Foobar. If not, see + From 9d1bdb95b1b1d73d75bed5d1e2e10df5a8cc0149 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Thu, 5 Mar 2026 08:52:26 +0100 Subject: [PATCH 37/48] Update FileDropOverlay.razor --- AudioCuesheetEditor/Shared/Inputs/FileDropOverlay.razor | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/AudioCuesheetEditor/Shared/Inputs/FileDropOverlay.razor b/AudioCuesheetEditor/Shared/Inputs/FileDropOverlay.razor index d2552472..2f22ee2f 100644 --- a/AudioCuesheetEditor/Shared/Inputs/FileDropOverlay.razor +++ b/AudioCuesheetEditor/Shared/Inputs/FileDropOverlay.razor @@ -78,9 +78,8 @@ along with Foobar. If not, see try { await _dialogManager.ShowLoadingDialogAsync(); - await _importManager.UploadFilesAsync(files, _fileUploadId); - _showOverlay = false; + await _importManager.UploadFilesAsync(files, _fileUploadId); await _jsRuntime.InvokeVoidAsync("globalFileDrag.reset", _dotNetRef); } finally From 77151ca674aac29cb6cd3818f2e82137d712b774 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Thu, 5 Mar 2026 10:52:43 +0100 Subject: [PATCH 38/48] first step for displaying export content directly --- .../Model/IO/Export/Exportfile.cs | 2 +- .../Services/IO/CuesheetExportService.cs | 14 ++--- .../Services/IO/ExportfileGenerator.cs | 14 ++--- .../Shared/Dialogs/GenerateExportDialog.razor | 57 +++++++++++++------ 4 files changed, 49 insertions(+), 38 deletions(-) diff --git a/AudioCuesheetEditor/Model/IO/Export/Exportfile.cs b/AudioCuesheetEditor/Model/IO/Export/Exportfile.cs index 154a3af4..e2c7baf5 100644 --- a/AudioCuesheetEditor/Model/IO/Export/Exportfile.cs +++ b/AudioCuesheetEditor/Model/IO/Export/Exportfile.cs @@ -20,7 +20,7 @@ public class Exportfile { public static readonly string DefaultCuesheetFilename = "Cuesheet.cue"; public string Name { get; set; } = String.Empty; - public byte[]? Content { get; set; } + public string? Content { get; set; } public TimeSpan? Begin { get; set; } public TimeSpan? End { get; set; } } diff --git a/AudioCuesheetEditor/Services/IO/CuesheetExportService.cs b/AudioCuesheetEditor/Services/IO/CuesheetExportService.cs index e3b74d4b..835cad77 100644 --- a/AudioCuesheetEditor/Services/IO/CuesheetExportService.cs +++ b/AudioCuesheetEditor/Services/IO/CuesheetExportService.cs @@ -57,10 +57,7 @@ public IReadOnlyCollection GenerateExportfiles(string? filename) { content = WriteCuesheet(audioFileName, section); var name = string.Format("{0}({1}){2}", Path.GetFileNameWithoutExtension(filename), counter, FileExtensions.Cuesheet); - if (content != null) - { - exportfiles.Add(new Exportfile() { Name = name, Content = Encoding.UTF8.GetBytes(content), Begin = section.Begin, End = section.End }); - } + exportfiles.Add(new Exportfile() { Name = name, Content = content, Begin = section.Begin, End = section.End }); counter++; } } @@ -77,12 +74,9 @@ public IReadOnlyCollection GenerateExportfiles(string? filename) { content = WriteCuesheet(_sessionStateContainer.Cuesheet.Audiofile.Name); } - if (content != null) - { - var begin = _sessionStateContainer.Cuesheet.Tracks.Min(x => x.Begin); - var end = _sessionStateContainer.Cuesheet.Tracks.Max(x => x.End); - exportfiles.Add(new Exportfile() { Name = filename!, Content = Encoding.UTF8.GetBytes(content), Begin = begin, End = end }); - } + var begin = _sessionStateContainer.Cuesheet.Tracks.Min(x => x.Begin); + var end = _sessionStateContainer.Cuesheet.Tracks.Max(x => x.End); + exportfiles.Add(new Exportfile() { Name = filename!, Content = content, Begin = begin, End = end }); } } return exportfiles; diff --git a/AudioCuesheetEditor/Services/IO/ExportfileGenerator.cs b/AudioCuesheetEditor/Services/IO/ExportfileGenerator.cs index bbca550a..b6f3656b 100644 --- a/AudioCuesheetEditor/Services/IO/ExportfileGenerator.cs +++ b/AudioCuesheetEditor/Services/IO/ExportfileGenerator.cs @@ -60,10 +60,7 @@ public IReadOnlyCollection GenerateExportfiles(Exportprofile? export { content = WriteExport(exportprofile, audioFileName, section); filename = string.Format("{0}({1}){2}", Path.GetFileNameWithoutExtension(exportprofile.Filename), counter, Path.GetExtension(exportprofile.Filename)); - if (content != null) - { - exportfiles.Add(new Exportfile() { Name = filename, Content = Encoding.UTF8.GetBytes(content), Begin = section.Begin, End = section.End }); - } + exportfiles.Add(new Exportfile() { Name = filename, Content = content, Begin = section.Begin, End = section.End }); counter++; } } @@ -75,12 +72,9 @@ public IReadOnlyCollection GenerateExportfiles(Exportprofile? export { content = WriteExport(exportprofile, _sessionStateContainer.Cuesheet.Audiofile.Name); } - if (content != null) - { - var begin = _sessionStateContainer.Cuesheet.Tracks.Min(x => x.Begin); - var end = _sessionStateContainer.Cuesheet.Tracks.Max(x => x.End); - exportfiles.Add(new Exportfile() { Name = exportprofile.Filename, Content = Encoding.UTF8.GetBytes(content), Begin = begin, End = end }); - } + var begin = _sessionStateContainer.Cuesheet.Tracks.Min(x => x.Begin); + var end = _sessionStateContainer.Cuesheet.Tracks.Max(x => x.End); + exportfiles.Add(new Exportfile() { Name = exportprofile.Filename, Content = content, Begin = begin, End = end }); } } return exportfiles; diff --git a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.razor b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.razor index 38adcd9d..9ef62448 100644 --- a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.razor +++ b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.razor @@ -22,6 +22,7 @@ along with Foobar. If not, see @inject ValidationService _validationService @inject ExportfileGenerator _exportfileGenerator @inject IBlazorDownloadFileService _blazorDownloadFileService +@inject IJSRuntime _jsRuntime @if (exportOptions != null) { @@ -120,23 +121,38 @@ along with Foobar. If not, see - - - @_localizer["Name"] - @_localizer["Begin"] - @_localizer["End"] - @_localizer["Content"] - - - @context.Name - @context.Begin - @context.End - @{ - var ariaLabel = $"Download-{context.Name}"; - - } - - + @if (exportFiles.Count() == 1) + { + var exportFile = exportFiles.Single(); + var lines = exportFile.Content?.Split(Environment.NewLine).Length ?? 1; + + + + @_localizer["Copy"] + + + + } + else + { + + + @_localizer["Name"] + @_localizer["Begin"] + @_localizer["End"] + @_localizer["Content"] + + + @context.Name + @context.Begin + @context.End + @{ + var ariaLabel = $"Download-{context.Name}"; + + } + + + } @@ -144,6 +160,7 @@ along with Foobar. If not, see } @code { + //TODO: Display content for multiple files ExportOptions? exportOptions; IEnumerable exportFiles = []; Boolean configureExportCompleted = false; @@ -228,4 +245,10 @@ along with Foobar. If not, see await menu.OpenMenuAsync(args); } } + + async Task CopyClipboardClicked(Exportfile exportfile) + { + //TODO: feedback for user + await _jsRuntime.InvokeVoidAsync("navigator.clipboard.writeText", exportfile.Content); + } } From 5f5e6d6c743124f07e28b02f0be4ca25435d05fb Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Thu, 5 Mar 2026 10:54:53 +0100 Subject: [PATCH 39/48] add localization --- .../Shared/Dialogs/GenerateExportDialog.de.resx | 3 +++ AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.razor | 4 +++- AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.resx | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.de.resx b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.de.resx index 75873533..5f2ab83e 100644 --- a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.de.resx +++ b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.de.resx @@ -216,4 +216,7 @@ Nachlücke + + Kopieren + \ No newline at end of file diff --git a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.razor b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.razor index 9ef62448..97ff738b 100644 --- a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.razor +++ b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.razor @@ -148,7 +148,9 @@ along with Foobar. If not, see @context.End @{ var ariaLabel = $"Download-{context.Name}"; - + + + } diff --git a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.resx b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.resx index 1a4e41a3..2dc0f72c 100644 --- a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.resx +++ b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.resx @@ -216,4 +216,7 @@ PostGap + + Copy + \ No newline at end of file From f04ed82bf9443d58b975735c7c97e270d006948f Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Thu, 5 Mar 2026 11:05:48 +0100 Subject: [PATCH 40/48] moved to seperate component --- AudioCuesheetEditor.sln | 4 +- .../Dialogs/GenerateExportDialog.de.resx | 3 - .../Shared/Dialogs/GenerateExportDialog.razor | 15 +-- .../Shared/Dialogs/GenerateExportDialog.resx | 3 - .../Export/DisplayExportfileContent.de.resx | 123 ++++++++++++++++++ .../Export/DisplayExportfileContent.razor | 50 +++++++ .../Export/DisplayExportfileContent.resx | 123 ++++++++++++++++++ AudioCuesheetEditor/_Imports.razor | 1 + 8 files changed, 300 insertions(+), 22 deletions(-) create mode 100644 AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.de.resx create mode 100644 AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.razor create mode 100644 AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.resx diff --git a/AudioCuesheetEditor.sln b/AudioCuesheetEditor.sln index bf6847c0..40cbf088 100644 --- a/AudioCuesheetEditor.sln +++ b/AudioCuesheetEditor.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.1.32210.238 +# Visual Studio Version 18 +VisualStudioVersion = 18.3.11512.155 d18.3 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudioCuesheetEditor", "AudioCuesheetEditor\AudioCuesheetEditor.csproj", "{099AE4DF-4847-460F-BBF2-207B9A35B58D}" EndProject diff --git a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.de.resx b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.de.resx index 5f2ab83e..75873533 100644 --- a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.de.resx +++ b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.de.resx @@ -216,7 +216,4 @@ Nachlücke - - Kopieren - \ No newline at end of file diff --git a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.razor b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.razor index 97ff738b..be9bb8e5 100644 --- a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.razor +++ b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.razor @@ -124,14 +124,7 @@ along with Foobar. If not, see @if (exportFiles.Count() == 1) { var exportFile = exportFiles.Single(); - var lines = exportFile.Content?.Split(Environment.NewLine).Length ?? 1; - - - - @_localizer["Copy"] - - - + } else { @@ -247,10 +240,4 @@ along with Foobar. If not, see await menu.OpenMenuAsync(args); } } - - async Task CopyClipboardClicked(Exportfile exportfile) - { - //TODO: feedback for user - await _jsRuntime.InvokeVoidAsync("navigator.clipboard.writeText", exportfile.Content); - } } diff --git a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.resx b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.resx index 2dc0f72c..1a4e41a3 100644 --- a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.resx +++ b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.resx @@ -216,7 +216,4 @@ PostGap - - Copy - \ No newline at end of file diff --git a/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.de.resx b/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.de.resx new file mode 100644 index 00000000..fcd6beb0 --- /dev/null +++ b/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.de.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 + + + Kopieren + + \ No newline at end of file diff --git a/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.razor b/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.razor new file mode 100644 index 00000000..abd7eed1 --- /dev/null +++ b/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.razor @@ -0,0 +1,50 @@ + +@inherits BaseLocalizedComponent + +@inject IStringLocalizer _localizer +@inject IBlazorDownloadFileService _blazorDownloadFileService +@inject IJSRuntime _jsRuntime + + + + + @_localizer["Copy"] + + + + +@code { + private int _lineCount; + + [Parameter] + [EditorRequired] + public Exportfile Exportfile { get; set; } = default!; + + protected override void OnParametersSet() + { + base.OnParametersSet(); + _lineCount = Exportfile.Content?.Split(Environment.NewLine).Length ?? 1; + } + + async Task CopyClipboardClicked() + { + //TODO: feedback for user + await _jsRuntime.InvokeVoidAsync("navigator.clipboard.writeText", Exportfile.Content); + } +} diff --git a/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.resx b/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.resx new file mode 100644 index 00000000..fa348179 --- /dev/null +++ b/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.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 + + + Copy + + \ No newline at end of file diff --git a/AudioCuesheetEditor/_Imports.razor b/AudioCuesheetEditor/_Imports.razor index 4dd0e275..911e475a 100644 --- a/AudioCuesheetEditor/_Imports.razor +++ b/AudioCuesheetEditor/_Imports.razor @@ -21,6 +21,7 @@ @using AudioCuesheetEditor.Shared.Import @using AudioCuesheetEditor.Shared.Audio @using AudioCuesheetEditor.Shared.Record +@using AudioCuesheetEditor.Shared.Export @using AudioCuesheetEditor.Model.AudioCuesheet @using AudioCuesheetEditor.Model.IO @using AudioCuesheetEditor.Model.IO.Export From 810baa301eb6b2d944f3fd7c2acbd1f3e62dc17c Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Thu, 5 Mar 2026 13:24:19 +0100 Subject: [PATCH 41/48] display content with multiple files --- .../Dialogs/GenerateExportDialog.de.resx | 3 + .../Shared/Dialogs/GenerateExportDialog.razor | 115 ++++++++++-------- .../Shared/Dialogs/GenerateExportDialog.resx | 3 + .../Export/DisplayExportfileContent.razor | 10 +- 4 files changed, 74 insertions(+), 57 deletions(-) diff --git a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.de.resx b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.de.resx index 75873533..29747256 100644 --- a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.de.resx +++ b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.de.resx @@ -216,4 +216,7 @@ Nachlücke + + Inhalt anzeigen + \ No newline at end of file diff --git a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.razor b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.razor index be9bb8e5..c8d8da7f 100644 --- a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.razor +++ b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.razor @@ -22,14 +22,14 @@ along with Foobar. If not, see @inject ValidationService _validationService @inject ExportfileGenerator _exportfileGenerator @inject IBlazorDownloadFileService _blazorDownloadFileService -@inject IJSRuntime _jsRuntime +@inject IDialogService _dialogService -@if (exportOptions != null) +@if (_exportOptions != null) { - + @if (String.IsNullOrEmpty(GetGenerationValidationMessages()) == false) { @@ -37,8 +37,8 @@ along with Foobar. If not, see @((MarkupString)GetGenerationValidationMessages()!) } - - @foreach (var profile in exportOptions.ExportProfiles) + + @foreach (var profile in _exportOptions.ExportProfiles) { @profile.Name } @@ -51,68 +51,68 @@ along with Foobar. If not, see - @_localizer["Delete selected export profile"] + @_localizer["Delete selected export profile"] - +
- - + - + - + Validation="(string? newValue) => _validationService.Validate(_exportOptions.SelectedExportProfile, nameof(Exportprofile.SchemeHead))" + Adornment="Adornment.End" AdornmentIcon="@Icons.Material.Outlined.AddBox" OnAdornmentClick="(args) => AdornmentClickAsync(_schemeHeadMenu, args)" /> + @foreach (var placeholder in Exportprofile.AvailableCuesheetSchemes) { @_localizer[placeholder.Key] } - - + Validation="(string? newValue) => _validationService.Validate(_exportOptions.SelectedExportProfile, nameof(Exportprofile.SchemeTracks))" + Adornment="Adornment.End" AdornmentIcon="@Icons.Material.Outlined.AddBox" OnAdornmentClick="(args) => AdornmentClickAsync(_schemeTracksMenu, args)" /> + @foreach (var placeholder in Exportprofile.AvailableTrackSchemes) { @_localizer[placeholder.Key] } - - + Validation="(string? newValue) => _validationService.Validate(_exportOptions.SelectedExportProfile, nameof(Exportprofile.SchemeFooter))" + Adornment="Adornment.End" AdornmentIcon="@Icons.Material.Outlined.AddBox" OnAdornmentClick="(args) => AdornmentClickAsync(_schemeFooterMenu, args)" /> + @foreach (var placeholder in Exportprofile.AvailableCuesheetSchemes) { @_localizer[placeholder.Key] @@ -121,14 +121,14 @@ along with Foobar. If not, see - @if (exportFiles.Count() == 1) + @if (_exportFiles.Count() == 1) { - var exportFile = exportFiles.Single(); + var exportFile = _exportFiles.Single(); } else { - + @_localizer["Name"] @_localizer["Begin"] @@ -140,9 +140,18 @@ along with Foobar. If not, see @context.Begin @context.End @{ - var ariaLabel = $"Download-{context.Name}"; + var ariaLabelDownload = $"Download-{context.Name}"; + var ariaLabelDialog = $"Dialog-{context.Name}"; + var dialogVisible = _displayContentDialogVisible.GetValueOrDefault(context); + + @_localizer["Display content"] + + + + - + + } @@ -155,11 +164,13 @@ along with Foobar. If not, see } @code { - //TODO: Display content for multiple files - ExportOptions? exportOptions; - IEnumerable exportFiles = []; - Boolean configureExportCompleted = false; - MudMenu? schemeHeadMenu, schemeTracksMenu, schemeFooterMenu; + ExportOptions? _exportOptions; + IEnumerable _exportFiles = []; + Boolean _configureExportCompleted = false; + MudMenu? _schemeHeadMenu, _schemeTracksMenu, _schemeFooterMenu; + Dictionary _displayContentDialogVisible = new(); + + private readonly DialogOptions _displayContentDialogOptions = new() { CloseButton = true, CloseOnEscapeKey = true, FullWidth = true, MaxWidth = MaxWidth.ExtraExtraLarge }; protected override void Dispose(bool disposing) { @@ -171,43 +182,43 @@ along with Foobar. If not, see { await base.OnInitializedAsync(); LocalStorageOptionsProvider.OptionSaved += LocalStorageOptionsProvider_OptionSaved; - exportOptions = await LocalStorageOptionsProvider.GetOptionsAsync(); + _exportOptions = await LocalStorageOptionsProvider.GetOptionsAsync(); } void LocalStorageOptionsProvider_OptionSaved(object? sender, IOptions option) { if (option is ExportOptions exportOption) { - exportOptions = exportOption; + _exportOptions = exportOption; StateHasChanged(); } } async Task AddClick() { - if (exportOptions != null) + if (_exportOptions != null) { var newProfile = new Exportprofile(); - exportOptions.ExportProfiles.Add(newProfile); - exportOptions.SelectedExportProfile = newProfile; - await LocalStorageOptionsProvider.SaveOptionsAsync(exportOptions); + _exportOptions.ExportProfiles.Add(newProfile); + _exportOptions.SelectedExportProfile = newProfile; + await LocalStorageOptionsProvider.SaveOptionsAsync(_exportOptions); } } async Task DeleteClick() { - if (exportOptions?.SelectedExportProfile != null) + if (_exportOptions?.SelectedExportProfile != null) { - exportOptions.ExportProfiles.Remove(exportOptions.SelectedExportProfile); - exportOptions.SelectedExportProfile = exportOptions.ExportProfiles.LastOrDefault(); - await LocalStorageOptionsProvider.SaveOptionsAsync(exportOptions); + _exportOptions.ExportProfiles.Remove(_exportOptions.SelectedExportProfile); + _exportOptions.SelectedExportProfile = _exportOptions.ExportProfiles.LastOrDefault(); + await LocalStorageOptionsProvider.SaveOptionsAsync(_exportOptions); } } String? GetGenerationValidationMessages() { String? validationErrorMessage = null; - var messages = _exportfileGenerator.CanGenerateExportfiles(exportOptions?.SelectedExportProfile); + var messages = _exportfileGenerator.CanGenerateExportfiles(_exportOptions?.SelectedExportProfile); if (messages.Count() > 0) { validationErrorMessage = String.Join("
", messages.Select(x => x.GetMessageLocalized(_validationMessageLocalizer))); @@ -228,8 +239,8 @@ along with Foobar. If not, see { if (newIndex == 1) { - exportFiles = _exportfileGenerator.GenerateExportfiles(exportOptions?.SelectedExportProfile); - configureExportCompleted = exportFiles.Any(); + _exportFiles = _exportfileGenerator.GenerateExportfiles(_exportOptions?.SelectedExportProfile); + _configureExportCompleted = _exportFiles.Any(); } } diff --git a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.resx b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.resx index 1a4e41a3..b44b5523 100644 --- a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.resx +++ b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.resx @@ -216,4 +216,7 @@ PostGap + + Display content + \ No newline at end of file diff --git a/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.razor b/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.razor index abd7eed1..78fd8a21 100644 --- a/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.razor +++ b/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.razor @@ -22,10 +22,10 @@ along with Foobar. If not, see @inject IJSRuntime _jsRuntime - + @_localizer["Copy"] - + @@ -34,17 +34,17 @@ along with Foobar. If not, see [Parameter] [EditorRequired] - public Exportfile Exportfile { get; set; } = default!; + public Exportfile? Exportfile { get; set; } protected override void OnParametersSet() { base.OnParametersSet(); - _lineCount = Exportfile.Content?.Split(Environment.NewLine).Length ?? 1; + _lineCount = Exportfile?.Content?.Split(Environment.NewLine).Length ?? 1; } async Task CopyClipboardClicked() { //TODO: feedback for user - await _jsRuntime.InvokeVoidAsync("navigator.clipboard.writeText", Exportfile.Content); + await _jsRuntime.InvokeVoidAsync("navigator.clipboard.writeText", Exportfile?.Content); } } From 432894cde586ac0c88e08456374e0c568812d389 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Thu, 5 Mar 2026 13:43:00 +0100 Subject: [PATCH 42/48] add feedback for user --- .../Shared/Export/DisplayExportfileContent.de.resx | 3 +++ .../Shared/Export/DisplayExportfileContent.razor | 9 ++++++++- .../Shared/Export/DisplayExportfileContent.resx | 3 +++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.de.resx b/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.de.resx index fcd6beb0..19803525 100644 --- a/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.de.resx +++ b/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.de.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + In Zwischenablage kopiert + Kopieren diff --git a/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.razor b/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.razor index 78fd8a21..114bbcbe 100644 --- a/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.razor +++ b/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.razor @@ -20,6 +20,7 @@ along with Foobar. If not, see @inject IStringLocalizer _localizer @inject IBlazorDownloadFileService _blazorDownloadFileService @inject IJSRuntime _jsRuntime +@inject ISnackbar _snackbar @@ -44,7 +45,13 @@ along with Foobar. If not, see async Task CopyClipboardClicked() { - //TODO: feedback for user await _jsRuntime.InvokeVoidAsync("navigator.clipboard.writeText", Exportfile?.Content); + _snackbar.Add(_localizer["Copied to clipboard"], Severity.Info, config => + { + config.ShowTransitionDuration = 500; + config.VisibleStateDuration = 1000; + config.HideTransitionDuration = 500; + config.ShowCloseIcon = false; + }); } } diff --git a/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.resx b/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.resx index fa348179..2af11de2 100644 --- a/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.resx +++ b/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Copied to clipboard + Copy From 1e3e089103b6debf06d9b3d0e3ffb25fb42737f7 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Thu, 5 Mar 2026 13:56:24 +0100 Subject: [PATCH 43/48] fix tests --- .../Tests/Desktop/ExportTest.cs | 2 +- .../Tests/Smartphone/ExportTestSmartphone.cs | 2 +- .../Services/IO/CuesheetExportServiceTests.cs | 6 ++---- .../Services/IO/ExportfileGeneratorTests.cs | 6 ++---- .../Shared/Dialogs/GenerateCuesheetDialog.razor | 2 +- .../Shared/Export/DisplayExportfileContent.razor | 4 ++-- 6 files changed, 9 insertions(+), 13 deletions(-) diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ExportTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ExportTest.cs index 03108b43..e2cb6937 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ExportTest.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ExportTest.cs @@ -85,7 +85,7 @@ public async Task DownloadText_GeneratesTextFile_WhenCuesheetIsValidAsync() await bar.OpenExportDialogAsync("Textfile"); await TestPage.GetByRole(AriaRole.Button, new() { Name = "Next", Exact = true }).ClickAsync(); var downloadTask = TestPage.WaitForDownloadAsync(); - await TestPage.GetByRole(AriaRole.Button, new() { Name = "Download-YouTube.txt" }).ClickAsync(); + await TestPage.GetByRole(AriaRole.Button, new() { Name = "Download" }).ClickAsync(); var download = await downloadTask; using var stream = await download.CreateReadStreamAsync(); using var reader = new StreamReader(stream); diff --git a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ExportTestSmartphone.cs b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ExportTestSmartphone.cs index eafe2b1f..61cdc3fd 100644 --- a/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ExportTestSmartphone.cs +++ b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ExportTestSmartphone.cs @@ -85,7 +85,7 @@ public async Task DownloadText_GeneratesTextFile_WhenCuesheetIsValidAsync() await bar.OpenExportDialogAsync("Textfile"); await TestPage.GetByRole(AriaRole.Button, new() { Name = "Next", Exact = true }).ClickAsync(); var downloadTask = TestPage.WaitForDownloadAsync(); - await TestPage.GetByRole(AriaRole.Button, new() { Name = "Download-YouTube.txt" }).ClickAsync(); + await TestPage.GetByRole(AriaRole.Button, new() { Name = "Download" }).ClickAsync(); var download = await downloadTask; using var stream = await download.CreateReadStreamAsync(); using var reader = new StreamReader(stream); diff --git a/AudioCuesheetEditor.Tests/Services/IO/CuesheetExportServiceTests.cs b/AudioCuesheetEditor.Tests/Services/IO/CuesheetExportServiceTests.cs index 968fc387..bfa05bc5 100644 --- a/AudioCuesheetEditor.Tests/Services/IO/CuesheetExportServiceTests.cs +++ b/AudioCuesheetEditor.Tests/Services/IO/CuesheetExportServiceTests.cs @@ -115,7 +115,6 @@ public void GenerateExportfiles_WithoutSections_ReturnsExportFile() Assert.AreEqual(new TimeSpan(0, 8, 32), result.First().End); var content = result.First().Content; Assert.IsNotNull(content); - var contentString = Encoding.UTF8.GetString(content); Assert.AreEqual(@"TITLE ""Test title cuesheet"" PERFORMER ""Test artist cuesheet"" FILE ""Test audiofile.mp3"" MP3 @@ -127,7 +126,7 @@ TRACK 02 AUDIO TITLE ""Test title 2"" PERFORMER ""Test artist 2"" INDEX 01 04:12:00 -", contentString); +", content); } [TestMethod] @@ -212,7 +211,6 @@ public void GenerateExportfiles_WithSections_ReturnsExportFiles() Assert.AreEqual(section3.End, result.Last().End); var content = result.Last().Content; Assert.IsNotNull(content); - var contentString = Encoding.UTF8.GetString(content); Assert.AreEqual(@"TITLE ""Test title cuesheet"" PERFORMER ""Test artist cuesheet"" FILE ""Test audiofile.mp3"" MP3 @@ -224,7 +222,7 @@ TRACK 02 AUDIO TITLE ""Test title 6"" PERFORMER ""Test artist 6"" INDEX 01 01:54:00 -", contentString); +", content); } [TestMethod] diff --git a/AudioCuesheetEditor.Tests/Services/IO/ExportfileGeneratorTests.cs b/AudioCuesheetEditor.Tests/Services/IO/ExportfileGeneratorTests.cs index e9c1339a..98246d33 100644 --- a/AudioCuesheetEditor.Tests/Services/IO/ExportfileGeneratorTests.cs +++ b/AudioCuesheetEditor.Tests/Services/IO/ExportfileGeneratorTests.cs @@ -83,12 +83,11 @@ public void GenerateExportFile_ShouldGenerateExportfile_WithoutSections() Assert.AreEqual(new TimeSpan(0, 8, 32), result.First().End); var content = result.First().Content; Assert.IsNotNull(content); - var contentString = System.Text.Encoding.UTF8.GetString(content); Assert.AreEqual(@"Test artist cuesheet - Test title cuesheet 1 Test artist 1 - Test title 1 2 Test artist 2 - Test title 2 -", contentString); +", content); } [TestMethod] @@ -173,12 +172,11 @@ public void GenerateExportFile_ShouldGenerateExportfiles_WithSections() Assert.AreEqual(section3.End, result.Last().End); var content = result.Last().Content; Assert.IsNotNull(content); - var contentString = System.Text.Encoding.UTF8.GetString(content); Assert.AreEqual(@"Test artist cuesheet - Test title cuesheet 1 Test artist 5 - Test title 5 2 Test artist 6 - Test title 6 -", contentString); +", content); } [TestMethod] diff --git a/AudioCuesheetEditor/Shared/Dialogs/GenerateCuesheetDialog.razor b/AudioCuesheetEditor/Shared/Dialogs/GenerateCuesheetDialog.razor index be6613a6..bf701eee 100644 --- a/AudioCuesheetEditor/Shared/Dialogs/GenerateCuesheetDialog.razor +++ b/AudioCuesheetEditor/Shared/Dialogs/GenerateCuesheetDialog.razor @@ -48,7 +48,7 @@ along with Foobar. If not, see @context.End @{ var ariaLabel = $"Download-{context.Name}"; - + }
diff --git a/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.razor b/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.razor index 114bbcbe..8e82bef7 100644 --- a/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.razor +++ b/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.razor @@ -25,8 +25,8 @@ along with Foobar. If not, see - @_localizer["Copy"] - + @_localizer["Copy"] + From da6f2280a3289b788f317be06c84b852e33c0b00 Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Fri, 6 Mar 2026 14:05:49 +0100 Subject: [PATCH 44/48] Update AppBar.razor --- AudioCuesheetEditor/Shared/AppBar.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AudioCuesheetEditor/Shared/AppBar.razor b/AudioCuesheetEditor/Shared/AppBar.razor index 2a5d3638..d4f89c8c 100644 --- a/AudioCuesheetEditor/Shared/AppBar.razor +++ b/AudioCuesheetEditor/Shared/AppBar.razor @@ -46,7 +46,7 @@ along with Foobar. If not, see @if (DisplayFileMenu) { - + From 943431c131acc520ebacc27dbc2ad100f642ef0f Mon Sep 17 00:00:00 2001 From: Sven <40752681+NeoCoderMatrix86@users.noreply.github.com> Date: Mon, 9 Mar 2026 16:41:22 +0100 Subject: [PATCH 45/48] fix upload dialog --- AudioCuesheetEditor/Model/IO/FileUpload.cs | 25 +++++ .../Services/IO/FileInputManager.cs | 93 ++++++++++++------- .../Services/IO/IFileInputManager.cs | 28 ++++-- .../Services/IO/ImportManager.cs | 53 ++++------- AudioCuesheetEditor/Shared/AppBar.razor | 4 +- .../Shared/Cuesheet/CuesheetData.razor | 29 ++++-- .../Shared/Cuesheet/EditSections.razor | 2 +- .../Shared/Inputs/FileDropOverlay.razor | 8 +- .../Shared/Inputs/TextField.razor | 5 +- 9 files changed, 157 insertions(+), 90 deletions(-) create mode 100644 AudioCuesheetEditor/Model/IO/FileUpload.cs diff --git a/AudioCuesheetEditor/Model/IO/FileUpload.cs b/AudioCuesheetEditor/Model/IO/FileUpload.cs new file mode 100644 index 00000000..a8df1ee7 --- /dev/null +++ b/AudioCuesheetEditor/Model/IO/FileUpload.cs @@ -0,0 +1,25 @@ +//This file is part of AudioCuesheetEditor. + +//AudioCuesheetEditor is free software: you can redistribute it and/or modify +//it under the terms of the GNU General Public License as published by +//the Free Software Foundation, either version 3 of the License, or +//(at your option) any later version. + +//AudioCuesheetEditor is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//GNU General Public License for more details. + +//You should have received a copy of the GNU General Public License +//along with Foobar. If not, see +//. +namespace AudioCuesheetEditor.Model.IO +{ + public class FileUpload(string name, string contentType, string? content = null, string? objectUrl = null) + { + public string Name { get; set; } = name; + public string ContentType { get; set; } = contentType; + public string? Content { get; set; } = content; + public string? ObjectUrl { get; set; } = objectUrl; + } +} diff --git a/AudioCuesheetEditor/Services/IO/FileInputManager.cs b/AudioCuesheetEditor/Services/IO/FileInputManager.cs index a47eb4f5..6bbabfae 100644 --- a/AudioCuesheetEditor/Services/IO/FileInputManager.cs +++ b/AudioCuesheetEditor/Services/IO/FileInputManager.cs @@ -28,12 +28,12 @@ public class FileInputManager(IJSRuntime jsRuntime, HttpClient httpClient, ILogg private readonly HttpClient _httpClient = httpClient; private readonly ILogger _logger = logger; - public AudioCodec? GetAudioCodec(IBrowserFile browserFile) + public AudioCodec? GetAudioCodec(string? fileContentType, string fileName) { AudioCodec? foundAudioCodec = null; - var extension = Path.GetExtension(browserFile.Name); + var extension = Path.GetExtension(fileName); // First search with mime type and file extension - var audioCodecsFound = Audiofile.AudioCodecs.Where(x => x.MimeType.Equals(browserFile.ContentType, StringComparison.OrdinalIgnoreCase) && x.FileExtension.Equals(extension, StringComparison.OrdinalIgnoreCase)); + var audioCodecsFound = Audiofile.AudioCodecs.Where(x => x.MimeType.Equals(fileContentType, StringComparison.OrdinalIgnoreCase) && x.FileExtension.Equals(extension, StringComparison.OrdinalIgnoreCase)); if (audioCodecsFound.Count() <= 1) { foundAudioCodec = audioCodecsFound.FirstOrDefault(); @@ -41,63 +41,62 @@ public class FileInputManager(IJSRuntime jsRuntime, HttpClient httpClient, ILogg if (foundAudioCodec == null) { // Second search with mime type or file extension - audioCodecsFound = Audiofile.AudioCodecs.Where(x => x.MimeType.Equals(browserFile.ContentType, StringComparison.OrdinalIgnoreCase) || x.FileExtension.Equals(extension, StringComparison.OrdinalIgnoreCase)); + audioCodecsFound = Audiofile.AudioCodecs.Where(x => x.MimeType.Equals(fileContentType, StringComparison.OrdinalIgnoreCase) || x.FileExtension.Equals(extension, StringComparison.OrdinalIgnoreCase)); foundAudioCodec = audioCodecsFound.FirstOrDefault(); } return foundAudioCodec; } - public bool IsValidAudiofile(IBrowserFile browserFile) + public bool IsValidAudiofile(string? fileContentType, string fileName) { - var codec = GetAudioCodec(browserFile); - return codec != null; + return GetAudioCodec(fileContentType, fileName) != null; } - public Boolean CheckFileMimeType(IBrowserFile file, String mimeType, IEnumerable fileExtensions) + public bool CheckFileMimeType(string? fileContentType, string fileName, string mimeType, IEnumerable fileExtensions) { + //TODO: Tests if (_logger.IsEnabled(LogLevel.Debug)) { - _logger.LogDebug("CheckFileMimeType called with file: file.Name: '{FileName}', file.ContentType: '{ContentType}', mimeType: '{MimeType}', fileExtensions: '{fileExtensions}'", file.Name, file.ContentType, mimeType, fileExtensions); + _logger.LogDebug("CheckFileMimeType called with file: file.Name: '{FileName}', file.ContentType: '{ContentType}', mimeType: '{MimeType}', fileExtensions: '{fileExtensions}'", fileName, fileContentType, mimeType, fileExtensions); } Boolean fileMimeTypeMatches = false; - if ((file != null) && (String.IsNullOrEmpty(mimeType) == false)) + if (String.IsNullOrEmpty(mimeType) == false) { - if (String.IsNullOrEmpty(file.ContentType) == false) + if (String.IsNullOrEmpty(fileContentType) == false) { if (mimeType.EndsWith("/*")) { var mainType = mimeType[..^1]; - fileMimeTypeMatches = file.ContentType.StartsWith(mainType, StringComparison.CurrentCultureIgnoreCase); + fileMimeTypeMatches = fileContentType.StartsWith(mainType, StringComparison.CurrentCultureIgnoreCase); } else { - fileMimeTypeMatches = file.ContentType.Equals(mimeType, StringComparison.CurrentCultureIgnoreCase); + fileMimeTypeMatches = fileContentType.Equals(mimeType, StringComparison.CurrentCultureIgnoreCase); } } if ((fileMimeTypeMatches == false) && (fileExtensions.Any())) { //Try to find by file extension - var extension = Path.GetExtension(file.Name); + var extension = Path.GetExtension(fileName); fileMimeTypeMatches = fileExtensions.Any(x => x.Equals(extension, StringComparison.CurrentCultureIgnoreCase)); } } return fileMimeTypeMatches; } - public async Task CreateAudiofileAsync(String? fileInputId, IBrowserFile? browserFile, Action>? afterContentStreamLoaded = null) + public async Task CreateAudiofileAsync(FileUpload fileUpload, Action>? afterContentStreamLoaded = null) { Audiofile? audiofile = null; - if ((String.IsNullOrEmpty(fileInputId) == false) && (browserFile != null)) + if (fileUpload.ObjectUrl != null) { // Check file mime type - var codec = GetAudioCodec(browserFile); + var codec = GetAudioCodec(fileUpload.ContentType, fileUpload.Name); if (codec != null) { - var audioFileObjectURL = await _jsRuntime.InvokeAsync("getObjectURLFromMudFileUpload", fileInputId); - audiofile = new Audiofile(browserFile.Name, audioFileObjectURL, codec); - if (String.IsNullOrEmpty(audioFileObjectURL) == false) + audiofile = new Audiofile(fileUpload.Name, fileUpload.ObjectUrl, codec); + if (String.IsNullOrEmpty(fileUpload.ObjectUrl) == false) { - var request = new HttpRequestMessage(HttpMethod.Get, audioFileObjectURL); + var request = new HttpRequestMessage(HttpMethod.Get, fileUpload.ObjectUrl); //TODO: Enable when https://github.com/NeoCoderMatrix86/AudioCuesheetEditor/issues/524 gets done request.SetBrowserRequestStreamingEnabled(false); @@ -119,27 +118,25 @@ public Boolean CheckFileMimeType(IBrowserFile file, String mimeType, IEnumerable return audiofile; } - public CDTextfile? CreateCDTextfile(IBrowserFile? browserFile) + public CDTextfile? CreateCDTextfile(string? fileContentType, string fileName) { - CDTextfile? cdTextfile = null; - if (browserFile != null) + CDTextfile? cdTextfile; + if (CheckFileMimeType(fileContentType, fileName, FileMimeTypes.Text, [FileExtensions.CDTextfile])) { - if (CheckFileMimeType(browserFile, FileMimeTypes.Text, [FileExtensions.CDTextfile])) - { - cdTextfile = new CDTextfile(browserFile.Name); - } - else - { - throw new ArgumentException("The cdtextfile provided is not of a valid type."); - } + cdTextfile = new CDTextfile(fileName); + } + else + { + throw new ArgumentException("The cdtextfile provided is not of a valid type."); } return cdTextfile; } /// - public bool IsValidForImportView(IBrowserFile browserFile) + public bool IsValidForImportView(string? fileContentType, string fileName) { - return CheckFileMimeType(browserFile, FileMimeTypes.Text, [FileExtensions.Text, FileExtensions.HTML]); + //TODO: Tests + return CheckFileMimeType(fileContentType, fileName, FileMimeTypes.Text, [FileExtensions.Text, FileExtensions.HTML]); } /// @@ -148,5 +145,33 @@ public async Task ReadFileContentAsync(IBrowserFile browserFile) var fileContent = new StreamContent(browserFile.OpenReadStream()); return await fileContent.ReadAsStringAsync(); } + + /// + public async Task> CreateFileUploadsAsync(IReadOnlyList browserFiles, string? fileInputId = null) + { + //TODO: Tests + List fileUploads = []; + foreach (var file in browserFiles) + { + 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)) + { + string? content = null; + string? objectUrl = null; + if (IsValidAudiofile(file.ContentType, file.Name)) + { + objectUrl = await _jsRuntime.InvokeAsync("getObjectURLFromMudFileUpload", fileInputId); + } + else + { + content = await ReadFileContentAsync(file); + } + fileUploads.Add(new(file.Name, file.ContentType, content, objectUrl)); + } + } + return fileUploads; + } } } diff --git a/AudioCuesheetEditor/Services/IO/IFileInputManager.cs b/AudioCuesheetEditor/Services/IO/IFileInputManager.cs index e5d00d13..026ed5d4 100644 --- a/AudioCuesheetEditor/Services/IO/IFileInputManager.cs +++ b/AudioCuesheetEditor/Services/IO/IFileInputManager.cs @@ -15,6 +15,7 @@ //. using AudioCuesheetEditor.Model.AudioCuesheet; +using AudioCuesheetEditor.Model.IO; using AudioCuesheetEditor.Model.IO.Audio; using Microsoft.AspNetCore.Components.Forms; @@ -22,22 +23,37 @@ namespace AudioCuesheetEditor.Services.IO { public interface IFileInputManager { - bool IsValidAudiofile(IBrowserFile browserFile); - AudioCodec? GetAudioCodec(IBrowserFile browserFile); - bool CheckFileMimeType(IBrowserFile file, string mimeType, IEnumerable fileExtensions); - Task CreateAudiofileAsync(string? fileInputId, IBrowserFile? browserFile, Action>? afterContentStreamLoaded = null); - CDTextfile? CreateCDTextfile(IBrowserFile? browserFile); + bool IsValidAudiofile(string? fileContentType, string fileName); + AudioCodec? GetAudioCodec(string? fileContentType, string fileName); + /// + /// Checks if a file content type and name matches given parameters + /// + /// + /// + /// + /// + /// + bool CheckFileMimeType(string? fileContentType, string fileName, string mimeType, IEnumerable fileExtensions); + Task CreateAudiofileAsync(FileUpload fileUpload, Action>? afterContentStreamLoaded = null); + CDTextfile? CreateCDTextfile(string? fileContentType, string fileName); /// /// Checks if the file can be used for the import view /// /// /// - bool IsValidForImportView(IBrowserFile browserFile); + bool IsValidForImportView(string? fileContentType, string fileName); /// /// Reads the browser file and gets the file content as string /// /// /// Task ReadFileContentAsync(IBrowserFile browserFile); + /// + /// Generates file upload references for files + /// + /// + /// + /// + Task> CreateFileUploadsAsync(IReadOnlyList browserFiles, string? fileInputId = null); } } \ No newline at end of file diff --git a/AudioCuesheetEditor/Services/IO/ImportManager.cs b/AudioCuesheetEditor/Services/IO/ImportManager.cs index 15a32fb3..6817670f 100644 --- a/AudioCuesheetEditor/Services/IO/ImportManager.cs +++ b/AudioCuesheetEditor/Services/IO/ImportManager.cs @@ -121,59 +121,47 @@ public void ImportCuesheet() } } - public async Task UploadFilesAsync(IEnumerable files, String? fileInputId = null) + public async Task UploadFilesAsync(IEnumerable files) { var stopwatch = Stopwatch.StartNew(); var invalidFiles = new List(); foreach (var file in files) { - if (_fileInputManager.CheckFileMimeType(file, FileMimeTypes.Projectfile, [FileExtensions.Projectfile]) - || _fileInputManager.CheckFileMimeType(file, FileMimeTypes.Cuesheet, [FileExtensions.Cuesheet]) - || _fileInputManager.IsValidForImportView(file) - || _fileInputManager.IsValidAudiofile(file)) + 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)) { - if (_fileInputManager.CheckFileMimeType(file, FileMimeTypes.Projectfile, [FileExtensions.Projectfile])) + if (_fileInputManager.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Projectfile, [FileExtensions.Projectfile])) { - var fileContent = await ReadFileContentAsync(file); - fileContent.Position = 0; - using var reader = new StreamReader(fileContent); - var stringFileContent = reader.ReadToEnd(); _sessionStateContainer.Importfile = new Importfile() { - FileContent = stringFileContent, - FileContentRecognized = stringFileContent, + FileContent = file.Content, + FileContentRecognized = file.Content, FileType = ImportFileType.ProjectFile }; } - if (_fileInputManager.CheckFileMimeType(file, FileMimeTypes.Cuesheet, [FileExtensions.Cuesheet])) + if (_fileInputManager.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Cuesheet, [FileExtensions.Cuesheet])) { - var fileContent = await ReadFileContentAsync(file); - fileContent.Position = 0; - using var reader = new StreamReader(fileContent); - var stringFileContent = reader.ReadToEnd(); _sessionStateContainer.Importfile = new Importfile() { - FileContent = stringFileContent, - FileContentRecognized = stringFileContent, + FileContent = file.Content, + FileContentRecognized = file.Content, FileType = ImportFileType.Cuesheet }; } - if (_fileInputManager.IsValidForImportView(file)) + if (_fileInputManager.IsValidForImportView(file.ContentType, file.Name)) { - var fileContent = await ReadFileContentAsync(file); - fileContent.Position = 0; - using var reader = new StreamReader(fileContent); - var stringFileContent = reader.ReadToEnd(); _sessionStateContainer.Importfile = new Importfile() { - FileContent = stringFileContent, - FileContentRecognized = stringFileContent, + FileContent = file.Content, + FileContentRecognized = file.Content, FileType = ImportFileType.Textfile }; } - if (_fileInputManager.IsValidAudiofile(file)) + if (_fileInputManager.IsValidAudiofile(file.ContentType, file.Name)) { - var audioFile = await _fileInputManager.CreateAudiofileAsync(fileInputId, file); + var audioFile = await _fileInputManager.CreateAudiofileAsync(file); _sessionStateContainer.ImportAudiofile = audioFile; } } @@ -190,15 +178,6 @@ public async Task UploadFilesAsync(IEnumerable files, String? file } } - private static async Task ReadFileContentAsync(IBrowserFile file) - { - var fileContent = new MemoryStream(); - var stream = file.OpenReadStream(); - await stream.CopyToAsync(fileContent); - stream.Close(); - return fileContent; - } - private static void CopyCuesheet(Cuesheet target, ICuesheet cuesheetToCopy) { target.IsImporting = true; diff --git a/AudioCuesheetEditor/Shared/AppBar.razor b/AudioCuesheetEditor/Shared/AppBar.razor index d4f89c8c..395d5e3d 100644 --- a/AudioCuesheetEditor/Shared/AppBar.razor +++ b/AudioCuesheetEditor/Shared/AppBar.razor @@ -25,6 +25,7 @@ along with Foobar. If not, see @inject IJSRuntime _jsRuntime @inject HotKeys _hotKeys @inject ImportManager _importManager +@inject IFileInputManager _fileInputManager @@ -229,7 +230,8 @@ along with Foobar. If not, see async Task FileUploaded(IBrowserFile file) { - await _importManager.UploadFilesAsync([file]); + var fileUploads = await _fileInputManager.CreateFileUploadsAsync([file]); + await _importManager.UploadFilesAsync(fileUploads); } async Task ShowHotkeysDialog() diff --git a/AudioCuesheetEditor/Shared/Cuesheet/CuesheetData.razor b/AudioCuesheetEditor/Shared/Cuesheet/CuesheetData.razor index 4d44b0a3..61605a5a 100644 --- a/AudioCuesheetEditor/Shared/Cuesheet/CuesheetData.razor +++ b/AudioCuesheetEditor/Shared/Cuesheet/CuesheetData.razor @@ -111,14 +111,22 @@ along with Foobar. If not, see fileInputAudiofileErrorText = null; try { - Cuesheet.Audiofile = await _fileInputManager.CreateAudiofileAsync(fileInputAudiofileId, browserFile, x => + if (browserFile != null) { - if (Cuesheet.RecalculateLastTrackEnd()) + var fileUpload = await _fileInputManager.CreateFileUploadsAsync([browserFile], fileInputAudiofileId); + Cuesheet.Audiofile = await _fileInputManager.CreateAudiofileAsync(fileUpload.Single(), x => { - TraceChangeManager.MergeLastEditWithEdit(x => x.Changes.All(y => y.TraceableObject == Cuesheet && y.TraceableChange.PropertyName == nameof(Audiofile))); - } - StateHasChanged(); - }); + if (Cuesheet.RecalculateLastTrackEnd()) + { + TraceChangeManager.MergeLastEditWithEdit(x => x.Changes.All(y => y.TraceableObject == Cuesheet && y.TraceableChange.PropertyName == nameof(Audiofile))); + } + StateHasChanged(); + }); + } + else + { + Cuesheet.Audiofile = null; + } } catch(ArgumentException ae) { @@ -157,7 +165,14 @@ along with Foobar. If not, see fileInputCDTextfileErrorText = null; try { - Cuesheet.CDTextfile = _fileInputManager.CreateCDTextfile(browserFile); + if (browserFile != null) + { + Cuesheet.CDTextfile = _fileInputManager.CreateCDTextfile(browserFile.ContentType, browserFile.Name); + } + else + { + Cuesheet.CDTextfile = null; + } } catch (ArgumentException ae) { diff --git a/AudioCuesheetEditor/Shared/Cuesheet/EditSections.razor b/AudioCuesheetEditor/Shared/Cuesheet/EditSections.razor index d1abd5cb..2c998de5 100644 --- a/AudioCuesheetEditor/Shared/Cuesheet/EditSections.razor +++ b/AudioCuesheetEditor/Shared/Cuesheet/EditSections.razor @@ -142,7 +142,7 @@ along with Foobar. If not, see void AudiofileSelected(CuesheetSection section, IBrowserFile? browserFile) { - if ((browserFile != null) && (_fileInputManager.IsValidAudiofile(browserFile) == true)) + if ((browserFile != null) && (_fileInputManager.IsValidAudiofile(browserFile.ContentType, browserFile.Name) == true)) { section.AudiofileName = browserFile?.Name; } diff --git a/AudioCuesheetEditor/Shared/Inputs/FileDropOverlay.razor b/AudioCuesheetEditor/Shared/Inputs/FileDropOverlay.razor index 2f22ee2f..deb044b1 100644 --- a/AudioCuesheetEditor/Shared/Inputs/FileDropOverlay.razor +++ b/AudioCuesheetEditor/Shared/Inputs/FileDropOverlay.razor @@ -23,6 +23,7 @@ along with Foobar. If not, see @inject IStringLocalizer _localizer @inject ImportManager _importManager @inject DialogManager _dialogManager +@inject IFileInputManager _fileInputManager