Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Daybreak.Core/Models/NotificationWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,14 @@ namespace Daybreak.Models;
public sealed class NotificationWrapper
{
public required Notification Notification { get; init; }
public DateTime StartTime { get; set; }
public DateTime ExpirationTime { get; set; }

/// <summary>
/// Incremented every time the notification lifetime is prolonged so the UI can
/// restart the expiration progress animation.
/// </summary>
public int LifetimeGeneration { get; set; }

public double LifetimeMilliseconds => Math.Max(0, (this.ExpirationTime - this.StartTime).TotalMilliseconds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ internal sealed class ApplicationLauncher(
IGuildWarsProcessFinder guildWarsProcessFinder,
IDaybreakRestartingService daybreakRestartingService,
IPrivilegeManager privilegeManager,
ISteamService steamService,
ILogger<ApplicationLauncher> logger
) : IApplicationLauncher
{
Expand All @@ -55,6 +56,7 @@ ILogger<ApplicationLauncher> logger

private readonly IPrivilegeManager privilegeManager =
privilegeManager.ThrowIfNull();
private readonly ISteamService steamService = steamService.ThrowIfNull();

public async Task<GuildWarsApplicationLaunchContext?> LaunchGuildwars(
LaunchConfigurationWithCredentials launchConfigurationWithCredentials,
Expand All @@ -63,6 +65,27 @@ CancellationToken cancellationToken
{
launchConfigurationWithCredentials.ThrowIfNull();
launchConfigurationWithCredentials.Credentials.ThrowIfNull();
if (launchConfigurationWithCredentials.Credentials?.IsSteamLogin is true)
{
if (!this.steamService.IsSteamLoginSupported)
{
this.notificationService.NotifyError(
title: "Steam login is not supported",
description: "Steam login is not supported on this platform yet. Please select a different set of credentials"
);
return default;
}

if (!this.steamService.IsSteamRunning())
{
this.notificationService.NotifyError(
title: "Steam is not running",
description: "Steam login is selected but the Steam client is not running. Please start Steam and log in, then try again"
);
return default;
}
}

using var timeout = new CancellationTokenSource(LaunchTimeout);
using var cancellation = CancellationTokenSource.CreateLinkedTokenSource(
cancellationToken,
Expand Down Expand Up @@ -107,8 +130,9 @@ CancellationToken cancellationToken
)
{
var scopedLogger = this.logger.CreateScopedLogger();
var email = launchConfigurationWithCredentials.Credentials?.Username;
var password = launchConfigurationWithCredentials.Credentials?.Password;
var isSteamLogin = launchConfigurationWithCredentials.Credentials?.IsSteamLogin is true;
var email = isSteamLogin ? null : launchConfigurationWithCredentials.Credentials?.Username;
var password = isSteamLogin ? null : launchConfigurationWithCredentials.Credentials?.Password;
var executable = launchConfigurationWithCredentials.ExecutablePath;
if (executable is not null && File.Exists(executable) is false)
{
Expand Down Expand Up @@ -229,7 +253,7 @@ is null
};

var steamAppIdFilePath = Path.Combine(workingDirectory, SteamAppIdFile);
if (launchConfigurationWithCredentials.SteamSupport)
if (launchConfigurationWithCredentials.SteamSupport || isSteamLogin)
{
await File.WriteAllTextAsync(steamAppIdFilePath, SteamAppId, cancellationToken);
scopedLogger.LogDebug(
Expand Down
7 changes: 7 additions & 0 deletions Daybreak.Core/Services/Credentials/CredentialManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ internal sealed class CredentialManager(
public bool TryGetCredentialsByIdentifier(string identifier, out LoginCredentials? loginCredentials)
{
loginCredentials = default;
if (string.Equals(identifier, LoginCredentials.SteamLoginIdentifier, StringComparison.Ordinal))
{
loginCredentials = LoginCredentials.SteamLogin;
return true;
}

if (this.GetCredentialList().FirstOrDefault(l => l.Identifier == identifier) is LoginCredentials foundCredentials)
{
loginCredentials = foundCredentials;
Expand Down Expand Up @@ -56,6 +62,7 @@ public void StoreCredentials(List<LoginCredentials> loginCredentials)
this.logger.LogDebug("Storing credentials");
var options = this.liveOptions.CurrentValue;
options.ProtectedLoginCredentials = [.. loginCredentials
.Where(c => !c.IsSteamLogin)
.Select(this.ProtectCredentials)
.OfType<ProtectedLoginCredentials>()];
this.optionsProvider.SaveOption(options);
Expand Down
6 changes: 4 additions & 2 deletions Daybreak.Core/Services/ReShade/ReShadeService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Daybreak.Shared.Services.Notifications;
using Daybreak.Shared.Services.Options;
using Daybreak.Shared.Services.ReShade;
using Daybreak.Shared.Services.Shell;
using Daybreak.Shared.Utils;
using Daybreak.Views.Mods;
using HtmlAgilityPack;
Expand All @@ -18,7 +19,6 @@
using Microsoft.Extensions.Options;
using System.Core.Extensions;
using System.Data;
using System.Diagnostics;
using System.Extensions;
using System.Extensions.Core;
using System.IO.Compression;
Expand All @@ -34,6 +34,7 @@ internal sealed class ReShadeService(
IHttpClient<ReShadeService> httpClient,
IDownloadService downloadService,
IViewManager viewManager,
IShellExecutor shellExecutor,
ILogger<ReShadeService> logger) : IReShadeService
{
private const string PackagesIniUrl = "https://raw.githubusercontent.com/crosire/reshade-shaders/list/EffectPackages.ini";
Expand Down Expand Up @@ -68,6 +69,7 @@ internal sealed class ReShadeService(
private readonly IHttpClient<ReShadeService> httpClient = httpClient.ThrowIfNull();
private readonly IDownloadService downloadService = downloadService.ThrowIfNull();
private readonly IViewManager viewManager = viewManager.ThrowIfNull();
private readonly IShellExecutor shellExecutor = shellExecutor.ThrowIfNull();
private readonly ILogger<ReShadeService> logger = logger.ThrowIfNull();

public string Name => "ReShade";
Expand Down Expand Up @@ -220,7 +222,7 @@ public Task OnGuildWarsStartingDisabled(GuildWarsStartingDisabledContext guildWa

public void OpenReShadeFolder()
{
Process.Start("explorer.exe", ReShadePath);
this.shellExecutor.OpenPath(ReShadePath);
}

public async Task<IEnumerable<ShaderPackage>> GetStockPackages(CancellationToken cancellationToken)
Expand Down
14 changes: 5 additions & 9 deletions Daybreak.Core/Views/App.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Daybreak.Shared.Services.Notifications;
using Daybreak.Shared.Services.Options;
using Daybreak.Shared.Services.Privilege;
using Daybreak.Shared.Services.Shell;
using Daybreak.Shared.Services.Themes;
using Daybreak.Shared.Services.Updater;
using Daybreak.Shared.Services.Window;
Expand All @@ -17,7 +18,6 @@
using Microsoft.JSInterop;
using Photino.Blazor;
using System.Core.Extensions;
using System.Diagnostics;
using System.Extensions;
using TrailBlazr.Services;

Expand All @@ -39,6 +39,7 @@ public sealed class AppViewModel
private readonly JSConsoleInterop jsConsoleInterop;
private readonly INotificationService notificationService;
private readonly IWindowManipulationService windowManipulationService;
private readonly IShellExecutor shellExecutor;
private readonly ILogger<App> logger;

private SemaphoreSlim themeChangeSemaphore = new(1, 1);
Expand Down Expand Up @@ -89,6 +90,7 @@ public AppViewModel(
INotificationProducer notificationProducer,
INotificationService notificationService,
IWindowManipulationService windowManipulationService,
IShellExecutor shellExecutor,
ILogger<App> logger)
{
this.keyboardHookService = keyboardHookService.ThrowIfNull();
Expand All @@ -104,6 +106,7 @@ public AppViewModel(
this.NotificationProducer = notificationProducer.ThrowIfNull();
this.notificationService = notificationService.ThrowIfNull();
this.windowManipulationService = windowManipulationService.ThrowIfNull();
this.shellExecutor = shellExecutor.ThrowIfNull();
this.logger = logger.ThrowIfNull();


Expand Down Expand Up @@ -215,14 +218,7 @@ public void CloseNavigationMenu()

public void OpenIssues()
{
try
{
Process.Start(new ProcessStartInfo { FileName = IssueUrl, UseShellExecute = true });
}
catch (Exception ex)
{
this.logger.LogError(ex, "Encountered exception while opening issues page");
}
this.shellExecutor.OpenUrl(IssueUrl);
}

public void OpenSynchronizationView()
Expand Down
7 changes: 6 additions & 1 deletion Daybreak.Core/Views/Components/NotificationArea.razor
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
{
<p class="notification-description">@notificationTuple.Notification.Description</p>
}
<div class="notification-progress"
@key="@($"{notificationTuple.Notification.Id}-{notificationTuple.LifetimeGeneration}")"
style="animation-duration: @(notificationTuple.LifetimeMilliseconds.ToString(System.Globalization.CultureInfo.InvariantCulture))ms;"></div>
</div>
}
</div>
Expand Down Expand Up @@ -72,7 +75,7 @@
try
{
await this.notificationLock.WaitAsync(cancellationToken);
this.notifications.Add(new NotificationWrapper { Notification = notification, ExpirationTime = notification.ExpirationTime });
this.notifications.Add(new NotificationWrapper { Notification = notification, StartTime = DateTime.UtcNow, ExpirationTime = notification.ExpirationTime });
}
catch (OperationCanceledException)
{
Expand Down Expand Up @@ -167,7 +170,9 @@

private void ExtendNotification(NotificationWrapper notification)
{
notification.StartTime = DateTime.UtcNow;
notification.ExpirationTime = DateTime.UtcNow + NotificationExtension;
notification.LifetimeGeneration++;
}

private async Task OpenNofitication(NotificationWrapper notification)
Expand Down
43 changes: 39 additions & 4 deletions Daybreak.Core/Views/Components/NotificationArea.razor.css
Original file line number Diff line number Diff line change
Expand Up @@ -99,28 +99,58 @@

/* Notification level styling */
.notification-item.level-information {
border-left: 4px solid var(--accent-fill-rest);
--notification-accent: var(--accent-fill-rest);
border-left: 4px solid var(--notification-accent);
}

.notification-item.level-warning {
border-left: 4px solid #ff9500;
--notification-accent: #ff9500;
border-left: 4px solid var(--notification-accent);
}

.notification-item.level-error,
.notification-item.level-critical {
border-left: 4px solid #d13438;
--notification-accent: #d13438;
border-left: 4px solid var(--notification-accent);
}

.notification-item.level-debug,
.notification-item.level-trace {
border-left: 4px solid var(--neutral-stroke-accessible);
--notification-accent: var(--neutral-stroke-accessible);
border-left: 4px solid var(--notification-accent);
}

.notification-item[class*="level-"]:hover {
border-left: 0px;
cursor: pointer;
}

/* Expiration progress line animating from start time to expiration */
.notification-progress {
position: absolute;
left: 0;
bottom: 0;
height: 3px;
width: 100%;
background: var(--notification-accent, var(--accent-fill-rest));
transform: scaleX(0);
transform-origin: left center;
animation-name: notificationProgress;
animation-timing-function: linear;
animation-fill-mode: forwards;
animation-iteration-count: 1;
pointer-events: none;
}

@keyframes notificationProgress {
from {
transform: scaleX(0);
}
to {
transform: scaleX(1);
}
}

@keyframes slideInFromRight {
from {
transform: translateX(100%);
Expand Down Expand Up @@ -179,4 +209,9 @@
animation: none;
opacity: 0;
}

.notification-progress {
animation: none;
transform: scaleX(1);
}
}
1 change: 1 addition & 0 deletions Daybreak.Core/Views/LaunchConfigurationsView.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public sealed class LaunchConfigurationsViewModel(
public override ValueTask ParametersSet(LaunchConfigurationsView view, CancellationToken cancellationToken)
{
this.Credentials.ClearAnd().AddRange(this.credentialManager.GetCredentialList());
this.Credentials.Add(LoginCredentials.SteamLogin);
this.LaunchConfigurations.ClearAnd().AddRange(this.launchConfigurationService.GetLaunchConfigurations());
this.Executables.ClearAnd().AddRange(this.guildWarsExecutableManager.GetExecutableList()).Add(string.Empty);
return base.ParametersSet(view, cancellationToken);
Expand Down
5 changes: 4 additions & 1 deletion Daybreak.Core/Views/LaunchView.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -507,9 +507,12 @@ launcherViewContext.AppContext is not null
cancellationToken
);

// If the API is available but it does not belong to the desired user, return null
// If the API is available but it does not belong to the desired user, return null.
// Steam login configurations use a virtual credential whose username is not the account
// email, so the ownership check is skipped for them.
if (
maybeApiContext is not null
&& launcherViewContext.Configuration.Credentials.IsSteamLogin is false
&& await maybeApiContext.GetLoginInfo(cancellationToken) is LoginInfo loginInfo
&& loginInfo.Email != launcherViewContext.Configuration.Credentials.Username
)
Expand Down
8 changes: 5 additions & 3 deletions Daybreak.Core/Views/PluginsView.razor.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using Daybreak.Shared.Models.Plugins;
using Daybreak.Shared.Services.ApplicationLauncher;
using Daybreak.Shared.Services.Plugins;
using Daybreak.Shared.Services.Shell;
using Photino.NET;
using System.Diagnostics;
using System.Extensions;
using TrailBlazr.Services;
using TrailBlazr.ViewModels;
Expand All @@ -12,13 +12,15 @@ public sealed class PluginsViewModel(
PhotinoWindow window,
IApplicationLauncher applicationLauncher,
IViewManager viewManager,
IPluginsService pluginsService)
IPluginsService pluginsService,
IShellExecutor shellExecutor)
: ViewModelBase<PluginsViewModel, PluginsView>
{
private readonly PhotinoWindow window = window;
private readonly IApplicationLauncher applicationLauncher = applicationLauncher;
private readonly IViewManager viewManager = viewManager;
private readonly IPluginsService pluginsService = pluginsService;
private readonly IShellExecutor shellExecutor = shellExecutor;

public List<AvailablePlugin> AvailablePlugins { get; init; } = [];
public bool CanSave { get; private set; } = false;
Expand Down Expand Up @@ -71,7 +73,7 @@ public async void LoadPluginsFromDisk()

public void NavigateToPlugin(AvailablePlugin plugin)
{
Process.Start("explorer.exe", plugin.Path);
this.shellExecutor.OpenPath(plugin.Path);
}

private void UpdatePlugins()
Expand Down
20 changes: 14 additions & 6 deletions Daybreak.Core/Views/VersionManagementView.razor
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
<div class="stretch-container backdrop-panel">
<div class="content">
<Virtualize Items="@this.ViewModel.Versions"
Context="version">
<div class="version-row">
<div class="version-row-center" @onclick="@(() => this.ViewModel.OpenVersionPage(version))">
<span>@version.ToString()</span>
Context="version"
ItemSize="56">
<div class="version-item" tabindex="0"
@onclick="@(() => this.ViewModel.OpenVersionPage(version))">
<div class="version-info">
<div class="version-icon">
<FluentIcon Value="@(new Microsoft.FluentUI.AspNetCore.Components.Icons.Regular.Size20.Tag())"
Color="Color.Neutral" />
</div>
<span class="version-name">@version.ToString()</span>
</div>
<div class="version-row-right">
<div class="version-actions"
@onclick="@(() => this.ViewModel.DownloadVersion(version))"
@onclick:stopPropagation="true">
<FluentButton Appearance="Appearance.Stealth"
IconOnly="true"
OnClick="@(() => this.ViewModel.DownloadVersion(version))">
Title="Download version">
<FluentIcon Value="@(new Microsoft.FluentUI.AspNetCore.Components.Icons.Regular.Size16.ArrowDownload())" />
</FluentButton>
</div>
Expand Down
Loading
Loading