Merge p9-w8-8c-medium-grid-parity into dev (8.C: per-medium tab grids to ALL-tab parity)
This commit is contained in:
@@ -65,6 +65,10 @@ else
|
|||||||
</MudTd>
|
</MudTd>
|
||||||
<MudTd DataLabel="Tracks">@context.TrackCount</MudTd>
|
<MudTd DataLabel="Tracks">@context.TrackCount</MudTd>
|
||||||
<MudTd DataLabel="Actions">
|
<MudTd DataLabel="Actions">
|
||||||
|
@* Medium-specific row action (Session hero, Mix waveform) when a host supplies one;
|
||||||
|
the ALL tab supplies none. Rendered before the shared edit/delete so the medium
|
||||||
|
affordance reads left-to-right ahead of the universal actions. *@
|
||||||
|
@RowActions?.Invoke(context.Release)
|
||||||
<MudTooltip Text="Batch Edit">
|
<MudTooltip Text="Batch Edit">
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Edit"
|
<MudIconButton Icon="@Icons.Material.Filled.Edit"
|
||||||
Size="Size.Small"
|
Size="Size.Small"
|
||||||
@@ -122,6 +126,13 @@ else
|
|||||||
[Parameter] public bool IsLoading { get; set; }
|
[Parameter] public bool IsLoading { get; set; }
|
||||||
[Parameter] public EventCallback OnReleasesChanged { get; set; }
|
[Parameter] public EventCallback OnReleasesChanged { get; set; }
|
||||||
|
|
||||||
|
// Optional per-row, medium-specific action slot (Session hero upload, Mix waveform generate),
|
||||||
|
// rendered in the Actions cell ahead of the shared edit/delete buttons. The ALL tab leaves it
|
||||||
|
// unset and renders the grid exactly as before. A per-medium host (CmsCut/Session/MixBrowser)
|
||||||
|
// supplies it so the rich grid filtered to one medium keeps that medium's bespoke affordance —
|
||||||
|
// the rich expand/delete/Type-chip/edit logic stays here, single-sourced, rather than forked.
|
||||||
|
[Parameter] public RenderFragment<ReleaseDto>? RowActions { get; set; }
|
||||||
|
|
||||||
private List<AlbumRow> _rows = new();
|
private List<AlbumRow> _rows = new();
|
||||||
|
|
||||||
// Tracks the Releases reference last projected into _rows. Guards against OnParametersSet
|
// Tracks the Releases reference last projected into _rows. Guards against OnParametersSet
|
||||||
|
|||||||
@@ -2,27 +2,20 @@
|
|||||||
@using DeepDrftModels.DTOs
|
@using DeepDrftModels.DTOs
|
||||||
@using DeepDrftModels.Enums
|
@using DeepDrftModels.Enums
|
||||||
|
|
||||||
@* Cut-filtered release grid for the Release Archive's CUTS tab (Phase 9 §8.A). Derived from the same
|
@* CUTS tab content (Phase 9 §8.A/§8.C): the rich CmsAlbumBrowser grid filtered to Cut releases, so the
|
||||||
CmsMediumBrowserBase pattern the Session/Mix browsers use, so a fourth medium would follow the same
|
tab carries expand-tracks, delete, the Type chip, and per-row edit identically to the ALL tab — no
|
||||||
shape with no parallel path. Cuts carry no medium-specific row action (no hero, no waveform), so the
|
forked grid. Cuts have no medium-specific row action, so no RowActions slot is supplied; the grid
|
||||||
ActionContent slot renders nothing — every row still gets the shared Edit affordance from
|
renders its shared edit/delete only. Embedded as tab content only; no standalone @page route. *@
|
||||||
CmsMediumTable. Embedded as tab content only; it has no standalone @page route. *@
|
<CmsAlbumBrowser Releases="Releases"
|
||||||
<CmsMediumTable TRow="CutRow"
|
IsLoading="Loading"
|
||||||
Rows="Rows"
|
OnReleasesChanged="ReloadAsync" />
|
||||||
Loading="Loading"
|
|
||||||
ReleaseAccessor="@(row => row.Release)"
|
|
||||||
ThumbUrl="@(key => ThumbUrl(key))"
|
|
||||||
TitleHeader="Cut"
|
|
||||||
EmptyMessage="No cuts found.">
|
|
||||||
<ActionContent Context="row">
|
|
||||||
</ActionContent>
|
|
||||||
</CmsMediumTable>
|
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
protected override ReleaseMedium Medium => ReleaseMedium.Cut;
|
protected override ReleaseMedium Medium => ReleaseMedium.Cut;
|
||||||
protected override string MediumNoun => "cuts";
|
protected override string MediumNoun => "cuts";
|
||||||
|
|
||||||
protected override CutRow ToRow(ReleaseDto release) => new() { Release = release };
|
protected override CutRow ToRow(ReleaseDto release) => new() { Release = release };
|
||||||
|
protected override ReleaseDto ReleaseOf(CutRow row) => row.Release;
|
||||||
|
|
||||||
public sealed class CutRow
|
public sealed class CutRow
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,14 +7,18 @@ using MudBlazor;
|
|||||||
namespace DeepDrftManager.Components.Pages.Tracks;
|
namespace DeepDrftManager.Components.Pages.Tracks;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Shared fetch + state logic for the single-track medium browsers (Sessions, Mixes). Analogous to the
|
/// Shared fetch + state logic for the per-medium browsers (Cuts, Sessions, Mixes). Each subclass feeds
|
||||||
/// public-site <c>MediumBrowseBase</c>: subclasses supply the <see cref="Medium"/>, the noun used in
|
/// the rich <c>CmsAlbumBrowser</c> grid a medium-filtered release list, so the per-medium tabs gain the
|
||||||
/// error text, and a per-row projection from <see cref="ReleaseDto"/> to their own row model; this base
|
/// same expand-tracks / delete / Type-chip / edit behaviour as the ALL tab without re-implementing any of
|
||||||
/// owns the loading flag, the row list, the initial load, and the cover-thumbnail URL helper. The shared
|
/// it (§8.C parity — reuse, don't fork). This base owns the loading flag, the medium-filtered load, the
|
||||||
/// table structure lives in <c>CmsMediumTable</c>; subclasses render it and fill only the action column.
|
/// per-release row projection, and a cover-thumbnail helper; subclasses supply the <see cref="Medium"/>,
|
||||||
|
/// an error noun, and their bespoke per-row action (Session hero upload, Mix waveform generate) via the
|
||||||
|
/// rich grid's <c>RowActions</c> slot, looking their action-state row up with <see cref="RowFor"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TRow">The subclass's row model wrapping a <see cref="ReleaseDto"/>.</typeparam>
|
/// <typeparam name="TRow">The subclass's row model wrapping a <see cref="ReleaseDto"/> plus its
|
||||||
public abstract class CmsMediumBrowserBase<TRow> : ComponentBase
|
/// medium-specific action state (upload/generate flags). The rich grid renders from the bare
|
||||||
|
/// <see cref="Releases"/> projection; <typeparamref name="TRow"/> only carries the action state.</typeparam>
|
||||||
|
public abstract class CmsMediumBrowserBase<TRow> : ComponentBase where TRow : class
|
||||||
{
|
{
|
||||||
[Inject] public required ICmsReleaseService CmsReleaseService { get; set; }
|
[Inject] public required ICmsReleaseService CmsReleaseService { get; set; }
|
||||||
[Inject] public required ISnackbar Snackbar { get; set; }
|
[Inject] public required ISnackbar Snackbar { get; set; }
|
||||||
@@ -28,11 +32,38 @@ public abstract class CmsMediumBrowserBase<TRow> : ComponentBase
|
|||||||
/// <summary>Projects a fetched release into the subclass's row model.</summary>
|
/// <summary>Projects a fetched release into the subclass's row model.</summary>
|
||||||
protected abstract TRow ToRow(ReleaseDto release);
|
protected abstract TRow ToRow(ReleaseDto release);
|
||||||
|
|
||||||
|
/// <summary>The release carried by a subclass row, for keying the action-state lookup.</summary>
|
||||||
|
protected abstract ReleaseDto ReleaseOf(TRow row);
|
||||||
|
|
||||||
protected List<TRow> Rows { get; private set; } = new();
|
protected List<TRow> Rows { get; private set; } = new();
|
||||||
protected bool Loading { get; private set; } = true;
|
protected bool Loading { get; private set; } = true;
|
||||||
|
|
||||||
|
// Bare release projection handed to the rich grid. The grid does the expand/delete/edit/Type-chip;
|
||||||
|
// it never sees TRow. Rebuilt on every (re)load so the grid re-projects against a fresh reference.
|
||||||
|
protected IReadOnlyList<ReleaseDto> Releases { get; private set; } = Array.Empty<ReleaseDto>();
|
||||||
|
|
||||||
|
// release.Id → action-state row, so a RowActions fragment (which the grid hands a ReleaseDto) can
|
||||||
|
// recover its TRow. Rebuilt alongside Rows so a refresh never leaves a stale row behind.
|
||||||
|
private Dictionary<long, TRow> _rowsById = new();
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync() => await LoadAsync();
|
protected override async Task OnInitializedAsync() => await LoadAsync();
|
||||||
|
|
||||||
|
/// <summary>Recovers the action-state row for a release the rich grid is rendering. Null if the
|
||||||
|
/// release is not in the current page (e.g. just deleted), in which case the action is skipped.</summary>
|
||||||
|
protected TRow? RowFor(ReleaseDto release) =>
|
||||||
|
_rowsById.TryGetValue(release.Id, out var row) ? row : null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reloads the medium-filtered release list. Wired to the rich grid's <c>OnReleasesChanged</c> so a
|
||||||
|
/// delete re-fetches the authoritative list (track counts, orphan cleanup) — the same single-load
|
||||||
|
/// posture <c>CmsAllReleasesGrid</c> uses for the ALL tab.
|
||||||
|
/// </summary>
|
||||||
|
protected async Task ReloadAsync()
|
||||||
|
{
|
||||||
|
await LoadAsync();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task LoadAsync()
|
private async Task LoadAsync()
|
||||||
{
|
{
|
||||||
Loading = true;
|
Loading = true;
|
||||||
@@ -53,6 +84,8 @@ public abstract class CmsMediumBrowserBase<TRow> : ComponentBase
|
|||||||
Rows = new List<TRow>();
|
Rows = new List<TRow>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Releases = Rows.Select(ReleaseOf).ToList();
|
||||||
|
_rowsById = Rows.ToDictionary(r => ReleaseOf(r).Id);
|
||||||
Loading = false;
|
Loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
@namespace DeepDrftManager.Components.Pages.Tracks
|
|
||||||
@typeparam TRow
|
|
||||||
@using DeepDrftModels.DTOs
|
|
||||||
|
|
||||||
@* Shared table shell for the single-track medium browsers (Sessions, Mixes). Renders the cover
|
|
||||||
thumbnail, title, artist, and a shared Edit affordance that every medium gets (9.5.E). The
|
|
||||||
medium-specific cells (hero / waveform / generate-or-upload action) are supplied per row via the
|
|
||||||
ActionContent slot, which receives the subclass's typed row. Fully controlled by the parent:
|
|
||||||
loading and row state are passed in. *@
|
|
||||||
|
|
||||||
@if (Loading)
|
|
||||||
{
|
|
||||||
<MudProgressCircular Indeterminate="true" Class="mt-4" />
|
|
||||||
}
|
|
||||||
else if (Rows.Count == 0)
|
|
||||||
{
|
|
||||||
<MudText Typo="Typo.body1" Class="mt-4">@EmptyMessage</MudText>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<MudTable T="TRow" Items="Rows" Hover="true" Striped="true" Dense="true" Bordered="false" FixedHeader="true">
|
|
||||||
<HeaderContent>
|
|
||||||
<MudTh Style="width: 1%;">Cover</MudTh>
|
|
||||||
<MudTh>@TitleHeader</MudTh>
|
|
||||||
<MudTh>Artist</MudTh>
|
|
||||||
<MudTh Style="width: 1%; white-space: nowrap;">Actions</MudTh>
|
|
||||||
</HeaderContent>
|
|
||||||
<RowTemplate>
|
|
||||||
<MudTd DataLabel="Cover">
|
|
||||||
@{ var release = ReleaseAccessor(context); }
|
|
||||||
@if (!string.IsNullOrEmpty(release.ImagePath))
|
|
||||||
{
|
|
||||||
<div class="cms-album-thumb" style="background-image: url('@ThumbUrl(release.ImagePath)');"></div>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<div class="cms-album-thumb cms-album-thumb--fallback"></div>
|
|
||||||
}
|
|
||||||
</MudTd>
|
|
||||||
<MudTd DataLabel="@TitleHeader">@ReleaseAccessor(context).Title</MudTd>
|
|
||||||
<MudTd DataLabel="Artist">@ReleaseAccessor(context).Artist</MudTd>
|
|
||||||
<MudTd DataLabel="Actions">
|
|
||||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
|
||||||
@ActionContent(context)
|
|
||||||
<MudTooltip Text="Edit release">
|
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Edit"
|
|
||||||
Size="Size.Small"
|
|
||||||
Color="Color.Primary"
|
|
||||||
Href="@($"/tracks/album/{Uri.EscapeDataString(ReleaseAccessor(context).Title)}/edit")" />
|
|
||||||
</MudTooltip>
|
|
||||||
</MudStack>
|
|
||||||
</MudTd>
|
|
||||||
</RowTemplate>
|
|
||||||
</MudTable>
|
|
||||||
}
|
|
||||||
|
|
||||||
@code {
|
|
||||||
[Parameter] public required IReadOnlyList<TRow> Rows { get; set; }
|
|
||||||
[Parameter] public bool Loading { get; set; }
|
|
||||||
|
|
||||||
/// <summary>Projects a row to its underlying release for the cover/title/artist cells.</summary>
|
|
||||||
[Parameter] public required Func<TRow, ReleaseDto> ReleaseAccessor { get; set; }
|
|
||||||
|
|
||||||
/// <summary>Medium-specific cell content (hero / waveform / generate action) for each row.</summary>
|
|
||||||
[Parameter] public required RenderFragment<TRow> ActionContent { get; set; }
|
|
||||||
|
|
||||||
/// <summary>Relative thumbnail URL builder; the base class supplies its proxy-aware helper.</summary>
|
|
||||||
[Parameter] public required Func<string, string> ThumbUrl { get; set; }
|
|
||||||
|
|
||||||
/// <summary>Column header / data-label for the title column (e.g. "Session", "Mix").</summary>
|
|
||||||
[Parameter] public string TitleHeader { get; set; } = "Title";
|
|
||||||
|
|
||||||
[Parameter] public string EmptyMessage { get; set; } = "Nothing here yet.";
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
/* Cover-thumbnail idiom shared by the medium browsers' tables. Blazor CSS isolation is per-component,
|
|
||||||
so this scoped copy of the album-browser thumb classes reaches only this component's own markup. */
|
|
||||||
.cms-album-thumb {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 4px;
|
|
||||||
background-size: cover;
|
|
||||||
background-position: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cms-album-thumb--fallback {
|
|
||||||
background-color: var(--mud-palette-action-default-hover);
|
|
||||||
}
|
|
||||||
@@ -6,10 +6,11 @@
|
|||||||
@inject ILogger<CmsMixBrowser> Logger
|
@inject ILogger<CmsMixBrowser> Logger
|
||||||
|
|
||||||
@* Embedded as the MIXES tab content of the Release Archive (Phase 9 §8.A), and still routable at
|
@* 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-
|
/tracks/mixes for direct-URL access. The grid is the rich CmsAlbumBrowser filtered to Mixes (§8.C
|
||||||
meaningless "Back to Release Archive" button) is suppressed — the host tab strip owns that frame; only
|
parity: expand-tracks, delete, Type chip, per-row edit), with the Mix waveform generate supplied as
|
||||||
the grid renders. The standalone route keeps the full page chrome. The per-row waveform affordance
|
its medium-specific RowActions slot so that affordance survives the move off the thin table. When
|
||||||
(9.5.E) is preserved in both contexts. *@
|
embedded, the page chrome (title, container, the now-meaningless "Back to Release Archive" button) is
|
||||||
|
suppressed; the standalone route keeps it. The waveform affordance (9.5.E) is preserved in both. *@
|
||||||
@if (Embedded)
|
@if (Embedded)
|
||||||
{
|
{
|
||||||
@GridContent
|
@GridContent
|
||||||
@@ -48,16 +49,19 @@ else
|
|||||||
HasWaveform = !string.IsNullOrEmpty(release.MixMetadata?.WaveformEntryKey)
|
HasWaveform = !string.IsNullOrEmpty(release.MixMetadata?.WaveformEntryKey)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
protected override ReleaseDto ReleaseOf(MixRow row) => row.Release;
|
||||||
|
|
||||||
// The grid itself — identical in the embedded and standalone contexts. Defined once as a fragment so
|
// 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.
|
// both branches above render the same markup without duplication. The waveform generate is the Mix's
|
||||||
private RenderFragment GridContent => @<CmsMediumTable TRow="MixRow"
|
// medium-specific RowActions content; the grid hands it each release, and RowFor recovers the
|
||||||
Rows="Rows"
|
// matching MixRow's generate state.
|
||||||
Loading="Loading"
|
private RenderFragment GridContent => @<CmsAlbumBrowser Releases="Releases"
|
||||||
ReleaseAccessor="@(row => row.Release)"
|
IsLoading="Loading"
|
||||||
ThumbUrl="@(key => ThumbUrl(key))"
|
OnReleasesChanged="ReloadAsync">
|
||||||
TitleHeader="Mix"
|
<RowActions Context="release">
|
||||||
EmptyMessage="No mixes found.">
|
@{ var row = RowFor(release); }
|
||||||
<ActionContent Context="row">
|
@if (row is not null)
|
||||||
|
{
|
||||||
@if (row.HasWaveform)
|
@if (row.HasWaveform)
|
||||||
{
|
{
|
||||||
<MudTooltip Text="Waveform generated">
|
<MudTooltip Text="Waveform generated">
|
||||||
@@ -85,8 +89,9 @@ else
|
|||||||
<span>@(row.HasWaveform ? "Regenerate" : "Generate")</span>
|
<span>@(row.HasWaveform ? "Regenerate" : "Generate")</span>
|
||||||
}
|
}
|
||||||
</MudButton>
|
</MudButton>
|
||||||
</ActionContent>
|
}
|
||||||
</CmsMediumTable>;
|
</RowActions>
|
||||||
|
</CmsAlbumBrowser>;
|
||||||
|
|
||||||
private async Task GenerateWaveformAsync(MixRow row)
|
private async Task GenerateWaveformAsync(MixRow row)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,10 +7,11 @@
|
|||||||
@inject ILogger<CmsSessionBrowser> Logger
|
@inject ILogger<CmsSessionBrowser> Logger
|
||||||
|
|
||||||
@* Embedded as the SESSIONS tab content of the Release Archive (Phase 9 §8.A), and still routable at
|
@* 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-
|
/tracks/sessions for direct-URL access. The grid is the rich CmsAlbumBrowser filtered to Sessions
|
||||||
meaningless "Back to Release Archive" button) is suppressed — the host tab strip owns that frame; only
|
(§8.C parity: expand-tracks, delete, Type chip, per-row edit), with the Session hero upload supplied
|
||||||
the grid renders. The standalone route keeps the full page chrome. The per-row hero affordance (9.5.E)
|
as its medium-specific RowActions slot so that affordance survives the move off the thin table. When
|
||||||
is preserved in both contexts. *@
|
embedded, the page chrome (title, container, the now-meaningless "Back to Release Archive" button) is
|
||||||
|
suppressed; the standalone route keeps it. The hero affordance (9.5.E) is preserved in both contexts. *@
|
||||||
@if (Embedded)
|
@if (Embedded)
|
||||||
{
|
{
|
||||||
@GridContent
|
@GridContent
|
||||||
@@ -49,16 +50,19 @@ else
|
|||||||
HeroImageEntryKey = release.SessionMetadata?.HeroImageEntryKey
|
HeroImageEntryKey = release.SessionMetadata?.HeroImageEntryKey
|
||||||
};
|
};
|
||||||
|
|
||||||
|
protected override ReleaseDto ReleaseOf(SessionRow row) => row.Release;
|
||||||
|
|
||||||
// The grid itself — identical in the embedded and standalone contexts. Defined once as a fragment so
|
// 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.
|
// both branches above render the same markup without duplication. The hero upload is the Session's
|
||||||
private RenderFragment GridContent => @<CmsMediumTable TRow="SessionRow"
|
// medium-specific RowActions content; the grid hands it each release, and RowFor recovers the
|
||||||
Rows="Rows"
|
// matching SessionRow's upload state.
|
||||||
Loading="Loading"
|
private RenderFragment GridContent => @<CmsAlbumBrowser Releases="Releases"
|
||||||
ReleaseAccessor="@(row => row.Release)"
|
IsLoading="Loading"
|
||||||
ThumbUrl="@(key => ThumbUrl(key))"
|
OnReleasesChanged="ReloadAsync">
|
||||||
TitleHeader="Session"
|
<RowActions Context="release">
|
||||||
EmptyMessage="No sessions found.">
|
@{ var row = RowFor(release); }
|
||||||
<ActionContent Context="row">
|
@if (row is not null)
|
||||||
|
{
|
||||||
@if (row.HeroImageEntryKey is { Length: > 0 } heroKey)
|
@if (row.HeroImageEntryKey is { Length: > 0 } heroKey)
|
||||||
{
|
{
|
||||||
<div class="cms-album-thumb" style="background-image: url('@ThumbUrl(heroKey)');"></div>
|
<div class="cms-album-thumb" style="background-image: url('@ThumbUrl(heroKey)');"></div>
|
||||||
@@ -88,8 +92,9 @@ else
|
|||||||
</MudButton>
|
</MudButton>
|
||||||
</ActivatorContent>
|
</ActivatorContent>
|
||||||
</MudFileUpload>
|
</MudFileUpload>
|
||||||
</ActionContent>
|
}
|
||||||
</CmsMediumTable>;
|
</RowActions>
|
||||||
|
</CmsAlbumBrowser>;
|
||||||
|
|
||||||
private async Task UploadHeroAsync(SessionRow row, IBrowserFile? file)
|
private async Task UploadHeroAsync(SessionRow row, IBrowserFile? file)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/* Hero-thumbnail idiom for the session row's action cell. The cover thumb lives in CmsMediumTable's
|
/* Hero-thumbnail idiom for the session row's action cell. The hero <div> is authored in this
|
||||||
own scoped CSS; this scoped copy reaches only the hero <div> rendered in this component's
|
component's RowActions fragment, so Blazor stamps it with this component's scope attribute even
|
||||||
ActionContent markup (Blazor CSS isolation is per-component). */
|
though CmsAlbumBrowser renders it — this scoped copy reaches it (CSS isolation follows authoring
|
||||||
|
component, not rendering host). The grid's own cover thumb lives in CmsAlbumBrowser's scoped CSS. */
|
||||||
.cms-album-thumb {
|
.cms-album-thumb {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
|||||||
Reference in New Issue
Block a user