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 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 6453cf79..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 @@ -16,8 +16,8 @@ - - + + 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.End2EndTests/Models/AppBar.cs b/AudioCuesheetEditor.End2EndTests/Models/AppBar.cs index 6129ffd9..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(); } @@ -48,6 +42,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() @@ -81,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/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 85555e5b..e2cb6937 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"); @@ -83,9 +83,9 @@ 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(); + 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/Desktop/ImportTest.cs b/AudioCuesheetEditor.End2EndTests/Tests/Desktop/ImportTest.cs index 5554c20b..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"); @@ -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/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 385e3aa3..61cdc3fd 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"); @@ -83,9 +83,9 @@ 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(); + 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/ImportTestSmartphone.cs b/AudioCuesheetEditor.End2EndTests/Tests/Smartphone/ImportTestSmartphone.cs index 5b538dd2..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"); @@ -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.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]); diff --git a/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj b/AudioCuesheetEditor.Tests/AudioCuesheetEditor.Tests.csproj index 39ad8f63..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 @@ -12,11 +12,11 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/AudioCuesheetEditor.Tests/Services/IO/CuesheetExportServiceTests.cs b/AudioCuesheetEditor.Tests/Services/IO/CuesheetExportServiceTests.cs index f103792d..bfa05bc5 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] @@ -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.Tests/Services/IO/FileInputManagerTests.cs b/AudioCuesheetEditor.Tests/Services/IO/FileInputManagerTests.cs index f78b881c..4e4de503 100644 --- a/AudioCuesheetEditor.Tests/Services/IO/FileInputManagerTests.cs +++ b/AudioCuesheetEditor.Tests/Services/IO/FileInputManagerTests.cs @@ -20,7 +20,13 @@ using Microsoft.JSInterop; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Net.Http; +using System.Text; +using System.Threading.Tasks; namespace AudioCuesheetEditor.Tests.Services.IO { @@ -34,11 +40,12 @@ public void CheckFileMimeType_ReturnsTrue_WhenContentTypeDoesNotMatchButExtensio var jsRuntimeMock = new Mock(); var httpClientMock = new Mock(); var loggerMock = new Mock>(); - var file = CreateBrowserFile("test.mp3", "audio/wav"); + var fileName = "test.mp3"; + var contentType = "audio/wav"; var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); // Act - var result = manager.CheckFileMimeType(file, "audio/mpeg", [".mp3"]); + var result = manager.CheckFileMimeType(contentType, fileName, "audio/mpeg", [".mp3"]); // Assert Assert.IsTrue(result); @@ -51,11 +58,12 @@ public void CheckFileMimeType_ReturnsTrue_WhenContentTypeDoesMatchButNotExtensio var jsRuntimeMock = new Mock(); var httpClientMock = new Mock(); var loggerMock = new Mock>(); - var file = CreateBrowserFile("test.mpeg", "audio/mpeg"); + var fileName = "test.mpeg"; + var contentType = "audio/mpeg"; var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); // Act - var result = manager.CheckFileMimeType(file, "audio/mpeg", [".mp3", ".txt"]); + var result = manager.CheckFileMimeType(contentType, fileName, "audio/mpeg", [".mp3", ".txt"]); // Assert Assert.IsTrue(result); @@ -68,11 +76,12 @@ public void CheckFileMimeType_ReturnsFalse_WhenExtensionDoesNotMatchAndContentTy var jsRuntimeMock = new Mock(); var httpClientMock = new Mock(); var loggerMock = new Mock>(); - var file = CreateBrowserFile("test.flac", string.Empty); + var fileName = "test.flac"; + var contentType = string.Empty; var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); // Act - var result = manager.CheckFileMimeType(file, "audio/flac", [".mp3"]); + var result = manager.CheckFileMimeType(contentType, fileName, "audio/flac", [".mp3"]); // Assert Assert.IsFalse(result); @@ -85,11 +94,12 @@ public void CheckFileMimeType_ReturnsTrue_WhenContentTypeAndExtensionMatch() var jsRuntimeMock = new Mock(); var httpClientMock = new Mock(); var loggerMock = new Mock>(); - var file = CreateBrowserFile("test.wav", "audio/wave"); + var fileName = "test.wav"; + var contentType = "audio/wave"; var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); // Act - var result = manager.CheckFileMimeType(file, "audio/wave", [".wav"]); + var result = manager.CheckFileMimeType(contentType, fileName, "audio/wave", [".wav"]); // Assert Assert.IsTrue(result); @@ -102,11 +112,12 @@ public void CheckFileMimeType_ReturnsTrue_WhenContentMainTypeMatch() var jsRuntimeMock = new Mock(); var httpClientMock = new Mock(); var loggerMock = new Mock>(); - var file = CreateBrowserFile("history.txt", "text/plain"); + var fileName = "history.txt"; + var contentType = "text/plain"; var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); // Act - var result = manager.CheckFileMimeType(file, "text/*", [".txt", ".text"]); + var result = manager.CheckFileMimeType(contentType, fileName, "text/*", [".txt", ".text"]); // Assert Assert.IsTrue(result); @@ -119,11 +130,12 @@ public void IsValidAudiofile_ReturnsTrue_WithValidAudiocodec() var jsRuntimeMock = new Mock(); var httpClientMock = new Mock(); var loggerMock = new Mock>(); - var file = CreateBrowserFile("test.wav", "audio/wav"); + var fileName = "test.wav"; + var contentType = "audio/wav"; var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); // Act - var result = manager.IsValidAudiofile(file); + var result = manager.IsValidAudiofile(contentType, fileName); // Assert Assert.IsTrue(result); @@ -136,11 +148,12 @@ public void IsValidAudiofile_ReturnsFalse_WithInvalidAudiocodecAndExtension() var jsRuntimeMock = new Mock(); var httpClientMock = new Mock(); var loggerMock = new Mock>(); - var file = CreateBrowserFile("test.mock", "just a fantasy"); + var fileName = "test.mock"; + var contentType = "just a fantasy"; var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); // Act - var result = manager.IsValidAudiofile(file); + var result = manager.IsValidAudiofile(contentType, fileName); // Assert Assert.IsFalse(result); @@ -153,11 +166,12 @@ public void GetAudioCodec_ReturnsAudiocodec_WhenContentTypeMatches() var jsRuntimeMock = new Mock(); var httpClientMock = new Mock(); var loggerMock = new Mock>(); - var file = CreateBrowserFile("test.wbem", "audio/webm"); + var fileName = "test.wbem"; + var contentType = "audio/webm"; var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); // Act - var result = manager.GetAudioCodec(file); + var result = manager.GetAudioCodec(contentType, fileName); // Assert Assert.IsNotNull(result); @@ -171,11 +185,12 @@ public void GetAudioCodec_ReturnsAudiocodec_WhenContentTypeAndFileExtensionMatch var jsRuntimeMock = new Mock(); var httpClientMock = new Mock(); var loggerMock = new Mock>(); - var file = CreateBrowserFile("test.webm", "audio/webm"); + var fileName = "test.wbem"; + var contentType = "audio/webm"; var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); // Act - var result = manager.GetAudioCodec(file); + var result = manager.GetAudioCodec(contentType, fileName); // Assert Assert.IsNotNull(result); @@ -189,11 +204,12 @@ public void GetAudioCodec_ReturnsNull_WhenContentTypeAndFileExtensionNotMatch() var jsRuntimeMock = new Mock(); var httpClientMock = new Mock(); var loggerMock = new Mock>(); - var file = CreateBrowserFile("test.acx", "fantasy stuff"); + var fileName = "test.acx"; + var contentType = "fantasy stuff"; var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); // Act - var result = manager.GetAudioCodec(file); + var result = manager.GetAudioCodec(contentType, fileName); // Assert Assert.IsNull(result); @@ -206,11 +222,12 @@ public void IsValidForImportView_ReturnsTrue_WhenFileIsHtml() var jsRuntimeMock = new Mock(); var httpClientMock = new Mock(); var loggerMock = new Mock>(); - var file = CreateBrowserFile("test.html", "text/html"); + var fileName = "test.html"; + var contentType = "text/html"; var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); // Act - var result = manager.IsValidForImportView(file); + var result = manager.IsValidForImportView(contentType, fileName); // Assert Assert.IsTrue(result); @@ -223,21 +240,76 @@ public void IsValidForImportView_ReturnsFalse_WhenFileIsBinary() var jsRuntimeMock = new Mock(); var httpClientMock = new Mock(); var loggerMock = new Mock>(); - var file = CreateBrowserFile("test.dat", "application/octet-stream"); + var fileName = "test.dat"; + var contentType = "application/octet-stream"; var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); // Act - var result = manager.IsValidForImportView(file); + var result = manager.IsValidForImportView(contentType, fileName); // Assert Assert.IsFalse(result); } - private static IBrowserFile CreateBrowserFile(string name, string contentType) + [TestMethod] + public async Task CreateFileUploadsAsync_ReturnsFileUploads_WhenFileHasTextContentAsync() + { + // Arrange + var jsRuntimeMock = new Mock(); + var httpClientMock = new Mock(); + var loggerMock = new Mock>(); + var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); + var firstFile = CreateBrowserFile("Test.txt", "text/plain", "Just a test!"); + var secondFile = CreateBrowserFile("Test.mp3", "audio/mpeg"); + var fileInputId = nameof(CreateFileUploadsAsync_ReturnsFileUploads_WhenFileHasTextContentAsync); + IReadOnlyList browserfiles = [ + firstFile, + secondFile + ]; + var objectUrl = "Some object url!"; + jsRuntimeMock.Setup(js => js.InvokeAsync(It.IsAny(), It.IsAny())).ReturnsAsync(objectUrl); + // Act + var result = await manager.CreateFileUploadsAsync(browserfiles, fileInputId); + // Assert + Assert.AreEqual(2, result.Count()); + Assert.AreEqual(firstFile.Name, result.First().Name); + Assert.AreEqual(firstFile.ContentType, result.First().ContentType); + Assert.AreEqual("Just a test!", result.First().Content); + Assert.AreEqual(secondFile.Name, result.Last().Name); + Assert.AreEqual(secondFile.ContentType, result.Last().ContentType); + Assert.IsNull(result.Last().Content); + Assert.AreEqual(objectUrl, result.Last().ObjectUrl); + } + + [TestMethod] + public async Task CreateFileUploadsAsync_ReturnsEmpty_WhenFilesHaveInvalidMimeTypeAsync() + { + // Arrange + var jsRuntimeMock = new Mock(); + var httpClientMock = new Mock(); + var loggerMock = new Mock>(); + var manager = new FileInputManager(jsRuntimeMock.Object, httpClientMock.Object, loggerMock.Object); + var firstFile = CreateBrowserFile("Test.bin", "binary", "Just a test!"); + var secondFile = CreateBrowserFile("Test.bin", "octet/stream"); + IReadOnlyList browserfiles = [ + firstFile, + secondFile + ]; + // Act + var result = await manager.CreateFileUploadsAsync(browserfiles); + // Assert + Assert.AreEqual(0, result.Count()); + } + + private static IBrowserFile CreateBrowserFile(string name, string contentType, string? content = null) { var fileMock = new Mock(); fileMock.Setup(f => f.Name).Returns(name); fileMock.Setup(f => f.ContentType).Returns(contentType); + if (content != null) + { + fileMock.Setup(f => f.OpenReadStream()).Returns(new MemoryStream(Encoding.UTF8.GetBytes(content))); + } return fileMock.Object; } } diff --git a/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs b/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs index f5893914..5b6b2ec9 100644 --- a/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs +++ b/AudioCuesheetEditor.Tests/Services/IO/ImportManagerTests.cs @@ -17,11 +17,11 @@ 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; using AudioCuesheetEditor.Services.UI; -using AudioCuesheetEditor.Tests.Utility; using Microsoft.AspNetCore.Components.Forms; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -39,15 +39,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 +79,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 +121,93 @@ 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 _service.AnalyseImportfile(); + // Assert + Assert.AreEqual(importFile, sessionStateContainerImportfile); + 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 importManager.AnalyseImportfile(); + await _service.AnalyseImportfile(); // Assert - Assert.AreEqual(importfile, sessionStateContainer.Importfile); - Assert.IsNull(sessionStateContainer.ImportCuesheet); + 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.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); + 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] @@ -135,25 +215,21 @@ 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); + var file = new FileUpload("test.projectfile", "text/plain", fileContent); + _fileInputManagerMock.Setup(f => f.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Projectfile, It.IsAny>())).Returns(true); + _fileInputManagerMock.Setup(f => f.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Cuesheet, It.IsAny>())).Returns(false); + _fileInputManagerMock.Setup(f => f.IsValidForImportView(file.ContentType, file.Name)).Returns(false); - 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] @@ -161,25 +237,22 @@ 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); + var file = new FileUpload("test.cue", "text/plain", fileContent); - var loggerMock = new Mock>(); - var importManager = new ImportManager(sessionStateContainer, traceChangeManager, fileInputManagerMock.Object, textImportServiceMock.Object, loggerMock.Object); + _fileInputManagerMock.Setup(f => f.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Projectfile, It.IsAny>())).Returns(false); + _fileInputManagerMock.Setup(f => f.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Cuesheet, It.IsAny>())).Returns(true); + _fileInputManagerMock.Setup(f => f.IsValidForImportView(file.ContentType, file.Name)).Returns(false); + + 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] @@ -187,123 +260,104 @@ 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); + var file = new FileUpload("test.txt", "text/plain", fileContent); - var loggerMock = new Mock>(); - var importManager = new ImportManager(sessionStateContainer, traceChangeManager, fileInputManagerMock.Object, textImportServiceMock.Object, loggerMock.Object); + _fileInputManagerMock.Setup(f => f.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Projectfile, It.IsAny>())).Returns(false); + _fileInputManagerMock.Setup(f => f.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Cuesheet, It.IsAny>())).Returns(false); + _fileInputManagerMock.Setup(f => f.IsValidForImportView(file.ContentType, file.Name)).Returns(true); + + 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] 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)); + var file = new FileUpload("test.mp3", "audio/mpeg"); - var loggerMock = new Mock>(); - var importManager = new ImportManager(sessionStateContainer, traceChangeManager, fileInputManagerMock.Object, textImportServiceMock.Object, loggerMock.Object); + _fileInputManagerMock.Setup(f => f.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Projectfile, It.IsAny>())).Returns(false); + _fileInputManagerMock.Setup(f => f.CheckFileMimeType(file.ContentType, file.Name, FileMimeTypes.Cuesheet, It.IsAny>())).Returns(false); + _fileInputManagerMock.Setup(f => f.IsValidForImportView(file.ContentType, file.Name)).Returns(false); + _fileInputManagerMock.Setup(f => f.IsValidAudiofile(file.ContentType, file.Name)).Returns(true); + _fileInputManagerMock.Setup(f => f.CreateAudiofileAsync(It.IsAny(), It.IsAny>?>())).ReturnsAsync(new AudioCuesheetEditor.Model.IO.Audio.Audiofile(file.Name)); + + 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); - } - - private static IBrowserFile CreateBrowserFileMock(string name, string content = "TestContent") - { - var fileMock = new Mock(); - fileMock.Setup(f => f.Name).Returns(name); - fileMock.Setup(f => f.OpenReadStream(It.IsAny(), It.IsAny())) - .Returns(new MemoryStream(Encoding.UTF8.GetBytes(content))); - return fileMock.Object; + Assert.IsNotNull(sessionStateContainerImportfile); + Assert.AreEqual(importData, sessionStateContainerImportfile.FileContent); + Assert.AreEqual(importData, sessionStateContainerImportfile.FileContentRecognized); + Assert.AreEqual(ImportFileType.Textfile, sessionStateContainerImportfile.FileType); } } } \ No newline at end of file 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/AudioCuesheetEditor.csproj b/AudioCuesheetEditor/AudioCuesheetEditor.csproj index fa4071f0..9dd43125 100644 --- a/AudioCuesheetEditor/AudioCuesheetEditor.csproj +++ b/AudioCuesheetEditor/AudioCuesheetEditor.csproj @@ -1,13 +1,13 @@ - net9.0 + net10.0 true enable enable https://github.com/NeoCoderMatrix86/AudioCuesheetEditor 3.0 - 10.3.1 + 11.0.0 false true true @@ -21,17 +21,17 @@ - - - + + + - - - - - - + + + + + + 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); 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/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/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 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/Services/IO/FileInputManager.cs b/AudioCuesheetEditor/Services/IO/FileInputManager.cs index 78154ac4..61cef4db 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 @@ -27,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(); @@ -40,60 +41,66 @@ 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) { - _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}'", 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.Substring(0, mimeType.Length - 1); - fileMimeTypeMatches = file.ContentType.StartsWith(mainType, StringComparison.CurrentCultureIgnoreCase); + var mainType = mimeType[..^1]; + 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())) + 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 loadContentStreamTask = _httpClient.GetStreamAsync(audioFileObjectURL) + var request = new HttpRequestMessage(HttpMethod.Get, fileUpload.ObjectUrl); + //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) { @@ -110,27 +117,24 @@ 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]); + return CheckFileMimeType(fileContentType, fileName, FileMimeTypes.Text, [FileExtensions.Text, FileExtensions.HTML]); } /// @@ -139,5 +143,32 @@ 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) + { + 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 a1323d13..6817670f 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() @@ -87,13 +90,17 @@ public async Task AnalyseImportfile() break; case ImportFileType.Cuesheet: _traceChangeManager.BulkEdit = true; + _sessionStateContainer.Cuesheet = new(); CopyCuesheet(_sessionStateContainer.Cuesheet, _sessionStateContainer.Importfile.AnalyzedCuesheet); _traceChangeManager.BulkEdit = false; break; } } 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() @@ -108,62 +115,53 @@ 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) + 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; } } @@ -174,16 +172,10 @@ public async Task UploadFilesAsync(IEnumerable files, String? file } UploadFilesFinished?.Invoke(this, invalidFiles); stopwatch.Stop(); - _logger.LogDebug("UploadFilesAsync duration: {stopwatch.Elapsed}", stopwatch.Elapsed); - } - - private static async Task ReadFileContentAsync(IBrowserFile file) - { - var fileContent = new MemoryStream(); - var stream = file.OpenReadStream(); - await stream.CopyToAsync(fileContent); - stream.Close(); - return fileContent; + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug("UploadFilesAsync duration: {stopwatch.Elapsed}", stopwatch.Elapsed); + } } private static void CopyCuesheet(Cuesheet target, ICuesheet cuesheetToCopy) diff --git a/AudioCuesheetEditor/Services/UI/LocalizationService.cs b/AudioCuesheetEditor/Services/UI/LocalizationService.cs index c48c8588..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,35 +42,34 @@ public static IReadOnlyCollection AvailableCultures } } - public event EventHandler? LocalizationChanged; - public static CultureInfo SelectedCulture => CultureInfo.DefaultThreadCurrentUICulture ?? CultureInfo.CurrentUICulture; 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); - LocalizationChanged?.Invoke(this, new EventArgs()); + await _localStorageOptionsProvider.SaveOptionsValueAsync(x => x.CultureName!, culture.Name); + await _jsRuntime.InvokeVoidAsync("removeBeforeunload"); + _navigationManager.NavigateTo(_navigationManager.Uri, true); } } - 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; + CultureInfo.DefaultThreadCurrentCulture = newCulture; } - return newCulture != null; + return contains; } } } diff --git a/AudioCuesheetEditor/Shared/AppBar.razor b/AudioCuesheetEditor/Shared/AppBar.razor index d106a201..395d5e3d 100644 --- a/AudioCuesheetEditor/Shared/AppBar.razor +++ b/AudioCuesheetEditor/Shared/AppBar.razor @@ -25,9 +25,10 @@ along with Foobar. If not, see @inject IJSRuntime _jsRuntime @inject HotKeys _hotKeys @inject ImportManager _importManager +@inject IFileInputManager _fileInputManager - + AudioCuesheetEditor @@ -46,7 +47,10 @@ along with Foobar. If not, see @if (DisplayFileMenu) { - + + + + @_localizer["Open"] @@ -59,7 +63,7 @@ along with Foobar. If not, see @foreach (var culture in LocalizationService.AvailableCultures) { - @IsoCountryCodeToFlagEmoji(culture) @culture.DisplayName + @IsoCountryCodeToFlagEmoji(culture) @culture.DisplayName } @@ -69,7 +73,10 @@ along with Foobar. If not, see } @_localizer["Help"] @_localizer["About"] - @_localizer["Hotkeys"] + @if (DisplayHotkeys) + { + @_localizer["Hotkeys"] + } @_localizer["Preview environment"] @if (DisplayReset) { @@ -115,9 +122,12 @@ along with Foobar. If not, see [Parameter] public Boolean DisplayReset { get; set; } - async Task SelectedCultureChanged(string name) + [Parameter] + public Boolean DisplayHotkeys { get; set; } + + async Task SelectedCultureChanged(CultureInfo culture) { - await base.LocalizationService.ChangeLanguageAsync(name); + await base.LocalizationService.ChangeLanguageAsync(culture); } String GetStyle(CultureInfo cultureInfo) @@ -156,12 +166,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 +182,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) { @@ -188,6 +198,29 @@ along with Foobar. If not, see } async Task OpenFileClicked() + { + //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) { @@ -197,10 +230,11 @@ 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 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/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/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/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 9bf716f4..2c998de5 100644 --- a/AudioCuesheetEditor/Shared/Cuesheet/EditSections.razor +++ b/AudioCuesheetEditor/Shared/Cuesheet/EditSections.razor @@ -26,7 +26,7 @@ along with Foobar. If not, see - + @@ -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) { @@ -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/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 81% rename from AudioCuesheetEditor/Shared/Dialogs/ConfirmDialog.razor rename to AudioCuesheetEditor/Shared/Dialogs/ConfirmationDialog.razor index ac4a9859..98b7d01a 100644 --- a/AudioCuesheetEditor/Shared/Dialogs/ConfirmDialog.razor +++ b/AudioCuesheetEditor/Shared/Dialogs/ConfirmationDialog.razor @@ -17,15 +17,15 @@ along with Foobar. If not, see --> @inherits BaseLocalizedComponent -@inject IStringLocalizer _localizer +@inject IStringLocalizer _localizer @ConfirmText - @_localizer["Yes"] - @_localizer["No"] + @_localizer["Yes"] + @_localizer["No"] 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/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/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 }; } - 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/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 38adcd9d..c8d8da7f 100644 --- a/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.razor +++ b/AudioCuesheetEditor/Shared/Dialogs/GenerateExportDialog.razor @@ -22,13 +22,14 @@ along with Foobar. If not, see @inject ValidationService _validationService @inject ExportfileGenerator _exportfileGenerator @inject IBlazorDownloadFileService _blazorDownloadFileService +@inject IDialogService _dialogService -@if (exportOptions != null) +@if (_exportOptions != null) { - + @if (String.IsNullOrEmpty(GetGenerationValidationMessages()) == false) { @@ -36,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 } @@ -50,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] @@ -120,23 +121,42 @@ 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(); + + } + else + { + + + @_localizer["Name"] + @_localizer["Begin"] + @_localizer["End"] + @_localizer["Content"] + + + @context.Name + @context.Begin + @context.End + @{ + var ariaLabelDownload = $"Download-{context.Name}"; + var ariaLabelDialog = $"Dialog-{context.Name}"; + var dialogVisible = _displayContentDialogVisible.GetValueOrDefault(context); + + @_localizer["Display content"] + + + + + + + + + } + + + } @@ -144,10 +164,13 @@ along with Foobar. If not, see } @code { - 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) { @@ -159,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))); @@ -216,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/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/Export/DisplayExportfileContent.de.resx b/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.de.resx new file mode 100644 index 00000000..19803525 --- /dev/null +++ b/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.de.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + In Zwischenablage kopiert + + + 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..8e82bef7 --- /dev/null +++ b/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.razor @@ -0,0 +1,57 @@ + +@inherits BaseLocalizedComponent + +@inject IStringLocalizer _localizer +@inject IBlazorDownloadFileService _blazorDownloadFileService +@inject IJSRuntime _jsRuntime +@inject ISnackbar _snackbar + + + + + @_localizer["Copy"] + + + + +@code { + private int _lineCount; + + [Parameter] + [EditorRequired] + public Exportfile? Exportfile { get; set; } + + protected override void OnParametersSet() + { + base.OnParametersSet(); + _lineCount = Exportfile?.Content?.Split(Environment.NewLine).Length ?? 1; + } + + async Task CopyClipboardClicked() + { + 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 new file mode 100644 index 00000000..2af11de2 --- /dev/null +++ b/AudioCuesheetEditor/Shared/Export/DisplayExportfileContent.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Copied to clipboard + + + Copy + + \ No newline at end of file diff --git a/AudioCuesheetEditor/Shared/Import/Importprofiles.razor b/AudioCuesheetEditor/Shared/Import/Importprofiles.razor index 5933864f..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) @@ -165,11 +174,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/Inputs/FileDropOverlay.razor b/AudioCuesheetEditor/Shared/Inputs/FileDropOverlay.razor index f36353d9..deb044b1 100644 --- a/AudioCuesheetEditor/Shared/Inputs/FileDropOverlay.razor +++ b/AudioCuesheetEditor/Shared/Inputs/FileDropOverlay.razor @@ -23,17 +23,19 @@ along with Foobar. If not, see @inject IStringLocalizer _localizer @inject ImportManager _importManager @inject DialogManager _dialogManager +@inject IFileInputManager _fileInputManager - - @@ -41,10 +43,8 @@ along with Foobar. If not, see 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() { @@ -69,33 +69,29 @@ 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); - - ClearDragClass(); + var fileUploads = await _fileInputManager.CreateFileUploadsAsync(files, _fileUploadId); _showOverlay = false; + await InvokeAsync(StateHasChanged); + await Task.Yield(); + await _importManager.UploadFilesAsync(fileUploads); 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) diff --git a/AudioCuesheetEditor/Shared/Inputs/FileInput.razor b/AudioCuesheetEditor/Shared/Inputs/FileInput.razor index 91ebeb91..22398f4a 100644 --- a/AudioCuesheetEditor/Shared/Inputs/FileInput.razor +++ b/AudioCuesheetEditor/Shared/Inputs/FileInput.razor @@ -20,20 +20,21 @@ along with Foobar. If not, see @inject IStringLocalizer _localizer - @@ -71,25 +72,25 @@ along with Foobar. If not, see [Parameter] public Boolean DisplayMenu { get; set; } = true; - IBrowserFile? selectedFile; - MudFileUpload? fileUpload; - String TextFieldValue => String.IsNullOrEmpty(FileName) ? _localizer["No file selected"] : FileName; + IBrowserFile? _selectedFile; + MudFileUpload? _fileUpload; + String _textFieldValue => String.IsNullOrEmpty(FileName) ? _localizer["No file selected"] : FileName; async Task PickFilesClickedAsync() { - await (fileUpload?.OpenFilePickerAsync() ?? Task.CompletedTask); + await (_fileUpload?.OpenFilePickerAsync() ?? Task.CompletedTask); } async Task ClearClickedAsync() { - await (fileUpload?.ClearAsync() ?? Task.CompletedTask); + await (_fileUpload?.ClearAsync() ?? Task.CompletedTask); await SetSelectedFileAsync(null); } async Task SetSelectedFileAsync(IBrowserFile? browserFile) { - selectedFile = browserFile; - FileName = selectedFile?.Name; - await OnFileSelected.InvokeAsync(selectedFile); + _selectedFile = browserFile; + FileName = _selectedFile?.Name; + await OnFileSelected.InvokeAsync(_selectedFile); } } diff --git a/AudioCuesheetEditor/Shared/Inputs/TextField.razor b/AudioCuesheetEditor/Shared/Inputs/TextField.razor index 0dd4df3e..fea7e524 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"] + + @@ -43,9 +44,10 @@ along with Foobar. If not, see async Task FileUploaded(InputFileChangeEventArgs e) { - if (_fileInputManager.IsValidForImportView(e.File)) + var file = e.File; + if (_fileInputManager.IsValidForImportView(file.ContentType, file.Name)) { - var fileContent = await _fileInputManager.ReadFileContentAsync(e.File); + var fileContent = await _fileInputManager.ReadFileContentAsync(file); await TextChanged.InvokeAsync(fileContent); } } 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 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 }; } - diff --git a/AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor b/AudioCuesheetEditor/Shared/TrackList/ArtistColumn.razor index b82322b0..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 ccb84295..68b190ff 100644 --- a/AudioCuesheetEditor/Shared/TrackList/TitleColumn.razor +++ b/AudioCuesheetEditor/Shared/TrackList/TitleColumn.razor @@ -20,9 +20,9 @@ along with Foobar. If not, see @inject AutocompleteManager _autocompleteManager @inject ValidationService _validationService - + @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; } } diff --git a/AudioCuesheetEditor/Shared/TrackList/TrackList.razor b/AudioCuesheetEditor/Shared/TrackList/TrackList.razor index fe1c94a8..a445f42b 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) { @@ -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/_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 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 @@ 🗙
- + 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