< Summary

Information
Class: Chronicis.Client.ViewModels.WorldSharingViewModel
Assembly: Chronicis.Client
File(s): /home/runner/work/chronicis/chronicis/src/Chronicis.Client/ViewModels/WorldSharingViewModel.cs
Line coverage
100%
Covered lines: 38
Uncovered lines: 0
Coverable lines: 38
Total lines: 158
Line coverage: 100%
Branch coverage
100%
Covered branches: 16
Total branches: 16
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_IsPublic()100%11100%
set_IsPublic(...)100%11100%
get_PendingSlug()100%11100%
set_PendingSlug(...)100%11100%
get_IsRenamingSlug()100%11100%
set_IsRenamingSlug(...)100%11100%
get_SlugRenameError()100%11100%
set_SlugRenameError(...)100%11100%
InitializeFrom(...)100%11100%
OnPublicToggleChanged()100%22100%
GetPublicUrlBase(...)100%11100%
GetFullPublicUrl(...)100%66100%
ShouldShowPublicPreview(...)100%44100%
GenerateSlugFromName(...)100%44100%

File(s)

/home/runner/work/chronicis/chronicis/src/Chronicis.Client/ViewModels/WorldSharingViewModel.cs

#LineLine coverage
 1using Chronicis.Client.Abstractions;
 2using Chronicis.Client.Services;
 3using Chronicis.Shared.DTOs;
 4using Chronicis.Shared.Extensions;
 5
 6namespace Chronicis.Client.ViewModels;
 7
 8public sealed class WorldSharingViewModel : ViewModelBase
 9{
 10    private readonly IWorldApiService _worldApi;
 11    private readonly IAppNavigator _navigator;
 12    private readonly IUserNotifier _notifier;
 13    private readonly ILogger<WorldSharingViewModel> _logger;
 14
 15    private bool _isPublic;
 3516    private string _pendingSlug = string.Empty;
 17    private bool _isRenamingSlug;
 18    private string? _slugRenameError;
 19
 20    /// <summary>Raised when a change is made that requires the parent world to be saved.</summary>
 21    public event Action? UnsavedChangesOccurred;
 22
 3523    public WorldSharingViewModel(
 3524        IWorldApiService worldApi,
 3525        IAppNavigator navigator,
 3526        IUserNotifier notifier,
 3527        ILogger<WorldSharingViewModel> logger)
 28    {
 3529        _worldApi = worldApi;
 3530        _navigator = navigator;
 3531        _notifier = notifier;
 3532        _logger = logger;
 3533    }
 34
 35    public bool IsPublic
 36    {
 437        get => _isPublic;
 1238        set => SetField(ref _isPublic, value);
 39    }
 40
 41    public string PendingSlug
 42    {
 1043        get => _pendingSlug;
 1744        set => SetField(ref _pendingSlug, value);
 45    }
 46
 47    public bool IsRenamingSlug
 48    {
 449        get => _isRenamingSlug;
 650        private set => SetField(ref _isRenamingSlug, value);
 51    }
 52
 53    public string? SlugRenameError
 54    {
 555        get => _slugRenameError;
 1856        private set => SetField(ref _slugRenameError, value);
 57    }
 58
 59    /// <summary>Initialises sharing state from a loaded world.</summary>
 60    public void InitializeFrom(WorldDetailDto world)
 61    {
 1262        IsPublic = world.IsPublic;
 1263        PendingSlug = world.Slug;
 1264        SlugRenameError = null;
 1265    }
 66
 67    public void OnPublicToggleChanged()
 68    {
 269        UnsavedChangesOccurred?.Invoke();
 170    }
 71
 72    /// <summary>
 73    /// Submits a slug rename. Returns the resolved slug on success, or null on failure.
 74    /// </summary>
 75    public async Task<string?> SaveSlugAsync(Guid worldId)
 76    {
 77        if (string.IsNullOrWhiteSpace(PendingSlug))
 78        {
 79            SlugRenameError = "Slug cannot be empty";
 80            return null;
 81        }
 82
 83        IsRenamingSlug = true;
 84        SlugRenameError = null;
 85
 86        try
 87        {
 88            var result = await _worldApi.UpdateSlugAsync(worldId, PendingSlug.Trim().ToLowerInvariant());
 89            if (result != null)
 90            {
 91                PendingSlug = result.Slug;
 92                _notifier.Success("World URL updated");
 93                return result.Slug;
 94            }
 95
 96            SlugRenameError = "Failed to update URL";
 97            return null;
 98        }
 99        catch (Exception ex)
 100        {
 101            _logger.LogErrorSanitized(ex, "Error updating slug for world {WorldId}", worldId);
 102            _notifier.Error("Failed to update URL");
 103            SlugRenameError = "Failed to update URL";
 104            return null;
 105        }
 106        finally
 107        {
 108            IsRenamingSlug = false;
 109        }
 110    }
 111
 112    public async Task CopyPublicUrlAsync(string url)
 113    {
 114        if (string.IsNullOrEmpty(url))
 115            return;
 116
 117        try
 118        {
 119            _navigator.NavigateTo($"javascript:navigator.clipboard.writeText('{url}')");
 120            _notifier.Success("Public URL copied to clipboard");
 121        }
 122        catch (Exception ex)
 123        {
 124            _logger.LogErrorSanitized(ex, "Error copying public URL");
 125            _notifier.Error("Failed to copy URL");
 126        }
 127
 128        await Task.CompletedTask;
 129    }
 130
 131    public string GetPublicUrlBase(string baseUri) =>
 2132        $"{baseUri.TrimEnd('/')}/w/";
 133
 134    public string GetFullPublicUrl(string baseUri, WorldDetailDto? world)
 135    {
 3136        if (world == null || !world.IsPublic || string.IsNullOrEmpty(world.Slug))
 2137            return string.Empty;
 1138        return $"{GetPublicUrlBase(baseUri)}{world.Slug}";
 139    }
 140
 141    public bool ShouldShowPublicPreview(WorldDetailDto? world) =>
 5142        world is not null && world.IsPublic && !string.IsNullOrEmpty(world.Slug);
 143
 144    /// <summary>Generates a URL-safe slug from a world name.</summary>
 145    public static string GenerateSlugFromName(string name)
 146    {
 6147        if (string.IsNullOrWhiteSpace(name))
 2148            return string.Empty;
 149
 4150        var slug = name.Trim().ToLowerInvariant();
 4151        slug = System.Text.RegularExpressions.Regex.Replace(slug, @"[\s_]+", "-");
 4152        slug = System.Text.RegularExpressions.Regex.Replace(slug, @"[^a-z0-9-]", "");
 4153        slug = System.Text.RegularExpressions.Regex.Replace(slug, @"-+", "-");
 4154        slug = slug.Trim('-');
 155
 4156        return slug.Length >= 3 ? slug : slug.PadRight(3, '0');
 157    }
 158}