139 lines
3.8 KiB
Plaintext
139 lines
3.8 KiB
Plaintext
@implements IDisposable
|
|
|
|
<div class="cooldown-wrap" style="--cooldown-size: @(Size)px">
|
|
<button class="cooldown-btn"
|
|
disabled="@_isCooldown"
|
|
@onclick="HandleClick"
|
|
@onclick:preventDefault="_isCooldown">
|
|
<span class="cooldown-btn-text">@ChildContent</span>
|
|
</button>
|
|
@if (_isCooldown)
|
|
{
|
|
<div class="cooldown-overlay"
|
|
style="mask-image: conic-gradient(transparent 0deg, transparent @(_elapsedAngle)deg, #000 @(_elapsedAngle)deg, #000 360deg);
|
|
-webkit-mask-image: conic-gradient(transparent 0deg, transparent @(_elapsedAngle)deg, #000 @(_elapsedAngle)deg, #000 360deg);">
|
|
<span class="cooldown-label">@_remainingSeconds</span>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<style>
|
|
.cooldown-wrap {
|
|
position: relative;
|
|
display: inline-flex;
|
|
width: var(--cooldown-size, 120px);
|
|
height: var(--cooldown-size, 120px);
|
|
}
|
|
.cooldown-btn {
|
|
width: 100%;
|
|
height: 100%;
|
|
border: 2px solid var(--primary);
|
|
border-radius: 12px;
|
|
background: var(--paper);
|
|
color: var(--text-primary, #eee);
|
|
font-weight: 700;
|
|
font-size: 0.9rem;
|
|
cursor: pointer;
|
|
padding: 12px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: background 0.15s, border-color 0.15s;
|
|
line-height: 1.3;
|
|
font-family: inherit;
|
|
}
|
|
.cooldown-btn:hover:not(:disabled) {
|
|
background: var(--paper-hover);
|
|
border-color: var(--primary-hover);
|
|
}
|
|
.cooldown-btn:active:not(:disabled) {
|
|
transform: scale(0.97);
|
|
}
|
|
.cooldown-btn:disabled {
|
|
opacity: 0;
|
|
cursor: default;
|
|
}
|
|
.cooldown-btn-text {
|
|
pointer-events: none;
|
|
}
|
|
.cooldown-overlay {
|
|
position: absolute;
|
|
inset: 0;
|
|
border-radius: 12px;
|
|
background: rgba(22, 22, 24, 0.82);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
pointer-events: none;
|
|
user-select: none;
|
|
}
|
|
.cooldown-label {
|
|
font-size: 1.6rem;
|
|
font-weight: 900;
|
|
color: #999;
|
|
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
|
|
pointer-events: none;
|
|
}
|
|
</style>
|
|
|
|
@code {
|
|
[Parameter] public RenderFragment? ChildContent { get; set; }
|
|
[Parameter] public EventCallback OnClick { get; set; }
|
|
[Parameter] public int CooldownSeconds { get; set; } = 12;
|
|
[Parameter] public int Size { get; set; } = 120;
|
|
|
|
private bool _isCooldown;
|
|
private int _elapsedAngle;
|
|
private int _remainingSeconds;
|
|
private DateTime _startTime;
|
|
private System.Timers.Timer? _timer;
|
|
|
|
private async Task HandleClick()
|
|
{
|
|
if (_isCooldown) return;
|
|
await OnClick.InvokeAsync(null);
|
|
StartCooldown();
|
|
}
|
|
|
|
private void StartCooldown()
|
|
{
|
|
_isCooldown = true;
|
|
_startTime = DateTime.UtcNow;
|
|
_elapsedAngle = 0;
|
|
_remainingSeconds = CooldownSeconds;
|
|
|
|
_timer = new System.Timers.Timer(33);
|
|
_timer.Elapsed += OnTick;
|
|
_timer.AutoReset = true;
|
|
_timer.Enabled = true;
|
|
}
|
|
|
|
private void OnTick(object? sender, System.Timers.ElapsedEventArgs e)
|
|
{
|
|
var elapsed = (DateTime.UtcNow - _startTime).TotalSeconds;
|
|
if (elapsed >= CooldownSeconds)
|
|
{
|
|
_isCooldown = false;
|
|
_timer?.Stop();
|
|
_timer?.Dispose();
|
|
_timer = null;
|
|
InvokeAsync(StateHasChanged);
|
|
return;
|
|
}
|
|
|
|
_elapsedAngle = (int)(elapsed / CooldownSeconds * 360);
|
|
_remainingSeconds = CooldownSeconds - (int)elapsed;
|
|
InvokeAsync(StateHasChanged);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_timer != null)
|
|
{
|
|
_timer.Stop();
|
|
_timer.Dispose();
|
|
_timer = null;
|
|
}
|
|
}
|
|
}
|