< Summary

Information
Class: Chronicis.Client.Components.Articles.EmojiPickerButton
Assembly: Chronicis.Client
File(s): /home/runner/work/chronicis/chronicis/src/Chronicis.Client/Components/Articles/EmojiPickerButton.razor
Line coverage
0%
Covered lines: 0
Uncovered lines: 51
Coverable lines: 51
Total lines: 172
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 20
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
BuildRenderTree(...)0%156120%
get_CurrentEmoji()100%210%
get_OnEmojiChanged()100%210%
get_IsSaving()100%210%
get_HasEmoji()100%210%
OnInitialized()100%210%
TogglePicker()0%620%
OpenPicker()100%210%
ClosePicker()0%620%
OnEmojiSelected()100%210%
ClearEmoji()100%210%
DisposeAsync()0%2040%

File(s)

/home/runner/work/chronicis/chronicis/src/Chronicis.Client/Components/Articles/EmojiPickerButton.razor

#LineLine coverage
 1@inject IJSRuntime JSRuntime
 2@implements IAsyncDisposable
 3@code {
 4    private readonly ILogger<EmojiPickerButton> _logger;
 5
 6    // Direct constructor injection
 07    public EmojiPickerButton(ILogger<EmojiPickerButton> logger)
 8    {
 09        _logger = logger;
 010    }
 11}
 12
 13<div class="chronicis-emoji-picker-container" @onclick:stopPropagation="true">
 14    <!-- Emoji Button -->
 15    <button type="button"
 16            class="chronicis-emoji-button @(HasEmoji ? "has-emoji" : "")"
 17            @onclick="TogglePicker"
 18            title="@(HasEmoji ? "Change icon" : "Add icon")">
 019        @if (HasEmoji)
 20        {
 021            <span>@CurrentEmoji</span>
 22        }
 23        else
 24        {
 25            <MudIcon Icon="@Icons.Material.Outlined.AddReaction"
 26                     Class="placeholder-icon"
 27                     Size="Size.Medium" />
 28        }
 29    </button>
 30
 31    <!-- Clear button (shown on hover when emoji exists) -->
 032    @if (HasEmoji)
 33    {
 34        <button type="button"
 35                class="chronicis-emoji-clear-button"
 36                @onclick="ClearEmoji"
 37                @onclick:stopPropagation="true"
 38                title="Remove icon">
 39            ✕
 40        </button>
 41    }
 42
 43    <!-- Picker Dropdown -->
 044    @if (_isPickerOpen)
 45    {
 46        <div class="chronicis-emoji-picker-dropdown" id="@_pickerId">
 47            <!-- emoji-mart picker will be injected here by JS -->
 48        </div>
 49    }
 50</div>
 51
 52@* Click-outside handler *@
 053@if (_isPickerOpen)
 54{
 55    <div class="chronicis-emoji-picker-backdrop"
 56         @onclick="ClosePicker"
 57         style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 999;"></div>
 58}
 59
 60@code {
 61    /// <summary>
 62    /// The current emoji value (null or empty means no icon set)
 63    /// </summary>
 64    [Parameter]
 065    public string? CurrentEmoji { get; set; }
 66
 67    /// <summary>
 68    /// Callback when an emoji is selected or cleared
 69    /// </summary>
 70    [Parameter]
 071    public EventCallback<string?> OnEmojiChanged { get; set; }
 72
 73    /// <summary>
 74    /// Whether the article is currently saving (to show loading state)
 75    /// </summary>
 76    [Parameter]
 077    public bool IsSaving { get; set; }
 78
 79    private bool _isPickerOpen = false;
 080    private string _pickerId = $"emoji-picker-{Guid.NewGuid():N}";
 81    private DotNetObjectReference<EmojiPickerButton>? _dotNetHelper;
 82    private bool _pickerInitialized = false;
 83
 084    private bool HasEmoji => !string.IsNullOrEmpty(CurrentEmoji);
 85
 86    protected override void OnInitialized()
 87    {
 088        _dotNetHelper = DotNetObjectReference.Create(this);
 089    }
 90
 91    private async Task TogglePicker()
 92    {
 093        if (_isPickerOpen)
 94        {
 095            await ClosePicker();
 96        }
 97        else
 98        {
 099            await OpenPicker();
 100        }
 0101    }
 102
 103    private async Task OpenPicker()
 104    {
 0105        _isPickerOpen = true;
 0106        StateHasChanged();
 107
 108        // Wait for the DOM to update, then initialize the picker
 0109        await Task.Delay(50);
 110
 111        try
 112        {
 0113            await JSRuntime.InvokeVoidAsync("initializeEmojiPicker", _pickerId, _dotNetHelper);
 0114            _pickerInitialized = true;
 0115        }
 0116        catch (Exception ex)
 117        {
 0118            _logger.LogErrorSanitized(ex, "Failed to initialize emoji picker");
 0119        }
 0120    }
 121
 122    private async Task ClosePicker()
 123    {
 0124        if (_pickerInitialized)
 125        {
 126            try
 127            {
 0128                await JSRuntime.InvokeVoidAsync("destroyEmojiPicker", _pickerId);
 0129            }
 0130            catch (Exception ex)
 131            {
 0132                _logger.LogErrorSanitized(ex, "Failed to destroy emoji picker");
 0133            }
 0134            _pickerInitialized = false;
 135        }
 136
 0137        _isPickerOpen = false;
 0138        StateHasChanged();
 0139    }
 140
 141    /// <summary>
 142    /// Called from JavaScript when an emoji is selected
 143    /// </summary>
 144    [JSInvokable]
 145    public async Task OnEmojiSelected(string emoji)
 146    {
 0147        await ClosePicker();
 0148        await OnEmojiChanged.InvokeAsync(emoji);
 0149    }
 150
 151    private async Task ClearEmoji()
 152    {
 0153        await OnEmojiChanged.InvokeAsync(null);
 0154    }
 155
 156    public async ValueTask DisposeAsync()
 157    {
 0158        if (_pickerInitialized)
 159        {
 160            try
 161            {
 0162                await JSRuntime.InvokeVoidAsync("destroyEmojiPicker", _pickerId);
 0163            }
 0164            catch
 165            {
 166                // Ignore disposal errors (component may already be gone)
 0167            }
 168        }
 169
 0170        _dotNetHelper?.Dispose();
 0171    }
 172}