Player Layout
This commit is contained in:
@@ -27,8 +27,11 @@ else
|
||||
|
||||
<VolumeZone Volume="@Volume" VolumeChanged="@OnVolumeChange"/>
|
||||
|
||||
<PlayerSeekZone CurrentTrack="CurrentTrack"
|
||||
OnSeekStart="@OnSeekStart"
|
||||
<div class="meta-zone">
|
||||
<TrackMetaLabel Track="CurrentTrack"/>
|
||||
</div>
|
||||
|
||||
<PlayerSeekZone OnSeekStart="@OnSeekStart"
|
||||
OnSeekEnd="@OnSeekEnd"
|
||||
OnSeekChange="@OnSeekChange"
|
||||
Class="seek-zone"/>
|
||||
|
||||
@@ -38,7 +38,7 @@ public partial class AudioPlayerBar : ComponentBase, IAsyncDisposable
|
||||
/// Display time - shows seek position while dragging, otherwise current playback time.
|
||||
/// </summary>
|
||||
private double DisplayTime => _isSeeking ? _seekPosition : (PlayerService?.CurrentTime ?? 0);
|
||||
private string PlayerModeClass => Fixed ? "player-fixed" : "player-docked";
|
||||
private string PlayerModeClass => Fixed ? "player-fixed" : "player-dock";
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
|
||||
@@ -71,29 +71,53 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Unified responsive player layout.
|
||||
Wide and narrow shapes are pure CSS — no runtime breakpoint subscription.
|
||||
Children are targeted by their stable classes (transport-zone, volume-zone,
|
||||
seek-zone) rather than positional nth-child, since all three render a .mud-stack
|
||||
root and positional selectors would be fragile. */
|
||||
/* Unified responsive player layout — CSS Grid with named areas redefined per breakpoint.
|
||||
The metadata (meta-zone) detaches from the waveform (seek-zone) in the mid band, which a
|
||||
flex order-swap can't express, so each of the four zones is placed by grid-area and the three
|
||||
shapes are pure template-area swaps — no runtime breakpoint subscription. min-width:0 on the
|
||||
shrinkable centre zones lets the title truncate and the waveform shrink instead of overflowing. */
|
||||
.player-layout {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
display: grid;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding-right: 2.5rem; /* clear the abs-positioned PlayerWindowControls */
|
||||
}
|
||||
|
||||
/* Wide (>= 600px): single row, seek zone grows and sits between transport and volume */
|
||||
@media (min-width: 600px) {
|
||||
::deep .transport-zone { order: 1; }
|
||||
::deep .seek-zone { order: 2; flex-grow: 1; flex-basis: 0; }
|
||||
::deep .volume-zone { order: 3; flex-shrink: 0; }
|
||||
::deep .transport-zone { grid-area: transport; }
|
||||
::deep .meta-zone { grid-area: meta; min-width: 0; }
|
||||
::deep .seek-zone { grid-area: waveform; min-width: 0; }
|
||||
::deep .volume-zone { grid-area: volume; }
|
||||
|
||||
/* Wide (>= 900px): single row — transport and volume flank the centre column; the metadata
|
||||
sits directly under the waveform (transport/volume span both rows, centred). */
|
||||
@media (min-width: 900px) {
|
||||
.player-layout {
|
||||
grid-template-columns: auto minmax(360px, 1fr) auto;
|
||||
grid-template-areas:
|
||||
"transport waveform volume"
|
||||
"transport meta volume";
|
||||
}
|
||||
}
|
||||
|
||||
/* Narrow (< 600px): transport + volume on top row, seek full-width below */
|
||||
@media (max-width: 599.98px) {
|
||||
::deep .transport-zone { order: 1; }
|
||||
::deep .volume-zone { order: 2; }
|
||||
::deep .seek-zone { flex-basis: 100%; order: 3; }
|
||||
/* Mid (600–900px): metadata rides the top row between transport and volume; the waveform gets
|
||||
the whole bottom row to itself rather than being squeezed beside the metadata. */
|
||||
@media (min-width: 600px) and (max-width: 899.98px) {
|
||||
.player-layout {
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
grid-template-areas:
|
||||
"transport meta volume"
|
||||
"waveform waveform waveform";
|
||||
}
|
||||
}
|
||||
|
||||
/* Narrow (< 600px): transport + volume share the top row; waveform then metadata stack full-width
|
||||
below — the most compressed shape. */
|
||||
@media (max-width: 599.98px) {
|
||||
.player-layout {
|
||||
grid-template-columns: auto 1fr auto;
|
||||
grid-template-areas:
|
||||
"transport . volume"
|
||||
"waveform waveform waveform"
|
||||
"meta meta meta";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,5 +5,4 @@
|
||||
OnSeekChange="OnSeekChange"
|
||||
OnSeekEnd="OnSeekEnd"
|
||||
Class="seek-waveform"/>
|
||||
<TrackMetaLabel Track="CurrentTrack"/>
|
||||
</MudStack>
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
using DeepDrftModels.DTOs;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace DeepDrftPublic.Client.Controls.AudioPlayerBar;
|
||||
|
||||
/// <summary>
|
||||
/// Centre zone of the player: the <see cref="WaveformSeeker"/> over the now-playing metadata row
|
||||
/// (<see cref="TrackMetaLabel"/>). The seeker owns the pointer-gesture seek logic and reads playback
|
||||
/// state off the cascaded player service directly; this zone just forwards the seek callbacks up to
|
||||
/// <see cref="AudioPlayerBar"/> (whose wiring is unchanged) and renders the current track's metadata.
|
||||
/// Centre zone of the player: the <see cref="WaveformSeeker"/>. The seeker owns the pointer-gesture
|
||||
/// seek logic and reads playback state off the cascaded player service directly; this zone just
|
||||
/// forwards the seek callbacks up to <see cref="AudioPlayerBar"/> (whose wiring is unchanged).
|
||||
/// The now-playing metadata (<see cref="TrackMetaLabel"/>) is a sibling zone in the grid, not nested
|
||||
/// here, so the responsive layouts can place it independently of the waveform.
|
||||
/// </summary>
|
||||
public partial class PlayerSeekZone : ComponentBase
|
||||
{
|
||||
[Parameter] public TrackDto? CurrentTrack { get; set; }
|
||||
[Parameter] public EventCallback OnSeekStart { get; set; }
|
||||
[Parameter] public EventCallback<double> OnSeekEnd { get; set; }
|
||||
[Parameter] public EventCallback<double> OnSeekChange { get; set; }
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<MudText Typo="Typo.subtitle2" Class="track-meta-title text-truncate">
|
||||
@Track.TrackName
|
||||
</MudText>
|
||||
<MudText Typo="Typo.subtitle2"> - </MudText>
|
||||
<MudText Typo="Typo.subtitle2" Class="track-meta-sep"> - </MudText>
|
||||
<MudText Typo="Typo.caption" Class="track-meta-artist text-truncate">
|
||||
@Track.Artist
|
||||
</MudText>
|
||||
|
||||
@@ -40,3 +40,56 @@
|
||||
::deep .track-meta-year {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
/* The metadata's three shapes track the dock's layout bands (same breakpoints as the grid in
|
||||
AudioPlayerBar.razor.css), not the label's own slot width — in the <600 band the slot is actually
|
||||
full-width yet we still want it fully vertical, which a container query can't express.
|
||||
|
||||
Mid band (600–900): 2×2 — title over artist on the left (start-justified), genre over year on the
|
||||
right (end-justified). The row stays a row; only the two inner groups go vertical. */
|
||||
@media (min-width: 600px) and (max-width: 899.98px) {
|
||||
.track-meta-row {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.track-meta-identity {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.track-meta-accents {
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
::deep .track-meta-sep {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Narrow band (<600): fully vertical — title / artist / genre / year all stacked, left-aligned. */
|
||||
@media (max-width: 599.98px) {
|
||||
.track-meta-row {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.track-meta-identity {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.track-meta-accents {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
::deep .track-meta-sep {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@namespace DeepDrftPublic.Client.Controls.AudioPlayerBar
|
||||
|
||||
<MudStack Row="false" AlignItems="AlignItems.Center" Spacing="1" Class="@($"volume-zone {Class}".TrimEnd())">
|
||||
<SpectrumVisualizer BucketCount="24"/>
|
||||
<SpectrumVisualizer BucketCount="10"/>
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1" Class="volume-row">
|
||||
<MudIcon Icon="@GetVolumeIcon()"/>
|
||||
<MudSlider T="double"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/* Width caps only — layout/colour come from MudStack + theme.
|
||||
The zone stacks the spectrum visualizer above the volume row. Width is sized
|
||||
so 24 spectrum bars (4px min + 2px gap ≈ 140px) render without clipping under
|
||||
the container's overflow:hidden, while staying compact. */
|
||||
The zone stacks the spectrum visualizer above the volume row, both sized to the same
|
||||
compact width so the spectrum matches the slider. 75px fits 10 spectrum bars (4px min +
|
||||
2px gap ≈ 70px) without clipping under the container's overflow:hidden. */
|
||||
.volume-zone {
|
||||
width: 150px;
|
||||
width: 75px;
|
||||
}
|
||||
|
||||
.volume-row {
|
||||
|
||||
Reference in New Issue
Block a user