< Summary

Information
Class: Chronicis.Client.Services.Tree.TreeDataBuilder
Assembly: Chronicis.Client
File(s): /home/runner/work/chronicis/chronicis/src/Chronicis.Client/Services/Tree/TreeDataBuilder.cs
Line coverage
100%
Covered lines: 224
Uncovered lines: 0
Coverable lines: 224
Total lines: 578
Line coverage: 100%
Branch coverage
100%
Covered branches: 50
Total branches: 50
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%
BuildWorldNode(...)100%2020100%
BuildCampaignNode(...)100%22100%
BuildArcNode(...)100%66100%
BuildSessionNode(...)100%44100%
BuildArticleNodeWithChildren(...)100%22100%
CreateArticleNode(...)100%11100%
CreateVirtualGroupNode(...)100%11100%
GetDocumentIcon(...)100%1616100%

File(s)

/home/runner/work/chronicis/chronicis/src/Chronicis.Client/Services/Tree/TreeDataBuilder.cs

#LineLine coverage
 1using Chronicis.Client.Models;
 2using Chronicis.Shared.DTOs;
 3using Chronicis.Shared.DTOs.Maps;
 4using Chronicis.Shared.DTOs.Sessions;
 5using Chronicis.Shared.Enums;
 6
 7namespace Chronicis.Client.Services.Tree;
 8
 9/// <summary>
 10/// Responsible for building the navigation tree structure from API data.
 11/// Fetches worlds, campaigns, arcs, and articles and constructs the hierarchical tree.
 12/// </summary>
 13internal sealed class TreeDataBuilder
 14{
 15    private readonly IArticleApiService _articleApi;
 16    private readonly IWorldApiService _worldApi;
 17    private readonly ICampaignApiService _campaignApi;
 18    private readonly IArcApiService _arcApi;
 19    private readonly ISessionApiService _sessionApi;
 20    private readonly IMapApiService _mapApi;
 21    private readonly ILogger _logger;
 22
 23    public TreeDataBuilder(
 24        IArticleApiService articleApi,
 25        IWorldApiService worldApi,
 26        ICampaignApiService campaignApi,
 27        IArcApiService arcApi,
 28        ISessionApiService sessionApi,
 29        IMapApiService mapApi,
 30        ILogger logger)
 31    {
 2432        _articleApi = articleApi;
 2433        _worldApi = worldApi;
 2434        _campaignApi = campaignApi;
 2435        _arcApi = arcApi;
 2436        _sessionApi = sessionApi;
 2437        _mapApi = mapApi;
 2438        _logger = logger;
 2439    }
 40
 41    /// <summary>
 42    /// Result of building the tree, containing the node index and cached articles.
 43    /// </summary>
 44    public sealed class BuildResult
 45    {
 46        public required TreeNodeIndex NodeIndex { get; init; }
 47        public required List<ArticleTreeDto> CachedArticles { get; init; }
 48    }
 49
 50    /// <summary>
 51    /// Builds the complete tree structure by fetching all data from APIs.
 52    /// </summary>
 53    public async Task<BuildResult> BuildTreeAsync()
 54    {
 55        var nodeIndex = new TreeNodeIndex();
 56        var stopwatch = System.Diagnostics.Stopwatch.StartNew();
 57
 58        // Phase 1: Fetch worlds and articles in parallel
 59        var worldsTask = _worldApi.GetWorldsAsync();
 60        var articlesTask = _articleApi.GetAllArticlesAsync();
 61
 62        await Task.WhenAll(worldsTask, articlesTask);
 63
 64        var worlds = worldsTask.Result;
 65        var allArticles = articlesTask.Result;
 66
 67        if (!worlds.Any())
 68        {
 69            return new BuildResult
 70            {
 71                NodeIndex = nodeIndex,
 72                CachedArticles = allArticles
 73            };
 74        }
 75
 76        // Phase 2: Fetch all world details in parallel
 77        _logger.LogDebug("Phase 2: Fetching world details in parallel...");
 78        var worldDetailTasks = worlds.Select(w => _worldApi.GetWorldAsync(w.Id)).ToList();
 79        var worldDetails = await Task.WhenAll(worldDetailTasks);
 80
 81        var worldDetailLookup = worldDetails
 82            .Where(wd => wd != null)
 83            .ToDictionary(wd => wd!.Id, wd => wd!);
 84
 85        _logger.LogDebug("Phase 2 complete: {Count} world details in {Ms}ms",
 86            worldDetailLookup.Count, stopwatch.ElapsedMilliseconds);
 87
 88        // Phase 3: Collect all campaigns and fetch arcs, links, documents in parallel
 89        var allCampaigns = worldDetails
 90            .Where(wd => wd != null)
 91            .SelectMany(wd => wd!.Campaigns ?? new List<CampaignDto>())
 92            .ToList();
 93
 94        _logger.LogDebug("Phase 3: Fetching arcs for {Count} campaigns, world links, and documents in parallel...", allC
 95
 96        var arcTasks = allCampaigns.Select(c => _arcApi.GetArcsByCampaignAsync(c.Id)).ToList();
 97        var linkTasks = worlds.Select(w => _worldApi.GetWorldLinksAsync(w.Id)).ToList();
 98        var documentTasks = worlds.Select(w => _worldApi.GetWorldDocumentsAsync(w.Id)).ToList();
 99        var mapTasks = worlds.Select(w => _mapApi.ListMapsForWorldAsync(w.Id)).ToList();
 100
 101        await Task.WhenAll(
 102            Task.WhenAll(arcTasks),
 103            Task.WhenAll(linkTasks),
 104            Task.WhenAll(documentTasks),
 105            Task.WhenAll(mapTasks)
 106        );
 107
 108        var arcResults = arcTasks.Select(t => t.Result).ToArray();
 109
 110        var linksByWorld = new Dictionary<Guid, List<WorldLinkDto>>();
 111        for (int i = 0; i < worlds.Count; i++)
 112        {
 113            linksByWorld[worlds[i].Id] = linkTasks[i].Result;
 114        }
 115
 116        var documentsByWorld = new Dictionary<Guid, List<WorldDocumentDto>>();
 117        for (int i = 0; i < worlds.Count; i++)
 118        {
 119            documentsByWorld[worlds[i].Id] = documentTasks[i].Result;
 120        }
 121
 122        var mapsByWorld = new Dictionary<Guid, List<MapSummaryDto>>();
 123        for (int i = 0; i < worlds.Count; i++)
 124        {
 125            mapsByWorld[worlds[i].Id] = mapTasks[i].Result;
 126        }
 127
 128        var arcsByCampaign = new Dictionary<Guid, List<ArcDto>>();
 129        for (int i = 0; i < allCampaigns.Count; i++)
 130        {
 131            arcsByCampaign[allCampaigns[i].Id] = arcResults[i];
 132        }
 133
 134        // Phase 3b: Fetch sessions for all arcs in parallel
 135        var allArcs = arcResults.SelectMany(r => r).ToList();
 136        var sessionTasks = allArcs.Select(a => _sessionApi.GetSessionsByArcAsync(a.Id)).ToList();
 137        await Task.WhenAll(sessionTasks);
 138
 139        var sessionsByArc = new Dictionary<Guid, List<SessionTreeDto>>();
 140        for (int i = 0; i < allArcs.Count; i++)
 141        {
 142            sessionsByArc[allArcs[i].Id] = sessionTasks[i].Result;
 143        }
 144
 145        _logger.LogDebug("Phase 3 complete: {Count} total arcs in {Ms}ms",
 146            arcResults.Sum(r => r.Count), stopwatch.ElapsedMilliseconds);
 147
 148        // Phase 4: Build the tree structure
 149        _logger.LogDebug("Phase 4: Building tree structure...");
 150
 151        var articleIndex = allArticles.ToDictionary(a => a.Id);
 152
 153        foreach (var world in worlds.OrderBy(w => w.Name))
 154        {
 155            if (!worldDetailLookup.TryGetValue(world.Id, out var worldDetail))
 156            {
 157                _logger.LogWarning("World detail not found for {WorldId}", world.Id);
 158                continue;
 159            }
 160
 161            var worldLinks = linksByWorld[world.Id];
 162            var worldDocuments = documentsByWorld[world.Id];
 163
 164            var worldMaps = mapsByWorld[world.Id];
 165
 166            var worldNode = BuildWorldNode(
 167                world,
 168                worldDetail,
 169                allArticles,
 170                articleIndex,
 171                arcsByCampaign,
 172                sessionsByArc,
 173                worldLinks,
 174                worldDocuments,
 175                worldMaps,
 176                nodeIndex);
 177
 178            nodeIndex.AddRootNode(worldNode);
 179        }
 180
 181        // Handle orphan articles (no world assigned)
 182        var orphanArticles = allArticles.Where(a => !a.WorldId.HasValue && a.ParentId == null).ToList();
 183        if (orphanArticles.Any())
 184        {
 185            _logger.LogWarning("{Count} articles have no world assigned", orphanArticles.Count);
 186
 187            var orphanNode = CreateVirtualGroupNode(
 188                VirtualGroupType.Uncategorized,
 189                "Unassigned Articles",
 190                null);
 191
 192            foreach (var article in orphanArticles)
 193            {
 194                var articleNode = CreateArticleNode(article);
 195                orphanNode.Children.Add(articleNode);
 196                nodeIndex.AddNode(articleNode);
 197            }
 198
 199            if (orphanNode.Children.Any())
 200            {
 201                orphanNode.ChildCount = orphanNode.Children.Count;
 202                nodeIndex.AddRootNode(orphanNode);
 203            }
 204        }
 205
 206        stopwatch.Stop();
 207        _logger.LogDebug("Tree build complete in {Ms}ms total", stopwatch.ElapsedMilliseconds);
 208
 209        return new BuildResult
 210        {
 211            NodeIndex = nodeIndex,
 212            CachedArticles = allArticles
 213        };
 214    }
 215
 216    private TreeNode BuildWorldNode(
 217        WorldDto world,
 218        WorldDetailDto worldDetail,
 219        List<ArticleTreeDto> allArticles,
 220        Dictionary<Guid, ArticleTreeDto> articleIndex,
 221        Dictionary<Guid, List<ArcDto>> arcsByCampaign,
 222        Dictionary<Guid, List<SessionTreeDto>> sessionsByArc,
 223        List<WorldLinkDto> worldLinks,
 224        List<WorldDocumentDto> worldDocuments,
 225        List<MapSummaryDto> worldMaps,
 226        TreeNodeIndex nodeIndex)
 227    {
 26228        var worldNode = new TreeNode
 26229        {
 26230            Id = world.Id,
 26231            NodeType = TreeNodeType.World,
 26232            Title = world.Name,
 26233            WorldId = world.Id,
 26234            IconEmoji = "fa-solid fa-globe"
 26235        };
 236
 26237        var campaigns = worldDetail.Campaigns ?? new List<CampaignDto>();
 26238        var worldArticles = allArticles.Where(a => a.WorldId == world.Id).ToList();
 239
 240        // Create virtual groups
 26241        var campaignsGroup = CreateVirtualGroupNode(VirtualGroupType.Campaigns, "Campaigns", world.Id);
 26242        var charactersGroup = CreateVirtualGroupNode(VirtualGroupType.PlayerCharacters, "Player Characters", world.Id);
 26243        var wikiGroup = CreateVirtualGroupNode(VirtualGroupType.Wiki, "Wiki", world.Id);
 26244        var uncategorizedGroup = CreateVirtualGroupNode(VirtualGroupType.Uncategorized, "Uncategorized", world.Id);
 245
 246        // Build Campaigns group
 100247        foreach (var campaign in campaigns.OrderBy(c => c.Name))
 248        {
 24249            var arcs = arcsByCampaign[campaign.Id];
 250
 24251            var campaignNode = BuildCampaignNode(campaign, arcs, worldArticles, articleIndex, sessionsByArc, nodeIndex);
 24252            campaignsGroup.Children.Add(campaignNode);
 24253            nodeIndex.AddNode(campaignNode);
 254        }
 26255        campaignsGroup.ChildCount = campaignsGroup.Children.Count;
 256
 257        // Build Characters group
 26258        var characterArticles = worldArticles
 26259            .Where(a => a.Type == ArticleType.Character && a.ParentId == null).OrderBy(a => a.Title).ToList();
 260
 56261        foreach (var article in characterArticles)
 262        {
 2263            var articleNode = BuildArticleNodeWithChildren(article, worldArticles, articleIndex, nodeIndex);
 2264            charactersGroup.Children.Add(articleNode);
 265        }
 26266        charactersGroup.ChildCount = charactersGroup.Children.Count;
 267
 268        // Build Wiki group
 26269        var wikiArticles = worldArticles
 26270            .Where(a => a.Type == ArticleType.WikiArticle && a.ParentId == null)
 26271            .OrderBy(a => a.Title)
 26272            .ToList();
 273
 100274        foreach (var article in wikiArticles)
 275        {
 24276            var articleNode = BuildArticleNodeWithChildren(article, worldArticles, articleIndex, nodeIndex);
 24277            wikiGroup.Children.Add(articleNode);
 278        }
 26279        wikiGroup.ChildCount = wikiGroup.Children.Count;
 280
 281        // Build Links group
 26282        var linksGroup = CreateVirtualGroupNode(VirtualGroupType.Links, "External Resources", world.Id);
 54283        foreach (var link in worldLinks.OrderBy(l => l.Title))
 284        {
 1285            var linkNode = new TreeNode
 1286            {
 1287                Id = link.Id,
 1288                NodeType = TreeNodeType.ExternalLink,
 1289                Title = link.Title,
 1290                Url = link.Url,
 1291                WorldId = world.Id,
 1292                IconEmoji = "fa-solid fa-external-link-alt"
 1293            };
 1294            linksGroup.Children.Add(linkNode);
 1295            nodeIndex.AddNode(linkNode);
 296        }
 297
 54298        foreach (var document in worldDocuments.Where(d => d.ArticleId == null).OrderBy(d => d.Title))
 299        {
 1300            var documentNode = new TreeNode
 1301            {
 1302                Id = document.Id,
 1303                NodeType = TreeNodeType.ExternalLink,
 1304                Title = document.Title,
 1305                Url = null,
 1306                WorldId = world.Id,
 1307                IconEmoji = GetDocumentIcon(document.ContentType),
 1308                AdditionalData = new Dictionary<string, object>
 1309                {
 1310                    { "IsDocument", true },
 1311                    { "ContentType", document.ContentType },
 1312                    { "FileSizeBytes", document.FileSizeBytes },
 1313                    { "FileName", document.FileName }
 1314                }
 1315            };
 1316            linksGroup.Children.Add(documentNode);
 1317            nodeIndex.AddNode(documentNode);
 318        }
 26319        linksGroup.ChildCount = linksGroup.Children.Count;
 320
 321        // Build Maps group
 26322        var mapsGroup = CreateVirtualGroupNode(VirtualGroupType.Maps, "Maps", world.Id);
 56323        foreach (var map in worldMaps.OrderBy(m => m.Name))
 324        {
 2325            var mapNode = new TreeNode
 2326            {
 2327                Id = map.WorldMapId,
 2328                NodeType = TreeNodeType.Map,
 2329                Title = map.Name,
 2330                WorldId = world.Id
 2331            };
 2332            mapsGroup.Children.Add(mapNode);
 2333            nodeIndex.AddNode(mapNode);
 334        }
 26335        mapsGroup.ChildCount = mapsGroup.Children.Count;
 336
 337        // Build Uncategorized group
 26338        var uncategorizedArticles = worldArticles
 26339            .Where(a => a.ParentId == null &&
 26340                       a.Type != ArticleType.WikiArticle &&
 26341                       a.Type != ArticleType.Character &&
 26342                       a.Type != ArticleType.Session &&
 26343                       a.Type != ArticleType.SessionNote &&
 26344                       a.Type != ArticleType.CharacterNote).OrderBy(a => a.Title).ToList();
 345
 56346        foreach (var article in uncategorizedArticles)
 347        {
 2348            var articleNode = BuildArticleNodeWithChildren(article, worldArticles, articleIndex, nodeIndex);
 2349            uncategorizedGroup.Children.Add(articleNode);
 350        }
 26351        uncategorizedGroup.ChildCount = uncategorizedGroup.Children.Count;
 352
 353        // Add groups to world node
 26354        worldNode.Children.Add(campaignsGroup);
 26355        nodeIndex.AddNode(campaignsGroup);
 356
 26357        worldNode.Children.Add(charactersGroup);
 26358        nodeIndex.AddNode(charactersGroup);
 359
 26360        worldNode.Children.Add(wikiGroup);
 26361        nodeIndex.AddNode(wikiGroup);
 362
 26363        worldNode.Children.Add(mapsGroup);
 26364        nodeIndex.AddNode(mapsGroup);
 365
 26366        if (linksGroup.Children.Any())
 367        {
 1368            worldNode.Children.Add(linksGroup);
 1369            nodeIndex.AddNode(linksGroup);
 370        }
 371
 26372        if (uncategorizedGroup.Children.Any())
 373        {
 2374            worldNode.Children.Add(uncategorizedGroup);
 2375            nodeIndex.AddNode(uncategorizedGroup);
 376        }
 377
 26378        worldNode.ChildCount = worldNode.Children.Count;
 379
 26380        return worldNode;
 381    }
 382
 383    private TreeNode BuildCampaignNode(
 384        CampaignDto campaign,
 385        List<ArcDto> arcs,
 386        List<ArticleTreeDto> worldArticles,
 387        Dictionary<Guid, ArticleTreeDto> articleIndex,
 388        Dictionary<Guid, List<SessionTreeDto>> sessionsByArc,
 389        TreeNodeIndex nodeIndex)
 390    {
 24391        var campaignNode = new TreeNode
 24392        {
 24393            Id = campaign.Id,
 24394            NodeType = TreeNodeType.Campaign,
 24395            Title = campaign.Name,
 24396            WorldId = campaign.WorldId,
 24397            CampaignId = campaign.Id,
 24398            IconEmoji = "fa-solid fa-dungeon"
 24399        };
 400
 96401        foreach (var arc in arcs.OrderBy(a => a.SortOrder).ThenBy(a => a.Name))
 402        {
 24403            var arcNode = BuildArcNode(arc, worldArticles, articleIndex, sessionsByArc, nodeIndex);
 24404            campaignNode.Children.Add(arcNode);
 24405            nodeIndex.AddNode(arcNode);
 406        }
 407
 24408        campaignNode.ChildCount = campaignNode.Children.Count;
 409
 24410        return campaignNode;
 411    }
 412
 413    private TreeNode BuildArcNode(
 414        ArcDto arc,
 415        List<ArticleTreeDto> worldArticles,
 416        Dictionary<Guid, ArticleTreeDto> articleIndex,
 417        Dictionary<Guid, List<SessionTreeDto>> sessionsByArc,
 418        TreeNodeIndex nodeIndex)
 419    {
 26420        var arcNode = new TreeNode
 26421        {
 26422            Id = arc.Id,
 26423            NodeType = TreeNodeType.Arc,
 26424            Title = arc.Name,
 26425            CampaignId = arc.CampaignId,
 26426            ArcId = arc.Id,
 26427            IconEmoji = "fa-solid fa-book-open"
 26428        };
 429
 26430        var sessions = sessionsByArc.TryGetValue(arc.Id, out var sessionList)
 26431            ? sessionList
 26432            : new List<SessionTreeDto>();
 433
 26434        if (sessions.Count > 0)
 435        {
 20436            foreach (var session in sessions
 4437                .OrderBy(s => s.Name, StringComparer.OrdinalIgnoreCase)
 4438                .ThenBy(s => s.Id))
 439            {
 6440                var sessionNode = BuildSessionNode(session, arc, worldArticles, articleIndex, nodeIndex);
 6441                arcNode.Children.Add(sessionNode);
 442            }
 443        }
 444
 26445        arcNode.ChildCount = arcNode.Children.Count;
 446
 26447        return arcNode;
 448    }
 449
 450    private TreeNode BuildSessionNode(
 451        SessionTreeDto session,
 452        ArcDto arc,
 453        List<ArticleTreeDto> worldArticles,
 454        Dictionary<Guid, ArticleTreeDto> articleIndex,
 455        TreeNodeIndex nodeIndex)
 456    {
 6457        var sessionNode = new TreeNode
 6458        {
 6459            Id = session.Id,
 6460            NodeType = TreeNodeType.Session,
 6461            Title = session.Name,
 6462            CampaignId = arc.CampaignId,
 6463            ArcId = arc.Id,
 6464            IconEmoji = "fa-solid fa-calendar-day",
 6465            HasAISummary = session.HasAiSummary
 6466        };
 467
 6468        nodeIndex.AddNode(sessionNode);
 469
 6470        var sessionNotes = worldArticles
 6471            .Where(a => a.Type == ArticleType.SessionNote && a.SessionId == session.Id)
 6472            .OrderBy(a => a.Title)
 6473            .ToList();
 474
 6475        if (sessionNotes.Count == 0)
 476        {
 4477            sessionNode.ChildCount = 0;
 4478            return sessionNode;
 479        }
 480
 2481        var sessionNoteIds = sessionNotes.Select(n => n.Id).ToHashSet();
 2482        var rootSessionNotes = sessionNotes
 2483            .Where(n => !n.ParentId.HasValue || !sessionNoteIds.Contains(n.ParentId.Value))
 2484            .ToList();
 485
 8486        foreach (var note in rootSessionNotes)
 487        {
 2488            var noteNode = BuildArticleNodeWithChildren(note, sessionNotes, articleIndex, nodeIndex);
 2489            noteNode.ParentId = sessionNode.Id;
 2490            sessionNode.Children.Add(noteNode);
 491        }
 492
 2493        sessionNode.ChildCount = sessionNode.Children.Count;
 2494        return sessionNode;
 495    }
 496
 497    private TreeNode BuildArticleNodeWithChildren(
 498        ArticleTreeDto article,
 499        List<ArticleTreeDto> allArticles,
 500        Dictionary<Guid, ArticleTreeDto> articleIndex,
 501        TreeNodeIndex nodeIndex)
 502    {
 32503        var node = CreateArticleNode(article);
 32504        nodeIndex.AddNode(node);
 505
 32506        var children = allArticles
 32507            .Where(a => a.ParentId == article.Id)
 32508            .OrderBy(a => a.Title)
 32509            .ToList();
 510
 68511        foreach (var child in children)
 512        {
 2513            var childNode = BuildArticleNodeWithChildren(child, allArticles, articleIndex, nodeIndex);
 2514            childNode.ParentId = node.Id;
 2515            node.Children.Add(childNode);
 516        }
 517
 32518        node.ChildCount = node.Children.Count;
 519
 32520        return node;
 521    }
 522
 523    /// <summary>
 524    /// Creates a TreeNode from an ArticleTreeDto.
 525    /// </summary>
 526    public static TreeNode CreateArticleNode(ArticleTreeDto article)
 527    {
 34528        return new TreeNode
 34529        {
 34530            Id = article.Id,
 34531            NodeType = TreeNodeType.Article,
 34532            ArticleType = article.Type,
 34533            Title = article.Title,
 34534            Slug = article.Slug,
 34535            IconEmoji = article.IconEmoji,
 34536            ParentId = article.ParentId,
 34537            WorldId = article.WorldId,
 34538            CampaignId = article.CampaignId,
 34539            ArcId = article.ArcId,
 34540            ChildCount = article.ChildCount,
 34541            Visibility = article.Visibility,
 34542            HasAISummary = article.HasAISummary
 34543        };
 544    }
 545
 546    /// <summary>
 547    /// Creates a virtual group node.
 548    /// </summary>
 549    public static TreeNode CreateVirtualGroupNode(VirtualGroupType groupType, string title, Guid? worldId)
 550    {
 158551        return new TreeNode
 158552        {
 158553            Id = Guid.NewGuid(),
 158554            NodeType = TreeNodeType.VirtualGroup,
 158555            VirtualGroupType = groupType,
 158556            Title = title,
 158557            WorldId = worldId
 158558        };
 559    }
 560
 561    /// <summary>
 562    /// Gets the appropriate icon for a document based on its content type.
 563    /// </summary>
 564    public static string GetDocumentIcon(string contentType)
 565    {
 9566        return contentType.ToLowerInvariant() switch
 9567        {
 2568            "application/pdf" => "fa-solid fa-file-pdf",
 1569            "application/vnd.openxmlformats-officedocument.wordprocessingml.document" => "fa-solid fa-file-word",
 1570            "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" => "fa-solid fa-file-excel",
 1571            "application/vnd.openxmlformats-officedocument.presentationml.presentation" => "fa-solid fa-file-powerpoint"
 1572            "text/plain" => "fa-solid fa-file-lines",
 1573            "text/markdown" => "fa-solid fa-file-lines",
 3574            string ct when ct.StartsWith("image/") => "fa-solid fa-file-image",
 1575            _ => "fa-solid fa-file"
 9576        };
 577    }
 578}

Methods/Properties

.ctor(Chronicis.Client.Services.IArticleApiService,Chronicis.Client.Services.IWorldApiService,Chronicis.Client.Services.ICampaignApiService,Chronicis.Client.Services.IArcApiService,Chronicis.Client.Services.ISessionApiService,Chronicis.Client.Services.IMapApiService,Microsoft.Extensions.Logging.ILogger)
BuildWorldNode(Chronicis.Shared.DTOs.WorldDto,Chronicis.Shared.DTOs.WorldDetailDto,System.Collections.Generic.List`1<Chronicis.Shared.DTOs.ArticleTreeDto>,System.Collections.Generic.Dictionary`2<System.Guid,Chronicis.Shared.DTOs.ArticleTreeDto>,System.Collections.Generic.Dictionary`2<System.Guid,System.Collections.Generic.List`1<Chronicis.Shared.DTOs.ArcDto>>,System.Collections.Generic.Dictionary`2<System.Guid,System.Collections.Generic.List`1<Chronicis.Shared.DTOs.Sessions.SessionTreeDto>>,System.Collections.Generic.List`1<Chronicis.Shared.DTOs.WorldLinkDto>,System.Collections.Generic.List`1<Chronicis.Shared.DTOs.WorldDocumentDto>,System.Collections.Generic.List`1<Chronicis.Shared.DTOs.Maps.MapSummaryDto>,Chronicis.Client.Services.Tree.TreeNodeIndex)
BuildCampaignNode(Chronicis.Shared.DTOs.CampaignDto,System.Collections.Generic.List`1<Chronicis.Shared.DTOs.ArcDto>,System.Collections.Generic.List`1<Chronicis.Shared.DTOs.ArticleTreeDto>,System.Collections.Generic.Dictionary`2<System.Guid,Chronicis.Shared.DTOs.ArticleTreeDto>,System.Collections.Generic.Dictionary`2<System.Guid,System.Collections.Generic.List`1<Chronicis.Shared.DTOs.Sessions.SessionTreeDto>>,Chronicis.Client.Services.Tree.TreeNodeIndex)
BuildArcNode(Chronicis.Shared.DTOs.ArcDto,System.Collections.Generic.List`1<Chronicis.Shared.DTOs.ArticleTreeDto>,System.Collections.Generic.Dictionary`2<System.Guid,Chronicis.Shared.DTOs.ArticleTreeDto>,System.Collections.Generic.Dictionary`2<System.Guid,System.Collections.Generic.List`1<Chronicis.Shared.DTOs.Sessions.SessionTreeDto>>,Chronicis.Client.Services.Tree.TreeNodeIndex)
BuildSessionNode(Chronicis.Shared.DTOs.Sessions.SessionTreeDto,Chronicis.Shared.DTOs.ArcDto,System.Collections.Generic.List`1<Chronicis.Shared.DTOs.ArticleTreeDto>,System.Collections.Generic.Dictionary`2<System.Guid,Chronicis.Shared.DTOs.ArticleTreeDto>,Chronicis.Client.Services.Tree.TreeNodeIndex)
BuildArticleNodeWithChildren(Chronicis.Shared.DTOs.ArticleTreeDto,System.Collections.Generic.List`1<Chronicis.Shared.DTOs.ArticleTreeDto>,System.Collections.Generic.Dictionary`2<System.Guid,Chronicis.Shared.DTOs.ArticleTreeDto>,Chronicis.Client.Services.Tree.TreeNodeIndex)
CreateArticleNode(Chronicis.Shared.DTOs.ArticleTreeDto)
CreateVirtualGroupNode(Chronicis.Client.Models.VirtualGroupType,System.String,System.Nullable`1<System.Guid>)
GetDocumentIcon(System.String)