Skip to content

Commit

Permalink
Manage loading state
Browse files Browse the repository at this point in the history
  • Loading branch information
dennisreimann committed Aug 21, 2023
1 parent a470019 commit 5bc207f
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 87 deletions.
13 changes: 9 additions & 4 deletions BTCPayApp.Core/BTCPayAppConfigManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ public class BTCPayAppConfigManager : IHostedService
public BTCPayPairConfig? PairConfig { get; private set; }
public WalletConfig? WalletConfig { get; private set; }

private const string PairConfigKey = "pairconfig";
private const string WalletConfigKey = "walletconfig";

public BTCPayAppConfigManager(IConfigProvider configProvider, ILogger<BTCPayAppConfigManager> logger)
{
_configProvider = configProvider;
Expand All @@ -40,13 +43,14 @@ public Task StartAsync(CancellationToken cancellationToken)

private async Task LoadPairConfig()
{
PairConfig = await _configProvider.Get<BTCPayPairConfig>("pairconfig");
PairConfig = await _configProvider.Get<BTCPayPairConfig>(PairConfigKey);
PairConfigUpdated?.Invoke(this, PairConfig);
_pairConfigLoaded.TrySetResult();
}

private async Task LoadWalletConfig()
{
WalletConfig = await _configProvider.Get<WalletConfig>("walletconfig");
WalletConfig = await _configProvider.Get<WalletConfig>(WalletConfigKey);
WalletConfigUpdated?.Invoke(this, WalletConfig);
_walletConfigLoaded.TrySetResult();
}
Expand All @@ -55,15 +59,16 @@ public async Task UpdateConfig(BTCPayPairConfig? config)
{
if (config == PairConfig)
return;
await _configProvider.Set("pairconfig", config);
await _configProvider.Set(PairConfigKey, config);
PairConfig = config;
PairConfigUpdated?.Invoke(this, PairConfig);
}

public async Task UpdateConfig(WalletConfig? config)
{
if (config == WalletConfig)
return;
await _configProvider.Set("walletconfig", config);
await _configProvider.Set(WalletConfigKey, config);
WalletConfig = config;
WalletConfigUpdated?.Invoke(this, WalletConfig);
}
Expand Down
9 changes: 1 addition & 8 deletions BTCPayApp.Server/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

using BTCPayApp.Core;
using BTCPayApp.Desktop;
using BTCPayApp.UI;
Expand All @@ -12,25 +11,19 @@
builder.Services.AddBTCPayAppUIServices();
builder.Services.ConfigureBTCPayAppCore();
builder.Services.ConfigureBTCPayAppDesktop();

builder.Services.AddLogging();
var app = builder.Build();

// Configure the HTTP request pipeline.
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

app.UseHttpsRedirection();

app.UseStaticFiles();

app.UseRouting();

app.MapBlazorHub();
app.MapFallbackToPage("/_Host");

app.Run();
31 changes: 21 additions & 10 deletions BTCPayApp.UI/Features/RootState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,57 +8,66 @@
namespace BTCPayApp.UI.Features;

[FeatureState]
public record RootState(bool Loading, BTCPayPairConfig? PairConfig, WalletConfig? WalletConfig,
public record RootState(HashSet<RootState.LoadingHandles> Loading, BTCPayPairConfig? PairConfig, WalletConfig? WalletConfig,
HubConnectionState? BTCPayServerConnectionState, LightningNodeState LightningNodeState)
{
public RootState() : this(true, null, null, null, LightningNodeState.NotConfigured)
public enum LoadingHandles
{
UiState,
PairConfig,
WalletConfig
}

public RootState() : this(new HashSet<LoadingHandles>(), null, null, null, LightningNodeState.NotConfigured)
{
}

public record PairConfigLoadedAction(BTCPayPairConfig? Config);

public record WalletConfigLoadedAction(WalletConfig? Config);


public record LoadingAction(bool Loading);
public record LoadingAction(LoadingHandles LoadingHandle, bool IsLoading);

protected class LoadingReducer : Reducer<RootState, LoadingAction>
{
public override RootState Reduce(RootState state, LoadingAction action)
{
return state with {Loading = action.Loading};
var loading = state.Loading;
var handle = action.LoadingHandle;
_ = action.IsLoading ? loading.Add(handle) : loading.Remove(handle);
return state with { Loading = loading };
}
}

protected class PairConfigLoadedReducer : Reducer<RootState, PairConfigLoadedAction>
{
public override RootState Reduce(RootState state, PairConfigLoadedAction action)
{
return state with {Loading = false, PairConfig = action.Config};
return state with { PairConfig = action.Config };
}
}

protected class WalletConfigLoadedReducer : Reducer<RootState, WalletConfigLoadedAction>
{
public override RootState Reduce(RootState state, WalletConfigLoadedAction action)
{
return state with {Loading = false, WalletConfig = action.Config};
return state with { WalletConfig = action.Config };
}
}

protected class BTCPayConnectionUpdatedReducer : Reducer<RootState, BTCPayConnectionUpdatedAction>
{
public override RootState Reduce(RootState state, BTCPayConnectionUpdatedAction action)
{
return state with {Loading = false, BTCPayServerConnectionState = action.ConnectionState};
return state with { BTCPayServerConnectionState = action.ConnectionState };
}
}

protected class LightningNodeStateUpdatedReducer : Reducer<RootState, LightningNodeStateUpdatedAction>
{
public override RootState Reduce(RootState state, LightningNodeStateUpdatedAction action)
{
return state with {Loading = false, LightningNodeState = action.State};
return state with { LightningNodeState = action.State };
}
}

Expand All @@ -84,11 +93,13 @@ public override async Task HandleAsync(PairConfigLoadedAction action, IDispatche
case null when _state.Value.WalletConfig is null && !_navigationManager.Uri.EndsWith(Routes.Pair):
dispatcher.Dispatch(new GoAction(Routes.FirstRun));
break;

//if wallet is configured and no pair, go to the pair page
case null when _state.Value.WalletConfig?.StandaloneMode is not true &&
!_navigationManager.Uri.EndsWith(Routes.Pair):
dispatcher.Dispatch(new GoAction(Routes.Pair));
break;

//if wallet is not configured and pair is configured, go to the wallet page
case not null when _state.Value.WalletConfig is null &&
!_navigationManager.Uri.EndsWith(Routes.WalletSetup):
Expand Down Expand Up @@ -119,7 +130,7 @@ public override async Task HandleAsync(WalletConfigLoadedAction action, IDispatc
if (action.Config is null && _state.Value.PairConfig is not null &&
!_navigationManager.Uri.EndsWith(Routes.WalletSetup))
dispatcher.Dispatch(new GoAction(Routes.FirstRun));

if (action.Config is not null && _state.Value.PairConfig is not null &&
_navigationManager.Uri.EndsWith(Routes.WalletSetup))
dispatcher.Dispatch(new GoAction(Routes.Splash));
Expand Down
70 changes: 38 additions & 32 deletions BTCPayApp.UI/Pages/Dashboard.razor
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,47 @@

<MudText Typo="Typo.h5" Class="mb-4">Dashboard</MudText>

@if (!State.Value.Loading)
@if (State.Value.WalletConfig is not null)
{
@if (State.Value.WalletConfig is not null)
{
<MudAlert Severity="Severity.Success">Your wallet is configured.</MudAlert>
}
else
{
<MudAlert Severity="Severity.Error">
<div class="d-flex align-center justify-space-between gap-3">
Your wallet is not configured.
<MudButton Href="@Routes.WalletSetup" Variant="Variant.Text" Color="Color.Error" Size="Size.Small">
Set up a wallet
</MudButton>
</div>
</MudAlert>
}
@if (State.Value.PairConfig is not null)
{
<MudAlert Severity="Severity.Success">Your BTCPay Server is paired. Connection status is: @ConnectionStatus</MudAlert>
}
else
{
<MudAlert Severity="Severity.Error">
<div class="d-flex align-center justify-space-between gap-3">
No BTCPay Server paired.
<MudButton Href="@Routes.Pair" Variant="Variant.Text" Color="Color.Error" Size="Size.Small">
Configure connection
</MudButton>
</div>
</MudAlert>
}
<MudAlert Severity="Severity.Success">Your wallet is configured.</MudAlert>
}
else if (State.Value.Loading.Contains(RootState.LoadingHandles.WalletConfig))
{
<MudAlert Severity="Severity.Info">Wallet configuration loading.</MudAlert>
}
else
{
<MudAlert Severity="Severity.Error">
<div class="d-flex align-center justify-space-between gap-3">
Your wallet is not configured.
<MudButton Href="@Routes.WalletSetup" Variant="Variant.Text" Color="Color.Error" Size="Size.Small">
Set up a wallet
</MudButton>
</div>
</MudAlert>
}

<MudAlert Severity="Severity.Normal">Lightning Node: @State.Value.LightningNodeState</MudAlert>
@if (State.Value.PairConfig is not null)
{
<MudAlert Severity="Severity.Success">Your BTCPay Server is paired. Connection status is: @ConnectionStatus</MudAlert>
}
else if (State.Value.Loading.Contains(RootState.LoadingHandles.PairConfig))
{
<MudAlert Severity="Severity.Info">Pairing configuration loading.</MudAlert>
}
else
{
<MudAlert Severity="Severity.Error">
<div class="d-flex align-center justify-space-between gap-3">
No BTCPay Server paired.
<MudButton Href="@Routes.Pair" Variant="Variant.Text" Color="Color.Error" Size="Size.Small">
Configure connection
</MudButton>
</div>
</MudAlert>
}

<MudAlert Severity="Severity.Normal">Lightning Node: @State.Value.LightningNodeState</MudAlert>

@code {
private string ConnectionStatus => State.Value.BTCPayServerConnectionState?.ToString() ?? "Not connected";
Expand Down
2 changes: 1 addition & 1 deletion BTCPayApp.UI/Shared/MainLayout.razor
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
</MudNavMenu>
</MudDrawer>
<MudMainContent>
<MudProgressLinear Color="Color.Primary" Indeterminate="true" Size="Size.Small" Class="@(RootState.Value.Loading ? null : "invisible")"/>
<MudProgressLinear Color="Color.Primary" Indeterminate="true" Size="Size.Small" Class="@(RootState.Value.Loading.Count > 0 ? null : "invisible")"/>
<MudContainer Fixed="true" Class="py-5">
@Body
</MudContainer>
Expand Down
33 changes: 21 additions & 12 deletions BTCPayApp.UI/StateMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ public class StateMiddleware : Middleware

private const string UiStateConfigKey = "uistate";

public StateMiddleware(IConfigProvider configProvider,
BTCPayAppConfigManager btcPayAppConfigManager,
BTCPayConnection btcPayConnection,
public StateMiddleware(IConfigProvider configProvider,
BTCPayAppConfigManager btcPayAppConfigManager,
BTCPayConnection btcPayConnection,
LightningNodeManager lightningNodeManager)
{
_configProvider = configProvider;
Expand All @@ -29,16 +29,17 @@ public override async Task InitializeAsync(IDispatcher dispatcher, IStore store)
{
if (store.Features.TryGetValue(typeof(UIState).FullName, out var uiStateFeature))
{
dispatcher.Dispatch(new RootState.LoadingAction(RootState.LoadingHandles.UiState, true));
var existing = await _configProvider.Get<UIState>(UiStateConfigKey);
if (existing is not null)
{
uiStateFeature.RestoreState(existing);
}

uiStateFeature.StateChanged += async (sender, args) =>
{
await _configProvider.Set(UiStateConfigKey, (UIState)uiStateFeature.GetState());
};
dispatcher.Dispatch(new RootState.LoadingAction(RootState.LoadingHandles.UiState, false));
}

await base.InitializeAsync(dispatcher, store);
Expand All @@ -48,20 +49,28 @@ public override async Task InitializeAsync(IDispatcher dispatcher, IStore store)

private void ListenIn(IDispatcher dispatcher)
{
dispatcher.Dispatch(new RootState.LoadingAction(true));
_btcPayAppConfigManager.PairConfigUpdated +=
(_, config) => dispatcher.Dispatch(new RootState.PairConfigLoadedAction(config));
_btcPayAppConfigManager.PairConfigUpdated += (_, config) =>
dispatcher.Dispatch(new RootState.PairConfigLoadedAction(config));

_btcPayAppConfigManager.WalletConfigUpdated += (_, config) =>
dispatcher.Dispatch(new RootState.WalletConfigLoadedAction(config));

_btcPayConnection.ConnectionChanged += (sender, args) =>
dispatcher.Dispatch(new RootState.BTCPayConnectionUpdatedAction(_btcPayConnection.Connection?.State));

_lightningNodeManager.StateChanged += (sender, args) =>
dispatcher.Dispatch(new RootState.LightningNodeStateUpdatedAction(args));

dispatcher.Dispatch(new RootState.LoadingAction(RootState.LoadingHandles.WalletConfig, true));
dispatcher.Dispatch(new RootState.LoadingAction(RootState.LoadingHandles.PairConfig, true));

_ = _btcPayAppConfigManager.Loaded.Task.ContinueWith(_ =>
{
dispatcher.Dispatch(new RootState.LoadingAction(false));
dispatcher.Dispatch(new RootState.WalletConfigLoadedAction(_btcPayAppConfigManager.WalletConfig));
dispatcher.Dispatch(new RootState.LoadingAction(RootState.LoadingHandles.WalletConfig, false));
dispatcher.Dispatch(new RootState.PairConfigLoadedAction(_btcPayAppConfigManager.PairConfig));
dispatcher.Dispatch(new RootState.LoadingAction(RootState.LoadingHandles.PairConfig, false));
});
_btcPayConnection.ConnectionChanged += (sender, args) =>
dispatcher.Dispatch(new RootState.BTCPayConnectionUpdatedAction(_btcPayConnection.Connection?.State));
_lightningNodeManager.StateChanged += (sender, args) =>
dispatcher.Dispatch(new RootState.LightningNodeStateUpdatedAction(args));
}
}
34 changes: 14 additions & 20 deletions BTCPayApp.UI/wwwroot/index.html
Original file line number Diff line number Diff line change
@@ -1,29 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<title>BTCPay Server</title>
<base href="/" />
<link href="./_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
<link href="./_content/BTCPayApp.UI/css/site.css" rel="stylesheet" />
<link rel="icon" type="image/png" href="./_content/BTCPayApp.UI/favicon.png"/>
<base href="/"/>
<meta charset="utf-8"/>
<meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no,viewport-fit=cover" name="viewport"/>
<title>BTCPay Server</title>
<script src="./_framework/blazor.webview.js"></script>
<script src="./_content/MudBlazor/MudBlazor.min.js"></script>
<link href="./_content/MudBlazor/MudBlazor.min.css" rel="stylesheet"/>
<link href="./_content/BTCPayApp.UI/css/site.css" rel="stylesheet"/>
<link href="./_content/BTCPayApp.UI/favicon.png" rel="icon" type="image/png"/>
</head>

<body>

<div class="status-bar-safe-area"></div>

<div id="app">Loading...</div>

<div id="blazor-error-ui">
<div class="status-bar-safe-area"></div>
<div id="app">Loading...</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="reload" href="">Reload</a>
<a class="dismiss">🗙</a>
</div>

<script src="./_framework/blazor.webview.js" autostart="false"></script>
<script src="./_content/MudBlazor/MudBlazor.min.js"></script>
</div>
</body>

</html>

0 comments on commit 5bc207f

Please sign in to comment.