229 lines
8.2 KiB
C#
229 lines
8.2 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"),
|
|
_ => 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 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} • {RollVisibilityLabel}" : "Select a character to enable";
|
|
|
|
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(RollVisibility, "private", StringComparison.OrdinalIgnoreCase) ? "private" : "public";
|
|
|
|
private string CustomRollExpression
|
|
{
|
|
get => CustomRollState.Model.Expression;
|
|
set
|
|
{
|
|
CustomRollState.Model.Expression = value;
|
|
if (HasCustomRollError)
|
|
CustomRollState.ResetValidation();
|
|
}
|
|
}
|
|
} |