diff --git a/DeepDrftManager/Components/Pages/Tracks/CmsCutBrowser.razor b/DeepDrftManager/Components/Pages/Tracks/CmsCutBrowser.razor new file mode 100644 index 0000000..f0b0a16 --- /dev/null +++ b/DeepDrftManager/Components/Pages/Tracks/CmsCutBrowser.razor @@ -0,0 +1,31 @@ +@inherits CmsMediumBrowserBase +@using DeepDrftModels.DTOs +@using DeepDrftModels.Enums + +@* Cut-filtered release grid for the Release Archive's CUTS tab (Phase 9 §8.A). Derived from the same + CmsMediumBrowserBase pattern the Session/Mix browsers use, so a fourth medium would follow the same + shape with no parallel path. Cuts carry no medium-specific row action (no hero, no waveform), so the + ActionContent slot renders nothing — every row still gets the shared Edit affordance from + CmsMediumTable. Embedded as tab content only; it has no standalone @page route. *@ + + + + + +@code { + protected override ReleaseMedium Medium => ReleaseMedium.Cut; + protected override string MediumNoun => "cuts"; + + protected override CutRow ToRow(ReleaseDto release) => new() { Release = release }; + + public sealed class CutRow + { + public required ReleaseDto Release { get; set; } + } +} diff --git a/DeepDrftManager/Components/Pages/Tracks/CmsMixBrowser.razor b/DeepDrftManager/Components/Pages/Tracks/CmsMixBrowser.razor index e39be57..6be116d 100644 --- a/DeepDrftManager/Components/Pages/Tracks/CmsMixBrowser.razor +++ b/DeepDrftManager/Components/Pages/Tracks/CmsMixBrowser.razor @@ -5,25 +5,58 @@ @attribute [Authorize] @inject ILogger Logger -Mixes — DeepDrft CMS +@* Embedded as the MIXES tab content of the Release Archive (Phase 9 §8.A), and still routable at + /tracks/mixes for direct-URL access. When embedded, the page chrome (title, container, the now- + meaningless "Back to Release Archive" button) is suppressed — the host tab strip owns that frame; only + the grid renders. The standalone route keeps the full page chrome. The per-row waveform affordance + (9.5.E) is preserved in both contexts. *@ +@if (Embedded) +{ + @GridContent +} +else +{ + Mixes — DeepDrft CMS - - - Back to Release Archive - + + + Back to Release Archive + - Mixes + Mixes - + @GridContent + +} + +@code { + /// + /// True when rendered as tab content inside the Release Archive; suppresses the standalone page + /// chrome (title, container, back button). False (default) renders the full routable page. + /// + [Parameter] public bool Embedded { get; set; } + + protected override ReleaseMedium Medium => ReleaseMedium.Mix; + protected override string MediumNoun => "mixes"; + + protected override MixRow ToRow(ReleaseDto release) => new() + { + Release = release, + HasWaveform = !string.IsNullOrEmpty(release.MixMetadata?.WaveformEntryKey) + }; + + // The grid itself — identical in the embedded and standalone contexts. Defined once as a fragment so + // both branches above render the same markup without duplication. + private RenderFragment GridContent => @ @if (row.HasWaveform) { @@ -53,18 +86,7 @@ } - - - -@code { - protected override ReleaseMedium Medium => ReleaseMedium.Mix; - protected override string MediumNoun => "mixes"; - - protected override MixRow ToRow(ReleaseDto release) => new() - { - Release = release, - HasWaveform = !string.IsNullOrEmpty(release.MixMetadata?.WaveformEntryKey) - }; + ; private async Task GenerateWaveformAsync(MixRow row) { diff --git a/DeepDrftManager/Components/Pages/Tracks/CmsSessionBrowser.razor b/DeepDrftManager/Components/Pages/Tracks/CmsSessionBrowser.razor index 3740495..a6bb8b0 100644 --- a/DeepDrftManager/Components/Pages/Tracks/CmsSessionBrowser.razor +++ b/DeepDrftManager/Components/Pages/Tracks/CmsSessionBrowser.razor @@ -6,25 +6,58 @@ @attribute [Authorize] @inject ILogger Logger -Sessions — DeepDrft CMS +@* Embedded as the SESSIONS tab content of the Release Archive (Phase 9 §8.A), and still routable at + /tracks/sessions for direct-URL access. When embedded, the page chrome (title, container, the now- + meaningless "Back to Release Archive" button) is suppressed — the host tab strip owns that frame; only + the grid renders. The standalone route keeps the full page chrome. The per-row hero affordance (9.5.E) + is preserved in both contexts. *@ +@if (Embedded) +{ + @GridContent +} +else +{ + Sessions — DeepDrft CMS - - - Back to Release Archive - + + + Back to Release Archive + - Sessions + Sessions - + @GridContent + +} + +@code { + /// + /// True when rendered as tab content inside the Release Archive; suppresses the standalone page + /// chrome (title, container, back button). False (default) renders the full routable page. + /// + [Parameter] public bool Embedded { get; set; } + + protected override ReleaseMedium Medium => ReleaseMedium.Session; + protected override string MediumNoun => "sessions"; + + protected override SessionRow ToRow(ReleaseDto release) => new() + { + Release = release, + HeroImageEntryKey = release.SessionMetadata?.HeroImageEntryKey + }; + + // The grid itself — identical in the embedded and standalone contexts. Defined once as a fragment so + // both branches above render the same markup without duplication. + private RenderFragment GridContent => @ @if (row.HeroImageEntryKey is { Length: > 0 } heroKey) { @@ -56,18 +89,7 @@ - - - -@code { - protected override ReleaseMedium Medium => ReleaseMedium.Session; - protected override string MediumNoun => "sessions"; - - protected override SessionRow ToRow(ReleaseDto release) => new() - { - Release = release, - HeroImageEntryKey = release.SessionMetadata?.HeroImageEntryKey - }; + ; private async Task UploadHeroAsync(SessionRow row, IBrowserFile? file) { diff --git a/DeepDrftManager/Components/Pages/Tracks/ReleaseArchiveBrowser.razor b/DeepDrftManager/Components/Pages/Tracks/ReleaseArchiveBrowser.razor deleted file mode 100644 index 0c169df..0000000 --- a/DeepDrftManager/Components/Pages/Tracks/ReleaseArchiveBrowser.razor +++ /dev/null @@ -1,37 +0,0 @@ -@using DeepDrftModels.Enums -@inject NavigationManager Navigation - -@* Release Archive: one card per ReleaseMedium, driven off Enum.GetValues + a display-metadata table. - No hardcoded three-arm switch in markup — adding a medium surfaces a new card automatically and only - needs one new entry in MediumCards below. Card idiom mirrors CmsGenreBrowser (MudCard + swatch). *@ - - @foreach (var medium in Enum.GetValues()) - { - var info = MediumCards[medium]; - - -
- - @info.Label - @info.Descriptor - -
-
- } -
- -@code { - private sealed record MediumCardInfo(string Label, string Descriptor, string SwatchModifier, string Route); - - // The one place medium → display + navigation target lives. A future medium adds one entry here; - // the markup above is untouched. The enum→record dictionary is a switch, data-structured (§3.1). - private static readonly IReadOnlyDictionary MediumCards = - new Dictionary - { - [ReleaseMedium.Cut] = new("Cuts", "Studio singles, EPs, and albums", "cut", "/tracks/albums"), - [ReleaseMedium.Session] = new("Sessions", "Single-track live recordings", "session", "/tracks/sessions"), - [ReleaseMedium.Mix] = new("Mixes", "Single-track DJ mixes", "mix", "/tracks/mixes"), - }; -} diff --git a/DeepDrftManager/Components/Pages/Tracks/ReleaseArchiveBrowser.razor.css b/DeepDrftManager/Components/Pages/Tracks/ReleaseArchiveBrowser.razor.css deleted file mode 100644 index f49db7a..0000000 --- a/DeepDrftManager/Components/Pages/Tracks/ReleaseArchiveBrowser.razor.css +++ /dev/null @@ -1,18 +0,0 @@ -.cms-medium-swatch { - width: 100%; - height: 80px; - background-color: var(--mud-palette-action-default-hover); - transition: background-color 0.2s ease; -} - -.cms-medium-swatch--cut { - background-color: var(--mud-palette-primary-hover); -} - -.cms-medium-swatch--session { - background-color: var(--mud-palette-secondary-hover); -} - -.cms-medium-swatch--mix { - background-color: var(--mud-palette-tertiary-hover); -} diff --git a/DeepDrftManager/Components/Pages/Tracks/TrackList.razor b/DeepDrftManager/Components/Pages/Tracks/TrackList.razor index 0bf6eb0..a962d17 100644 --- a/DeepDrftManager/Components/Pages/Tracks/TrackList.razor +++ b/DeepDrftManager/Components/Pages/Tracks/TrackList.razor @@ -3,6 +3,7 @@ @page "/tracks/genres" @page "/tracks/archive" @using DeepDrftManager.Services +@using DeepDrftModels.Enums @inject CmsTrackBrowserViewModel VM @inject ICmsTrackService CmsTrackService @inject ISnackbar Snackbar @@ -36,6 +37,9 @@ } + @* Top-level browse dimension. The former three-way toggle (Tracks / Releases / Release Archive) + collapsed to two (§8.A): "Releases" now hosts the in-page medium tab strip below, subsuming both + the old Releases grid (as the ALL tab) and the retired Release Archive cards. *@ Tracks Releases - Release Archive @if (VM.Mode == BrowseMode.Tracks) @@ -54,15 +57,21 @@ } else if (VM.Mode == BrowseMode.Albums) { - @* The all-releases grid is now a self-contained component (Phase 9 §8.B): it owns its own load - and refresh, so the host renders it with no parameters. The 8.A tab strip hosts this same - component as its ALL tab. Genre mode still uses the VM cache below; only album loading moved - into the component, so VM.Albums / VM.AlbumsLoading are no longer read here. *@ - - } - else if (VM.Mode == BrowseMode.Archive) - { - + @* The Release Archive tab strip (§8.A): an ALL tab plus one tab per ReleaseMedium, ALL left-most. + The medium tabs are enum-driven — a fourth medium adds a tab automatically; only a label-lookup + entry (MediumTabLabels) and a content arm (MediumGrid) are needed, no markup fork. Selecting a + tab swaps the grid below in place; no navigation to a separate page occurs. *@ + + + + + @foreach (var medium in Enum.GetValues()) + { + + @MediumGrid(medium) + + } + } else { @@ -78,6 +87,29 @@ @code { private CmsTrackGrid? _grid; + // Medium → tab label. The one place medium display text lives for the tab strip; a future medium adds + // one entry here and surfaces a tab automatically. Mirrors the extension discipline the retired + // ReleaseArchiveBrowser used for its cards. The ALL tab is rendered separately (it is not a medium). + private static readonly IReadOnlyDictionary MediumTabLabels = + new Dictionary + { + [ReleaseMedium.Cut] = "CUTS", + [ReleaseMedium.Session] = "SESSIONS", + [ReleaseMedium.Mix] = "MIXES", + }; + + // Medium → embedded grid. Each medium's grid is its own component (Cut has no per-row action; Session + // carries hero upload; Mix carries waveform generation), so the content dispatch is a per-medium + // mapping by nature — but it is a single switch returning a fragment, not a markup fork. The browsers + // render Embedded so their standalone page chrome (container, title, back button) is suppressed here. + private RenderFragment MediumGrid(ReleaseMedium medium) => medium switch + { + ReleaseMedium.Cut => @, + ReleaseMedium.Session => @, + ReleaseMedium.Mix => @, + _ => @No grid for this medium. + }; + // The all-releases grid refreshes its own list after a delete; this notification lets us invalidate // the VM's genre cache so genre counts reflect the deletion on the next switch into Genre mode. private void OnAlbumsChanged() @@ -93,10 +125,12 @@ protected override async Task OnInitializedAsync() { + // /tracks/archive and /tracks/albums both land on the Releases view (the tab strip); the old + // separate Archive mode is retired (§8.A) but the route stays reachable rather than 404ing. var uri = NavigationManager.Uri; var initial = uri.Contains("/tracks/albums", StringComparison.OrdinalIgnoreCase) ? BrowseMode.Albums - : uri.Contains("/tracks/archive", StringComparison.OrdinalIgnoreCase) ? BrowseMode.Archive + : uri.Contains("/tracks/archive", StringComparison.OrdinalIgnoreCase) ? BrowseMode.Albums : uri.Contains("/tracks/genres", StringComparison.OrdinalIgnoreCase) ? BrowseMode.Genres : BrowseMode.Tracks; await VM.SwitchModeAsync(initial); @@ -108,7 +142,6 @@ var path = mode switch { BrowseMode.Albums => "/tracks/albums", - BrowseMode.Archive => "/tracks/archive", BrowseMode.Genres => "/tracks/genres", _ => "/tracks" }; diff --git a/DeepDrftManager/Services/CmsTrackBrowserViewModel.cs b/DeepDrftManager/Services/CmsTrackBrowserViewModel.cs index d457982..d03e121 100644 --- a/DeepDrftManager/Services/CmsTrackBrowserViewModel.cs +++ b/DeepDrftManager/Services/CmsTrackBrowserViewModel.cs @@ -6,8 +6,10 @@ namespace DeepDrftManager.Services; public enum BrowseMode { Tracks, + + /// The release view — hosts the medium tab strip (ALL · CUTS · SESSIONS · MIXES, §8.A). Albums, - Archive, + Genres, }