Merge branch 'share-button' into dev
This commit is contained in:
@@ -0,0 +1,61 @@
|
|||||||
|
@namespace DeepDrftPublic.Client.Controls
|
||||||
|
|
||||||
|
<MudTooltip Text="Share">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Share"
|
||||||
|
Size="Size.Large"
|
||||||
|
Color="Color.Secondary"
|
||||||
|
OnClick="@Toggle"
|
||||||
|
aria-label="Share this track" />
|
||||||
|
</MudTooltip>
|
||||||
|
|
||||||
|
<MudOverlay Visible="@_open" OnClick="@Close" AutoClose="true" />
|
||||||
|
|
||||||
|
<MudPopover Open="@_open"
|
||||||
|
Fixed="false"
|
||||||
|
AnchorOrigin="Origin.BottomRight"
|
||||||
|
TransformOrigin="Origin.TopRight"
|
||||||
|
Class="deepdrft-share-popover">
|
||||||
|
<MudStack Spacing="2" Class="deepdrft-share-popover-body">
|
||||||
|
|
||||||
|
<MudStack Row AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween">
|
||||||
|
<MudButton StartIcon="@Icons.Material.Filled.Link"
|
||||||
|
Variant="Variant.Text"
|
||||||
|
Color="Color.Primary"
|
||||||
|
OnClick="@CopyLink">
|
||||||
|
Copy link
|
||||||
|
</MudButton>
|
||||||
|
@if (_linkCopied)
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Success">Copied!</MudText>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
|
||||||
|
<MudDivider />
|
||||||
|
|
||||||
|
<MudCheckBox @bind-Value="Embed" Color="Color.Primary" Label="Embed player" Dense="true" />
|
||||||
|
|
||||||
|
@if (_embed)
|
||||||
|
{
|
||||||
|
<MudStack Row AlignItems="AlignItems.Center" Spacing="1">
|
||||||
|
<MudTextField Value="@EmbedSnippet"
|
||||||
|
T="string"
|
||||||
|
ReadOnly="true"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Lines="3"
|
||||||
|
Margin="Margin.Dense"
|
||||||
|
Class="deepdrft-share-embed-field" />
|
||||||
|
<MudStack AlignItems="AlignItems.Center" Spacing="0">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.ContentCopy"
|
||||||
|
Color="Color.Primary"
|
||||||
|
OnClick="@CopyEmbed"
|
||||||
|
aria-label="Copy embed snippet" />
|
||||||
|
@if (_embedCopied)
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.caption" Color="Color.Success">Copied!</MudText>
|
||||||
|
}
|
||||||
|
</MudStack>
|
||||||
|
</MudStack>
|
||||||
|
}
|
||||||
|
|
||||||
|
</MudStack>
|
||||||
|
</MudPopover>
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
using Microsoft.AspNetCore.Components;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
|
namespace DeepDrftPublic.Client.Controls;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Share affordance for the track detail page: a popover offering a canonical-link copy
|
||||||
|
/// and an optional iframe embed snippet. Clipboard writes go through navigator.clipboard;
|
||||||
|
/// each copy shows a transient "Copied!" confirmation that resets after a short delay.
|
||||||
|
/// </summary>
|
||||||
|
public partial class SharePopover : ComponentBase, IDisposable
|
||||||
|
{
|
||||||
|
[Parameter] public string? EntryKey { get; set; }
|
||||||
|
|
||||||
|
[Inject] public required NavigationManager Navigation { get; set; }
|
||||||
|
[Inject] public required IJSRuntime JS { get; set; }
|
||||||
|
|
||||||
|
private bool _open;
|
||||||
|
private bool _embed;
|
||||||
|
private bool _linkCopied;
|
||||||
|
private bool _embedCopied;
|
||||||
|
|
||||||
|
private readonly CancellationTokenSource _cts = new();
|
||||||
|
|
||||||
|
private bool Embed
|
||||||
|
{
|
||||||
|
get => _embed;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_embed = value;
|
||||||
|
if (!value) _embedCopied = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string TrackUrl => $"{Navigation.BaseUri}track/{EntryKey}";
|
||||||
|
|
||||||
|
private string EmbedSnippet =>
|
||||||
|
$"""<iframe src="{Navigation.BaseUri}FramePlayer?TrackEntryKey={EntryKey}" width="640" height="96" frameborder="0" style="border-radius:8px;" allow="autoplay"></iframe>""";
|
||||||
|
|
||||||
|
private void Toggle() => _open = !_open;
|
||||||
|
|
||||||
|
private void Close() => _open = false;
|
||||||
|
|
||||||
|
private async Task CopyLink()
|
||||||
|
{
|
||||||
|
if (await CopyToClipboard(TrackUrl))
|
||||||
|
{
|
||||||
|
_linkCopied = true;
|
||||||
|
await ResetAfterDelay(() => _linkCopied = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CopyEmbed()
|
||||||
|
{
|
||||||
|
if (await CopyToClipboard(EmbedSnippet))
|
||||||
|
{
|
||||||
|
_embedCopied = true;
|
||||||
|
await ResetAfterDelay(() => _embedCopied = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> CopyToClipboard(string text)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await JS.InvokeVoidAsync("navigator.clipboard.writeText", text);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ResetAfterDelay(Action reset)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Task.Delay(1500, _cts.Token);
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reset();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() => _cts.Cancel();
|
||||||
|
}
|
||||||
@@ -57,9 +57,10 @@ else if (ViewModel.Track is not null)
|
|||||||
<MudText Typo="Typo.h6" Color="Color.Primary">@track.Artist</MudText>
|
<MudText Typo="Typo.h6" Color="Color.Primary">@track.Artist</MudText>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<MudStack Row AlignItems="AlignItems.Center" Spacing="1">
|
||||||
|
<SharePopover EntryKey="@track.EntryKey" />
|
||||||
<PlayStateIcon Size="Size.Large" Color="Color.Secondary" OnToggle="@PlayTrack"/>
|
<PlayStateIcon Size="Size.Large" Color="Color.Secondary" OnToggle="@PlayTrack"/>
|
||||||
</div>
|
</MudStack>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|
||||||
<div class="deepdrft-track-detail-cover">
|
<div class="deepdrft-track-detail-cover">
|
||||||
@@ -67,8 +68,6 @@ else if (ViewModel.Track is not null)
|
|||||||
<MudIcon Icon="@Icons.Material.Filled.Album" Color="Color.Primary" />
|
<MudIcon Icon="@Icons.Material.Filled.Album" Color="Color.Primary" />
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@if (hasMeta)
|
@if (hasMeta)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -339,6 +339,23 @@ h2, h3, h4, h5, h6,
|
|||||||
font-family: var(--deepdrft-font-mono) !important;
|
font-family: var(--deepdrft-font-mono) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.deepdrft-share-popover-body {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
min-width: 280px;
|
||||||
|
max-width: 360px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Monospace snippet so the iframe markup stays legible inside the readonly field. */
|
||||||
|
.deepdrft-share-embed-field {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deepdrft-share-embed-field .mud-input-slot {
|
||||||
|
font-family: var(--deepdrft-font-mono) !important;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
/* display:contents so the anchor wraps the card's cover and title without
|
/* display:contents so the anchor wraps the card's cover and title without
|
||||||
introducing its own box — the container's positioning context and the
|
introducing its own box — the container's positioning context and the
|
||||||
content column's flex layout are both preserved. */
|
content column's flex layout are both preserved. */
|
||||||
|
|||||||
Reference in New Issue
Block a user