| | | 1 | | @using Chronicis.Shared.DTOs |
| | | 2 | | |
| | | 3 | | @* Refactored ExternalLinksPanel - Now accepts data as parameters instead of fetching directly *@ |
| | | 4 | | |
| | | 5 | | <div class="links-panel"> |
| | | 6 | | <div class="links-panel-header"> |
| | | 7 | | <span class="links-panel-title">External Resources</span> |
| | 8 | 8 | | @if (IsLoading) |
| | | 9 | | { |
| | | 10 | | <span class="links-panel-loading">...</span> |
| | | 11 | | } |
| | 8 | 12 | | <span class="links-panel-count">@ExternalLinks.Count</span> |
| | | 13 | | </div> |
| | | 14 | | |
| | 8 | 15 | | @if (ExternalLinks.Any()) |
| | | 16 | | { |
| | | 17 | | <div class="external-links-container"> |
| | 39 | 18 | | @foreach (var group in ExternalLinks.GroupBy(el => el.Source)) |
| | | 19 | | { |
| | | 20 | | <div class="external-links-source-group"> |
| | 9 | 21 | | <div class="external-links-source-badge">@GetSourceDisplayName(group.Key)</div> |
| | | 22 | | <div class="links-chip-container"> |
| | 40 | 23 | | @foreach (var link in group) |
| | | 24 | | { |
| | | 25 | | <span class="external-link-chip" |
| | | 26 | | title="@($"{link.DisplayTitle} ({link.Source})")"> |
| | 11 | 27 | | @link.DisplayTitle |
| | | 28 | | </span> |
| | | 29 | | } |
| | | 30 | | </div> |
| | | 31 | | </div> |
| | | 32 | | } |
| | | 33 | | </div> |
| | | 34 | | } |
| | 3 | 35 | | else if (!IsLoading) |
| | | 36 | | { |
| | | 37 | | <div class="links-panel-empty">No external resources</div> |
| | | 38 | | } |
| | | 39 | | </div> |
| | | 40 | | |
| | | 41 | | @code { |
| | | 42 | | /// <summary> |
| | | 43 | | /// The list of external links to display. |
| | | 44 | | /// Parent component is responsible for loading this data. |
| | | 45 | | /// </summary> |
| | | 46 | | [Parameter, EditorRequired] |
| | 37 | 47 | | public List<ArticleExternalLinkDto> ExternalLinks { get; set; } = new(); |
| | | 48 | | |
| | | 49 | | /// <summary> |
| | | 50 | | /// Whether data is currently being loaded. |
| | | 51 | | /// Controls the loading indicator display. |
| | | 52 | | /// </summary> |
| | | 53 | | [Parameter] |
| | 13 | 54 | | public bool IsLoading { get; set; } |
| | | 55 | | |
| | | 56 | | private static string GetSourceDisplayName(string source) |
| | | 57 | | { |
| | 9 | 58 | | return source.ToUpperInvariant() switch |
| | 9 | 59 | | { |
| | 5 | 60 | | "SRD" => "SRD", |
| | 0 | 61 | | "SRD14" => "SRD 1.4", |
| | 0 | 62 | | "SRD24" => "SRD 2.4", |
| | 4 | 63 | | "OPEN5E" => "Open5e", |
| | 0 | 64 | | "ROS" => "Ruins of Symbaroum", |
| | 0 | 65 | | _ => source.ToUpperInvariant() |
| | 9 | 66 | | }; |
| | | 67 | | } |
| | | 68 | | } |
| | | 69 | | |
| | | 70 | | <style> |
| | | 71 | | .links-panel { |
| | | 72 | | padding: 12px 16px; |
| | | 73 | | } |
| | | 74 | | |
| | | 75 | | .links-panel-header { |
| | | 76 | | display: flex; |
| | | 77 | | align-items: center; |
| | | 78 | | gap: 8px; |
| | | 79 | | margin-bottom: 10px; |
| | | 80 | | } |
| | | 81 | | |
| | | 82 | | .links-panel-title { |
| | | 83 | | font-size: 13px; |
| | | 84 | | font-weight: 600; |
| | | 85 | | color: var(--mud-palette-text-secondary); |
| | | 86 | | text-transform: uppercase; |
| | | 87 | | letter-spacing: 0.5px; |
| | | 88 | | } |
| | | 89 | | |
| | | 90 | | .links-panel-loading { |
| | | 91 | | font-size: 12px; |
| | | 92 | | color: #C4AF8E; |
| | | 93 | | } |
| | | 94 | | |
| | | 95 | | .links-panel-count { |
| | | 96 | | font-size: 11px; |
| | | 97 | | background: rgba(196, 175, 142, 0.2); |
| | | 98 | | color: var(--mud-palette-text-secondary); |
| | | 99 | | padding: 2px 6px; |
| | | 100 | | border-radius: 10px; |
| | | 101 | | margin-left: auto; |
| | | 102 | | } |
| | | 103 | | |
| | | 104 | | .external-links-container { |
| | | 105 | | display: flex; |
| | | 106 | | flex-direction: column; |
| | | 107 | | gap: 12px; |
| | | 108 | | } |
| | | 109 | | |
| | | 110 | | .external-links-source-group { |
| | | 111 | | display: flex; |
| | | 112 | | flex-direction: column; |
| | | 113 | | gap: 6px; |
| | | 114 | | } |
| | | 115 | | |
| | | 116 | | .external-links-source-badge { |
| | | 117 | | font-size: 10px; |
| | | 118 | | font-weight: 600; |
| | | 119 | | color: var(--mud-palette-text-secondary); |
| | | 120 | | text-transform: uppercase; |
| | | 121 | | letter-spacing: 0.8px; |
| | | 122 | | padding: 2px 6px; |
| | | 123 | | background: rgba(196, 175, 142, 0.15); |
| | | 124 | | border-radius: 3px; |
| | | 125 | | width: fit-content; |
| | | 126 | | } |
| | | 127 | | |
| | | 128 | | .links-chip-container { |
| | | 129 | | display: flex; |
| | | 130 | | flex-wrap: wrap; |
| | | 131 | | gap: 6px; |
| | | 132 | | } |
| | | 133 | | |
| | | 134 | | .external-link-chip { |
| | | 135 | | display: inline-block; |
| | | 136 | | padding: 4px 10px; |
| | | 137 | | font-size: 12px; |
| | | 138 | | background: rgba(100, 150, 200, 0.1); |
| | | 139 | | border: 1px solid rgba(100, 150, 200, 0.25); |
| | | 140 | | border-radius: 12px; |
| | | 141 | | color: var(--mud-palette-text-primary); |
| | | 142 | | max-width: 180px; |
| | | 143 | | overflow: hidden; |
| | | 144 | | text-overflow: ellipsis; |
| | | 145 | | white-space: nowrap; |
| | | 146 | | } |
| | | 147 | | |
| | | 148 | | .external-link-chip:hover { |
| | | 149 | | background: rgba(100, 150, 200, 0.2); |
| | | 150 | | border-color: rgba(100, 150, 200, 0.4); |
| | | 151 | | } |
| | | 152 | | |
| | | 153 | | .links-panel-empty { |
| | | 154 | | font-size: 12px; |
| | | 155 | | color: var(--mud-palette-text-secondary); |
| | | 156 | | opacity: 0.6; |
| | | 157 | | font-style: italic; |
| | | 158 | | } |
| | | 159 | | </style> |