233 lines
8.6 KiB
C#
233 lines
8.6 KiB
C#
using System.Diagnostics.CodeAnalysis;
|
|
using Microsoft.AspNetCore.Components;
|
|
using Microsoft.JSInterop;
|
|
using RpgRoller.Contracts;
|
|
|
|
namespace RpgRoller.Components.Pages.HomeControls;
|
|
|
|
[ExcludeFromCodeCoverage]
|
|
public partial class CampaignLogPanel
|
|
{
|
|
private sealed record EventBadgeView(string Label, string Tone);
|
|
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
var currentLastRollId = CampaignLog.LastOrDefault()?.RollId;
|
|
if (IsCampaignDataLoading || CampaignLog.Count == 0)
|
|
{
|
|
LastRenderedLogCount = CampaignLog.Count;
|
|
LastRenderedLogRollId = currentLastRollId;
|
|
return;
|
|
}
|
|
|
|
if (firstRender || CampaignLog.Count > LastRenderedLogCount || currentLastRollId != LastRenderedLogRollId)
|
|
{
|
|
try
|
|
{
|
|
await JS.InvokeVoidAsync("rpgRollerApi.scrollElementToBottom", LogFeedRef);
|
|
}
|
|
catch (JSDisconnectedException)
|
|
{
|
|
}
|
|
catch (InvalidOperationException ex) when (ex.Message.Contains("statically rendered",
|
|
StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
}
|
|
}
|
|
|
|
LastRenderedLogCount = CampaignLog.Count;
|
|
LastRenderedLogRollId = currentLastRollId;
|
|
}
|
|
|
|
private async Task SubmitCustomRollAsync()
|
|
{
|
|
CustomRollState.ResetValidation();
|
|
|
|
var expression = CustomRollState.Model.Expression.Trim();
|
|
if (string.IsNullOrWhiteSpace(expression))
|
|
{
|
|
SetCustomRollError("Enter a roll expression first.");
|
|
return;
|
|
}
|
|
|
|
if (!SelectedCharacterId.HasValue)
|
|
{
|
|
SetCustomRollError("Select a character to make a custom roll.");
|
|
return;
|
|
}
|
|
|
|
IsSubmittingCustomRoll = true;
|
|
try
|
|
{
|
|
var roll = await ApiClient.RequestAsync<RollResult>("POST",
|
|
$"/api/characters/{SelectedCharacterId.Value}/custom-rolls", new
|
|
{
|
|
expression,
|
|
visibility = NormalizedRollVisibility
|
|
});
|
|
|
|
CustomRollState.Model.Expression = string.Empty;
|
|
CustomRollState.ResetValidation();
|
|
CustomRollInputVersion += 1;
|
|
await CustomRollCreated.InvokeAsync(roll);
|
|
await JS.InvokeVoidAsync("rpgRollerApi.clearInputValue", CustomRollInputRef);
|
|
await InvokeAsync(StateHasChanged);
|
|
}
|
|
catch (ApiRequestException ex) when
|
|
(string.Equals(ex.ErrorCode, "invalid_expression", StringComparison.Ordinal))
|
|
{
|
|
SetCustomRollError(ex.Message);
|
|
await InvokeAsync(StateHasChanged);
|
|
}
|
|
catch (ApiRequestException ex)
|
|
{
|
|
await ErrorOccurred.InvokeAsync(ex.Message);
|
|
}
|
|
finally
|
|
{
|
|
IsSubmittingCustomRoll = false;
|
|
}
|
|
}
|
|
|
|
private void SetCustomRollError(string message)
|
|
{
|
|
CustomRollState.Errors["expression"] = message;
|
|
}
|
|
|
|
private static IReadOnlyList<EventBadgeView> GetEventBadges(CampaignLogListEntry entry)
|
|
{
|
|
return (entry.EventBadges ?? []).Select(ToEventBadgeView).Where(badge => badge is not null)
|
|
.Cast<EventBadgeView>().ToArray();
|
|
}
|
|
|
|
private static bool HasSummary(CampaignLogListEntry entry)
|
|
{
|
|
return (entry.EventBadges?.Length ?? 0) > 0 || !string.IsNullOrWhiteSpace(entry.SummaryText);
|
|
}
|
|
|
|
private static EventBadgeView? ToEventBadgeView(string code)
|
|
{
|
|
return code switch
|
|
{
|
|
"w6" => new("Wild 6", "positive"),
|
|
"w1" => new("Wild 1", "danger"),
|
|
"n20" => new("Nat 20", "positive"),
|
|
"n1" => new("Nat 1", "danger"),
|
|
"rf" => new("Fumble", "danger"),
|
|
"r100" => new("100", "rare"),
|
|
"r66" => new("66", "rare"),
|
|
"rs5" => new("Retry +5", "rare"),
|
|
"rs10" => new("Retry +10", "rare"),
|
|
_ => null
|
|
};
|
|
}
|
|
|
|
private static string LogEntryCssClass(CampaignLogListEntry entry, bool isExpanded, bool isFresh)
|
|
{
|
|
var classes = new List<string> { entry.VisibilityStyle };
|
|
if (isExpanded)
|
|
classes.Add("expanded");
|
|
|
|
if (isFresh)
|
|
classes.Add("fresh");
|
|
|
|
return string.Join(" ", classes);
|
|
}
|
|
|
|
[Inject] private IJSRuntime JS { get; set; } = null!;
|
|
|
|
[Inject] private RpgRollerApiClient ApiClient { get; set; } = null!;
|
|
|
|
private ElementReference LogPanelRef { get; set; }
|
|
private ElementReference LogFeedRef { get; set; }
|
|
private ElementReference CustomRollInputRef { get; set; }
|
|
private int LastRenderedLogCount { get; set; }
|
|
private Guid? LastRenderedLogRollId { get; set; }
|
|
private int CustomRollInputVersion { get; set; }
|
|
private FormState<CustomRollFormModel> CustomRollState { get; } = new();
|
|
private bool IsSubmittingCustomRoll { get; set; }
|
|
|
|
[Parameter] public bool IsCampaignDataLoading { get; set; }
|
|
|
|
[Parameter] public IReadOnlyList<CampaignLogListEntry> CampaignLog { get; set; } = [];
|
|
|
|
[Parameter] public Guid? ExpandedRollId { get; set; }
|
|
|
|
[Parameter] public Guid? FreshRollId { get; set; }
|
|
|
|
[Parameter] public EventCallback<Guid> ToggleRollDetailRequested { get; set; }
|
|
|
|
[Parameter] public Func<Guid, CampaignRollDetail?> ResolveRollDetail { get; set; } = _ => null;
|
|
|
|
[Parameter] public Func<Guid, bool> IsRollDetailLoading { get; set; } = _ => false;
|
|
|
|
[Parameter] public Func<Guid, string?> GetRollDetailError { get; set; } = _ => null;
|
|
|
|
[Parameter] public Guid? SelectedCharacterId { get; set; }
|
|
|
|
[Parameter] public string? SelectedCharacterName { get; set; }
|
|
|
|
[Parameter] public string SelectedCampaignRulesetId { get; set; } = string.Empty;
|
|
|
|
[Parameter] public string RollVisibility { get; set; } = "public";
|
|
|
|
[Parameter] public Func<string>? ResolveRollVisibility { get; set; }
|
|
|
|
[Parameter] public bool IsMutating { get; set; }
|
|
|
|
[Parameter] public EventCallback<RollResult> CustomRollCreated { get; set; }
|
|
|
|
[Parameter] public EventCallback<string> ErrorOccurred { get; set; }
|
|
|
|
private bool HasCustomRollError => CustomRollState.Errors.ContainsKey("expression");
|
|
private string? CustomRollErrorMessage => CustomRollState.Errors.GetValueOrDefault("expression");
|
|
|
|
private bool IsCustomRollDisabled =>
|
|
IsCampaignDataLoading || IsMutating || IsSubmittingCustomRoll || !SelectedCharacterId.HasValue;
|
|
|
|
private string CustomRollInputCssClass => HasCustomRollError ? "custom-roll-input error" : "custom-roll-input";
|
|
private string? CustomRollInputTitle => HasCustomRollError ? CustomRollErrorMessage : null;
|
|
private string CustomRollErrorElementId => "custom-roll-expression-error";
|
|
private string? CustomRollInputDescribedBy => HasCustomRollError ? CustomRollErrorElementId : null;
|
|
|
|
private string CustomRollPlaceholder => SelectedCampaignRulesetId.ToLowerInvariant() switch
|
|
{
|
|
RulesetFormHelpers.RulesetIds.D6 => "e.g. 5D+4",
|
|
RulesetFormHelpers.RulesetIds.Dnd5e => "e.g. 2d12+2",
|
|
RulesetFormHelpers.RulesetIds.Rolemaster => "e.g. d10, 15d10, d100!+85",
|
|
_ => "Enter a roll expression"
|
|
};
|
|
|
|
private string CustomRollStatusText =>
|
|
SelectedCharacterId.HasValue && !string.IsNullOrWhiteSpace(SelectedCharacterName)
|
|
? $"For {SelectedCharacterName} • Uses {RollVisibilityLabel.ToLowerInvariant()} visibility"
|
|
: $"Select a character to enable • {RollVisibilityLabel} visibility selected";
|
|
|
|
private string CustomRollHelpText => SelectedCampaignRulesetId.ToLowerInvariant() switch
|
|
{
|
|
RulesetFormHelpers.RulesetIds.D6 =>
|
|
"Uses the campaign ruleset. D6 custom rolls use one wild die and allow fumbles.",
|
|
RulesetFormHelpers.RulesetIds.Rolemaster =>
|
|
$"{RulesetFormHelpers.RolemasterExampleText()}. Logged for the selected character.",
|
|
_ => "Uses the selected campaign ruleset and current visibility."
|
|
};
|
|
|
|
private string RollVisibilityLabel =>
|
|
string.Equals(NormalizedRollVisibility, "private", StringComparison.OrdinalIgnoreCase) ? "Private" : "Public";
|
|
|
|
private string NormalizedRollVisibility =>
|
|
string.Equals(ResolveRollVisibility?.Invoke() ?? RollVisibility, "private", StringComparison.OrdinalIgnoreCase)
|
|
? "private"
|
|
: "public";
|
|
|
|
private string CustomRollExpression
|
|
{
|
|
get => CustomRollState.Model.Expression;
|
|
set
|
|
{
|
|
CustomRollState.Model.Expression = value;
|
|
if (HasCustomRollError)
|
|
CustomRollState.ResetValidation();
|
|
}
|
|
}
|
|
} |