fix issues with new UI, add color profiles, add sdr brightness for AMD

This commit is contained in:
Vincent Maas committed 2024-09-01 16:24:05 +02:00
1 parent 4d2ca8a552
commit f42a46e53f
95 files changed
+12129 -10005

No files matched your search

+19 -7
View File
@@ -1,8 +1,15 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Services
@using ColorControl.Shared.Common
@using ColorControl.Shared.Contracts
@using ColorControl.UI.Services
@using ColorControl.UI.Components.Pages.Generic
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@inject AppState AppState
@inject RpcUiClientService ClientService;
@inject NavigationManager _navigationManager;
@inject RpcUiClientService _rpcClientService
@inject NavigationManager _navigationManager
@inject NotificationService _notificationService
@inject ProtectedSessionStorage _sessionStorage
@implements IDisposable
@attribute [StreamRendering]
@@ -15,28 +22,33 @@
<base href="/" />
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="app.css" />
<link rel="stylesheet" href="ColorControl.UI.styles.css" />
<link rel="icon" type="image/png" href="favicon.png" />
<HeadOutlet />
</head>
<body>
<Routes />
<script src="_framework/blazor.web.js"></script>
<NotificationsComponent></NotificationsComponent>
</body>
</html>
@code {
private Config? Config;
protected override async Task OnInitializedAsync()
{
var config = await ClientService.CallAsync<Config>("OptionsService", "GetConfig");
Config ??= await _rpcClientService.CallAsync<Config>("OptionsService", "GetConfig");
var newTheme = config.UseDarkMode ? "dark" : "light";
var newTheme = Config?.UseDarkMode == true ? "dark" : "light";
if (newTheme != AppState.SelectedTheme)
{
AppState.SelectedTheme = config.UseDarkMode ? "dark" : "light";
AppState.SelectedTheme = Config?.UseDarkMode == true ? "dark" : "light";
_navigationManager.Refresh();
}
@@ -1,5 +1,6 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.UI.Services
@rendermode InteractiveServer
@@ -28,28 +29,34 @@
@foreach (var module in modules.Where(m => m.IsActive))
{
<div class="nav-item px-3">
<NavLink class="nav-link px-3" href="@module.Link">
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> @module.DisplayName
<NavLink class="nav-link px-3 align-items-baseline" href="@module.Link">
<span class="bi bi-@module.GetIconClass()" aria-hidden="true"></span> @module.DisplayName
</NavLink>
</div>
}
}
<div class="nav-item px-3">
<NavLink class="nav-link px-3" href="options">
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Options
<NavLink class="nav-link px-3 align-items-baseline" href="options">
<span class="bi bi-gear-fill" aria-hidden="true"></span> Options
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link px-3" href="logging">
<span class="bi bi-list-logging-nav-menu" aria-hidden="true"></span> Logging
<NavLink class="nav-link px-3 align-items-baseline" href="ColorProfile">
<span class="bi bi-palette-fill" aria-hidden="true"></span> Color Profiles
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link px-3" href="info">
<span class="bi bi-list-logging-nav-menu" aria-hidden="true"></span> Info
<NavLink class="nav-link px-3 align-items-baseline" href="logging">
<span class="bi bi-list-columns-reverse" aria-hidden="true"></span> Logging
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link px-3 align-items-baseline" href="info">
<span class="bi bi-info-circle-fill" aria-hidden="true"></span> Info
</NavLink>
</div>
</nav>
@@ -1,5 +1,5 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Services
@using ColorControl.UI.Services
@inherits LayoutComponentBase
@inject AppState AppState
@@ -1,15 +1,15 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.AMD
@using ColorControl.Shared.Services;
@using ColorControl.UI.Components.Pages.Generic
@using ColorControl.Shared.Common;
@using ColorControl.UI.Services
@rendermode InteractiveServer
@inject RpcUiClientService _rpcClientService
<div class="modal" id="presetModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" @onfocus="ModalOnFocus">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@(Preset?.IsDisplayPreset == true ? $"Settings for display {Preset?.displayName}" : Preset?.id == 0 ? $"Settings for new preset" : $"Settings of preset {Preset?.IdOrName}")</h5>
@@ -316,7 +316,7 @@
selectedVirtualResolution = Preset?.DisplayConfig.Resolution?.GetVirtualString();
selectedRefreshRate = Preset?.DisplayConfig.RefreshRate?.ToString();
if (Preset?.IsDisplayPreset != true)
if (Preset?.IsDisplayPreset == false)
{
displayPresets = await _rpcClientService.CallAsync<List<AmdPreset>>("AmdService", "GetDisplayPresets");
}
@@ -1,6 +1,6 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.AMD
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@using ColorControl.UI.Components.Pages.Generic
@@ -8,7 +8,7 @@
@inject RpcUiClientService _rpcClientService
<div class="modal" id="settingsModal" tabindex="-1" @onfocus="OnFocus">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">AMD settings</h5>
@@ -3,7 +3,7 @@
@using ColorControl.Shared.Common
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.AMD
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.UI.Components.Pages.Generic
@using ColorControl.UI.Generics
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@@ -92,6 +92,10 @@ else
</div>
<div class="row mb-3">
<div class="col">
@if (!Presets.Any())
{
<span>No presets found</span>
}
<div class="list-group">
@foreach (var preset in Presets)
{
@@ -106,11 +110,21 @@ else
}
</h5>
</div>
<span>
<span class="d-none d-lg-block">
<button class="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => PresetOnDeleteClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Delete</button>
<button class="btn btn-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnCloneClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Clone</button>
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnEditClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Edit</button>
</span>
<div class="d-lg-none" @onclick:preventDefault @onclick:stopPropagation>
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-three-dots-vertical"></i>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => PresetOnDeleteClick(preset)">Delete</a></li>
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnCloneClick(preset)">Clone</a></li>
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnEditClick(preset)">Edit</a></li>
</ul>
</div>
</div>
<p class="mb-1">@preset.DisplayConfig.Resolution.ToString() @@ @preset.DisplayConfig.RefreshRate.ToString()Hz @(preset.applyColorData ? preset.colorDepth : "")</p>
<small class="text-muted">@preset.shortcut</small>
@@ -124,7 +138,6 @@ else
@code {
private List<AmdPreset>? Presets;
private List<AmdPreset>? DisplayPresets;
private List<VirtualResolution>? Resolutions;
private AmdPreset? CurrentPreset;
@@ -134,7 +147,6 @@ else
{
await RefreshPresets();
DisplayPresets = await _rpcClientService.CallAsync<List<AmdPreset>>("AmdService", "GetDisplayPresets");
Resolutions = await _rpcClientService.CallAsync<List<VirtualResolution>>("AmdService", "GetAvailableResolutionsV2", DisplayPresets?.FirstOrDefault());
}
protected override async Task OnAfterRenderAsync(bool firstTime)
@@ -0,0 +1,452 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.AMD
@using ColorControl.Shared.Contracts.DisplayInfo
@using ColorControl.UI.Components.Pages.Generic
@using ColorControl.Shared.Common;
@using ColorControl.UI.Services
@using MHC2Gen
@using Microsoft.JSInterop;
@rendermode InteractiveServer
@inject RpcUiClientService _rpcClientService
@inject JSHelper jsHelper;
<div class="modal modal-lg" id="colorProfileModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" @onfocus="ModalOnFocus">
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@(ColorProfile?.Name == null ? $"Settings for new color profile" : $"Settings of color profile {ColorProfile.Name}")</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="() => IsVisible = false"></button>
</div>
<div class="modal-body">
@if (ColorProfile?.HasExtraInfo == false)
{
<div class="alert alert-warning" role="alert">
This is not a profile created by ColorControl. Some fields are set to default.
</div>
}
@if (!string.IsNullOrEmpty(_rpcClientService.LastErrorMessage))
{
<div class="alert alert-danger" role="alert">
Error: @_rpcClientService.LastErrorMessage
</div>
}
<form class="row g-1 needs-validation @WasValidated" id="profileForm" novalidate>
@if (ColorProfile == null || DisplayInfo == null || CurrentColorInfo == null)
{
<div>Loading...</div>
}
else
{
<div class="mb-2">
<label class="form-label" for="display">Display</label>
<input class="form-control" type="text" id="display" value="@DisplayInfo" disabled />
</div>
<div class="mb-2">
<label class="form-label" for="name">Name</label>
<input class="form-control" type="text" id="name" @bind="ColorProfile.Name" @bind:after="AfterNameChanged" required />
</div>
<div class="mb-2">
<label class="form-label" for="description">Description</label>
<input class="form-control" type="text" id="description" @bind="ColorProfile.Description" />
</div>
<div class="accordion accordion-flush" id="presetAccordion">
<div class="accordion-item">
<h2 class="accordion-header">
<span class="d-flex align-items-baseline">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#displaySettings">
Color gamut
</button>
</span>
</h2>
<div id="displaySettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<div class="mb-2">
<div class="row">
<div class="col">
<label class="form-label" for="primariesSource"><small>Show Display Primaries and Luminance values of</small></label>
<select class="form-select" id="primariesSource" @bind="DisplayPrimariesSource" @bind:after="DisplayPrimariesSourceChanged">
@foreach (var colorInfo in DisplayColors)
{
<option value="@colorInfo.DisplayPrimariesSource">@(colorInfo.CustomName ?? colorInfo.DisplayPrimariesSource.ToString())</option>
}
</select>
</div>
<div class="col align-content-end">
<button class="btn btn-primary" type="button" disabled="@(DisplayPrimariesSource == DisplayPrimariesSource.ColorProfile)" @onclick="OverwritePrimariesOnClick">Overwrite color profile values</button>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="mb-2">
<label class="form-label text-danger" for="redX">Red X-coordinate</label>
<input class="form-control" id="redX" type="number" min="0" max="1" step="0.000000000000001" @bind="CurrentColorInfo.RgbPrimaries.Red.X" disabled="@PrimariesDisabled" />
</div>
</div>
<div class="col">
<div class="mb-2">
<label class="form-label text-danger" for="redY">Red Y-coordinate</label>
<input class="form-control" id="redY" type="number" min="0" max="1" step="0.000000000000001" @bind="CurrentColorInfo.RgbPrimaries.Red.Y" disabled="@PrimariesDisabled" />
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="mb-2">
<label class="form-label text-success" for="greenX">Green X-coordinate</label>
<input class="form-control" id="greenX" type="number" min="0" max="1" step="0.000000000000001" @bind="CurrentColorInfo.RgbPrimaries.Green.X" disabled="@PrimariesDisabled" />
</div>
</div>
<div class="col">
<div class="mb-2">
<label class="form-label text-success" for="greenY">Green Y-coordinate</label>
<input class="form-control" id="greenY" type="number" min="0" max="1" step="0.000000000000001" @bind="CurrentColorInfo.RgbPrimaries.Green.Y" disabled="@PrimariesDisabled" />
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="mb-2">
<label class="form-label text-primary" for="blueX">Blue X-coordinate</label>
<input class="form-control" id="blueX" type="number" min="0" max="1" step="0.000000000000001" @bind="CurrentColorInfo.RgbPrimaries.Blue.X" disabled="@PrimariesDisabled" />
</div>
</div>
<div class="col">
<div class="mb-2">
<label class="form-label text-primary" for="blueY">Blue Y-coordinate</label>
<input class="form-control" id="blueY" type="number" min="0" max="1" step="0.000000000000001" @bind="CurrentColorInfo.RgbPrimaries.Blue.Y" disabled="@PrimariesDisabled" />
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="mb-2">
<label class="form-label" for="whiteX">White X-coordinate</label>
<input class="form-control" id="whiteX" type="number" min="0" max="1" step="0.000000000000001" @bind="CurrentColorInfo.RgbPrimaries.White.X" disabled="@PrimariesDisabled" />
</div>
</div>
<div class="col">
<div class="mb-2">
<label class="form-label" for="whiteY">White Y-coordinate</label>
<input class="form-control" id="whiteY" type="number" min="0" max="1" step="0.000000000000001" @bind="CurrentColorInfo.RgbPrimaries.White.Y" disabled="@PrimariesDisabled" />
</div>
</div>
</div>
<div class="mb-2">
<label class="form-label" for="targetGamut">Target gamut</label>
<select class="form-select" id="targetGamut" @bind="ColorProfile.ColorGamut">
@foreach (var gamut in Enum.GetValues<ColorGamut>())
{
<option value="@gamut">@gamut.GetDescription()</option>
}
</select>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<span class="d-flex align-items-baseline">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#luminanceSettings">
Luminance settings
</button>
</span>
</h2>
<div id="luminanceSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<div class="row">
<div class="col">
<div class="mb-2">
<label class="form-label" for="minLuminance">Minimum luminance (nits)</label>
<input class="form-control" id="minLuminance" type="number" min="0" max="10" @bind="CurrentColorInfo.BlackLuminance" disabled="@PrimariesDisabled" />
</div>
</div>
<div class="col">
<div class="mb-2">
<label class="form-label" for="maxLuminance">Max full frame luminance (nits)</label>
<input class="form-control" id="maxLuminance" type="number" min="0" max="10000" @bind="CurrentColorInfo.WhiteLuminance" disabled="@PrimariesDisabled" />
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="mb-2">
<label class="form-label" for="minCLL">Minimum brightness (MinCLL) (nits)</label>
<input class="form-control" id="minCLL" type="number" min="0" max="10" @bind="CurrentColorInfo.MinCLL" disabled="@PrimariesDisabled" />
</div>
</div>
<div class="col">
<div class="mb-2">
<label class="form-label" for="maxCLL">Peak brightness (MaxCLL) (nits)</label>
<input class="form-control" id="maxCLL" type="number" min="0" max="10000" @bind="CurrentColorInfo.MaxCLL" disabled="@PrimariesDisabled" />
</div>
</div>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<span class="d-flex align-items-baseline">
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#hdrSettings">
Advanced EOTF settings
</button>
</span>
</h2>
<div id="hdrSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<div class="row">
<div class="col">
<div class="mb-2">
<label class="form-label" for="sdrTransfer">SDR Transfer Function</label>
<select class="form-select" id="sdrTransfer" @bind="ColorProfile.SDRTransferFunction">
@foreach (var function in Enum.GetValues<SDRTransferFunction>())
{
<option value="@function">@function.GetDescription()</option>
}
</select>
</div>
</div>
<div class="col">
<div class="mb-2">
<label class="form-label" for="gamma">Gamma</label>
<input class="form-control" id="gamma" type="number" min="0.1" max="10" step="0.01" @bind="ColorProfile.Gamma" disabled="@(ColorProfile.SDRTransferFunction != SDRTransferFunction.PurePower)" />
</div>
</div>
</div>
@if (ColorProfile.IsHDRProfile)
{
<div class="row">
<div class="col">
<div class="mb-2">
<label class="form-label" for="minSDR">Minimum SDR brightness (nits)</label>
<input class="form-control" id="minSDR" type="number" min="0" max="10" step="0.001" @bind="ColorProfile.SDRMinBrightness" disabled="@(ColorProfile.SDRTransferFunction != SDRTransferFunction.PurePower)" />
</div>
</div>
<div class="col">
<div class="mb-2">
<label class="form-label" for="maxSDR">Maximum SDR brightness (nits)</label>
<input class="form-control" id="maxSDR" type="number" min="0" max="10000" @bind="ColorProfile.SDRMaxBrightness" disabled="@(ColorProfile.SDRTransferFunction != SDRTransferFunction.PurePower)" />
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="mb-2">
<label class="form-label" for="brightnessBoost">Brightness boost (%)</label>
<input class="form-control" id="brightnessBoost" type="number" min="-100" max="100" @bind="ColorProfile.SDRBrightnessBoost" />
</div>
</div>
<div class="col">
<div class="mb-2">
<label class="form-label" for="shadowDetailBoost">Shadow detail boost (%)</label>
<input class="form-control" id="shadowDetailBoost" type="number" min="0" max="100" @bind="ColorProfile.ShadowDetailBoost" disabled="@(ColorProfile.SDRTransferFunction == SDRTransferFunction.Piecewise)" />
</div>
</div>
</div>
}
</div>
</div>
</div>
</div>
}
</form>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary" @onclick="InstallOnClick" disabled="@(ColorProfile == null)">Install and set as default</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="() => IsVisible = false">Close</button>
<button type="submit" class="btn btn-primary" @onclick="ApplyClick" disabled="@(ColorProfile == null)">Save</button>
</div>
</div>
</div>
</div>
@code {
private ColorProfileDto? ColorProfile { get; set; }
private DisplayColorInfo? CurrentColorInfo { get; set; }
private DisplayColorInfo? ColorProfileColorInfo { get; set; }
private List<DisplayColorInfo> DisplayColors { get; set; } = new();
private bool PrimariesDisabled = false;
[Parameter]
public string? ColorProfileName { get; set; }
[Parameter]
public bool UseAsTemplate { get; set; }
[Parameter]
public DisplayInfoDto? DisplayInfo { get; set; }
[Parameter]
public Func<ColorProfileDto, Task>? AfterApply { get; set; }
private string WasValidated = "";
private bool IsVisible;
private DisplayPrimariesSource DisplayPrimariesSource;
private bool IsInstalled;
protected override async Task OnParametersSetAsync()
{
if (ColorProfileName == null)
{
return;
}
if (ColorProfileName == "")
{
ColorProfile = ColorProfileDto.CreateDefault(DisplayInfo?.IsHdrSupportedAndEnabled ?? false);
}
else
{
ColorProfile = await _rpcClientService.CallAsync<ColorProfileDto>("ColorProfileService", "GetColorProfile", ColorProfileName);
}
if (ColorProfile != null)
{
ColorProfileColorInfo = new DisplayColorInfo
{
DisplayPrimariesSource = DisplayPrimariesSource.ColorProfile,
BlackLuminance = ColorProfile.BlackLuminance,
WhiteLuminance = ColorProfile.WhiteLuminance,
MinCLL = ColorProfile.MinCLL,
MaxCLL = ColorProfile.MaxCLL,
RgbPrimaries = new RgbPrimariesDto(ColorProfile.DevicePrimaries)
};
CurrentColorInfo = ColorProfileColorInfo;
DisplayColors.Clear();
DisplayColors.Add(CurrentColorInfo);
if (DisplayInfo != null)
{
DisplayColors.AddRange(DisplayInfo.DisplayColors);
}
if (UseAsTemplate)
{
if (ColorProfile.Name != null)
{
ColorProfile.Name = Path.GetFileNameWithoutExtension(ColorProfile.Name) + " (clone).icm";
}
else
{
ColorProfile.Name = "";
}
}
}
}
private async Task ApplyClick(MouseEventArgs e)
{
if (ColorProfile == null || CurrentColorInfo == null)
{
return;
}
if (!(await IsValid()))
{
return;
}
ColorProfile.UpdatePrimariesAndLuminance(CurrentColorInfo);
var result = await _rpcClientService.CallAsync<bool>("ColorProfileService", "UpdateColorProfile", ColorProfile, DisplayInfo?.DeviceName);
if (result)
{
await jsHelper.CloseModal("colorProfileModal");
IsVisible = false;
if (AfterApply != null)
{
await AfterApply.Invoke(ColorProfile);
}
}
}
private async Task InstallOnClick()
{
if (ColorProfile == null || DisplayInfo == null)
{
return;
}
if (!(await IsValid()))
{
return;
}
ColorProfile.UpdatePrimariesAndLuminance(CurrentColorInfo);
var result = await _rpcClientService.CallAsync<bool>("ColorProfileService", "InstallAndActivateColorProfile", ColorProfile, DisplayInfo.DeviceName);
IsInstalled = true;
}
private void ModalOnFocus(FocusEventArgs e)
{
if (!IsVisible)
{
WasValidated = "";
IsVisible = true;
DisplayPrimariesSource = DisplayPrimariesSource.ColorProfile;
}
}
private async Task<bool> IsValid()
{
var valid = await jsHelper.IsFormValid("profileForm");
if (!valid)
{
WasValidated = "was-validated";
}
return valid;
}
private void DisplayPrimariesSourceChanged()
{
if (DisplayInfo == null || ColorProfile == null)
{
return;
}
CurrentColorInfo = DisplayColors.First(dc => dc.DisplayPrimariesSource == DisplayPrimariesSource);
PrimariesDisabled = CurrentColorInfo.DisplayPrimariesSource != DisplayPrimariesSource.ColorProfile;
}
private void OverwritePrimariesOnClick(MouseEventArgs e)
{
if (ColorProfileColorInfo == null || CurrentColorInfo == null)
{
return;
}
ColorProfileColorInfo.BlackLuminance = CurrentColorInfo.BlackLuminance;
ColorProfileColorInfo.WhiteLuminance = CurrentColorInfo.WhiteLuminance;
ColorProfileColorInfo.MinCLL = CurrentColorInfo.MinCLL;
ColorProfileColorInfo.MaxCLL = CurrentColorInfo.MaxCLL;
ColorProfileColorInfo.RgbPrimaries = new RgbPrimariesDto(CurrentColorInfo.RgbPrimaries);
}
private void AfterNameChanged()
{
if (ColorProfile?.Name == null)
{
return;
}
if (!ColorProfile.Name.EndsWith(".icm", StringComparison.InvariantCultureIgnoreCase))
{
ColorProfile.Name += ".icm";
}
}
}
@@ -0,0 +1,301 @@
@page "/ColorProfile"
@using ColorControl.Shared.Common
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.DisplayInfo
@using ColorControl.UI.Components.Pages.Generic
@using ColorControl.UI.Generics
@using ColorControl.UI.Services
@rendermode InteractiveServer
@inject RpcUiClientService _rpcClientService
@inject JSHelper jsHelper;
<PageTitle>Color profiles</PageTitle>
<ColorProfilePage ColorProfileName="@SelectedColorProfileName" UseAsTemplate="UseAsTemplate" DisplayInfo="SelectedDisplay" AfterApply="AfterColorProfileSaved"></ColorProfilePage>
<ConfirmModal Title="Delete color profile" Message="Are you sure you want to delete the color profile?" OnConfirm="ColorProfileOnDeleteConfirmed" />
<ConfirmModal ModalId="disassociateConfirmModal" Title="Disassociate color profile" Message="Are you sure you want to disassociate the color profile from the selected display?" OnConfirm="ColorProfileOnDisassociateConfirmed" />
<ConfirmModal ModalId="associateConfirmModal" Title="Associate color profile" Message="Are you sure you want to associate the color profile with the selected display?" OnConfirm="ColorProfileOnAssociateConfirmed" />
<h2>Displays</h2>
@if (Displays == null)
{
<div>Loading...</div>
}
else
{
<div class="row mb-3">
@if (!Displays.Any())
{
<span>No displays found</span>
}
@foreach (var display in Displays)
{
<div class="mb-2">
<label class="form-label" for="device">Selected display</label>
<select class="form-select" id="device" @bind="SelectedDisplayId" @bind:after="SelectedDisplayChanged">
@foreach (var device in Displays)
{
<option value="@display.DisplayId">@display.DisplayName @@ @display.AdapterName</option>
}
</select>
</div>
@* <div class="col-lg-6 mb-3">
<div class="card h-100">
<div class="card-header d-flex align-items-baseline">
<span>
@display.DisplayName @@ @display.AdapterName
</span>
</div>
<div class="card-body">
<small class="text-body">@display.DevicePath</small>
<small class="text-muted">EDID? @display.DisplayColors.Any(dc => dc.DisplayPrimariesSource == MHC2Gen.DisplayPrimariesSource.EDID)</small>
</div>
</div>
</div>
*@ }
</div>
}
@if (SelectedDisplay == null)
{
<div>Loading...</div>
}
else
{
<div class="mb-2 d-flex align-items-center">
<h2>Display Associated Color profiles (@(SelectedDisplay.IsHdrSupportedAndEnabled ? "HDR" : "SDR"))</h2>
<div class="d-flex ms-auto">
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#colorProfileModal" @onclick="() => AddColorProfileOnClick()">
Add
</button>
</div>
</div>
<div class="row mb-3">
<div class="col">
@if (!SelectedDisplay.ColorProfiles.Any())
{
<span>No color profiles found that are associated with the display</span>
}
else
{
<div class="list-group">
@foreach (var colorProfile in SelectedDisplay.ColorProfiles)
{
<a class="list-group-item list-group-item-action" @onclick="() => SetColorProfileOnClick(colorProfile)" @onclick:preventDefault @onclick:stopPropagation>
<div class="d-flex align-items-start justify-content-between">
<div>
<h5 class="mb-1">
@colorProfile
@if (colorProfile == SelectedDisplay?.DefaultColorProfile)
{
<span class="ms-2 badge text-bg-warning" title="Active color profile">Active</span>
}
</h5>
</div>
<span class="d-none d-lg-block">
<button class="btn btn-warning btn-sm" data-bs-toggle="modal" data-bs-target="#disassociateConfirmModal" @onclick="() => SelectedColorProfileName = colorProfile" @onclick:preventDefault @onclick:stopPropagation>Disassociate</button>
<button class="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => DeleteColorProfileOnClick(colorProfile)" @onclick:preventDefault @onclick:stopPropagation>Delete</button>
<button class="btn btn-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#colorProfileModal" @onclick="() => CloneColorProfileOnClick(colorProfile)" @onclick:preventDefault @onclick:stopPropagation>Clone</button>
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#colorProfileModal" @onclick="() => EditColorProfileOnClick(colorProfile)" @onclick:preventDefault @onclick:stopPropagation>Edit</button>
</span>
<div class="d-lg-none" @onclick:preventDefault @onclick:stopPropagation>
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-three-dots-vertical"></i>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#disassociateConfirmModal" @onclick="() => SelectedColorProfileName = colorProfile">Disassociate</a></li>
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => DeleteColorProfileOnClick(colorProfile)">Delete</a></li>
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#colorProfileModal" @onclick="() => CloneColorProfileOnClick(colorProfile)">Clone</a></li>
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#colorProfileModal" @onclick="() => EditColorProfileOnClick(colorProfile)">Edit</a></li>
</ul>
</div>
</div>
</a>
}
</div>
}
</div>
</div>
}
<div class="mb-2 d-flex align-items-center">
<h2>Other Color profiles</h2>
</div>
<div class="row mb-3">
<div class="col">
@if (ColorProfileNames == null)
{
<span>No profiles found</span>
}
else
{
<div class="list-group">
@foreach (var colorProfile in ColorProfileNames)
{
<span class="list-group-item list-group-item-action" @onclick="() => EditColorProfileOnClick(colorProfile)" @onclick:preventDefault @onclick:stopPropagation>
<div class="d-flex align-items-start justify-content-between">
<div>
<h5 class="mb-1">
@colorProfile
</h5>
</div>
<span class="d-none d-lg-block">
<button class="btn btn-warning btn-sm" data-bs-toggle="modal" data-bs-target="#associateConfirmModal" @onclick="() => SelectedColorProfileName = colorProfile" @onclick:preventDefault @onclick:stopPropagation>Associate</button>
<button class="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => DeleteColorProfileOnClick(colorProfile)" @onclick:preventDefault @onclick:stopPropagation>Delete</button>
<button class="btn btn-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#colorProfileModal" @onclick="() => CloneColorProfileOnClick(colorProfile)" @onclick:preventDefault @onclick:stopPropagation>Clone</button>
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#colorProfileModal" @onclick="() => EditColorProfileOnClick(colorProfile)" @onclick:preventDefault @onclick:stopPropagation>Edit</button>
</span>
<div class="d-lg-none" @onclick:preventDefault @onclick:stopPropagation>
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-three-dots-vertical"></i>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#associateConfirmModal" @onclick="() => SelectedColorProfileName = colorProfile">Associate</a></li>
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => DeleteColorProfileOnClick(colorProfile)">Delete</a></li>
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#colorProfileModal" @onclick="() => CloneColorProfileOnClick(colorProfile)">Clone</a></li>
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#colorProfileModal" @onclick="() => EditColorProfileOnClick(colorProfile)">Edit</a></li>
</ul>
</div>
</div>
</span>
}
</div>
}
</div>
</div>
@code {
private string? SelectedDisplayId;
private DisplayInfoDto? SelectedDisplay;
private List<DisplayInfoDto>? Displays;
private string? SelectedColorProfileName;
private bool UseAsTemplate;
private List<string>? ColorProfileNames;
protected override async Task OnInitializedAsync()
{
await RefreshDisplays();
}
protected override async Task OnAfterRenderAsync(bool firstTime)
{
}
private async Task AddColorProfileOnClick()
{
SelectedColorProfileName = "";
}
private async Task EditColorProfileOnClick(string colorProfile)
{
UseAsTemplate = false;
SelectedColorProfileName = colorProfile;
await jsHelper.OpenModal("colorProfileModal");
}
private async Task SetColorProfileOnClick(string colorProfile)
{
if (SelectedDisplay == null)
{
return;
}
var result = await _rpcClientService.CallAsync<bool>("ColorProfileService", "SetDisplayDefaultColorProfile", SelectedDisplay.DeviceName, colorProfile);
if (result)
{
SelectedDisplay.DefaultColorProfile = colorProfile;
}
}
private async Task DeleteColorProfileOnClick(string colorProfile)
{
SelectedColorProfileName = colorProfile;
}
private async Task CloneColorProfileOnClick(string colorProfile)
{
UseAsTemplate = true;
SelectedColorProfileName = colorProfile;
}
private async Task ColorProfileOnDeleteConfirmed()
{
if (SelectedColorProfileName == null)
{
return;
}
await _rpcClientService.CallAsync<bool>("ColorProfileService", "DeleteColorProfile", SelectedColorProfileName);
SelectedColorProfileName = null;
await RefreshDisplays();
StateHasChanged();
}
private async Task ColorProfileOnDisassociateConfirmed()
{
if (SelectedColorProfileName == null || SelectedDisplay == null)
{
return;
}
await _rpcClientService.CallAsync<bool>("ColorProfileService", "RemoveDisplayColorProfile", SelectedDisplay.DeviceName, SelectedColorProfileName);
SelectedColorProfileName = null;
await RefreshDisplays();
StateHasChanged();
}
private async Task ColorProfileOnAssociateConfirmed()
{
if (SelectedColorProfileName == null || SelectedDisplay == null)
{
return;
}
await _rpcClientService.CallAsync<bool>("ColorProfileService", "AddDisplayColorProfile", SelectedDisplay.DeviceName, SelectedColorProfileName, false);
SelectedColorProfileName = null;
await RefreshDisplays();
StateHasChanged();
}
private void SelectedDisplayChanged()
{
SelectedDisplay = Displays?.FirstOrDefault(d => d.DisplayId == SelectedDisplayId);
}
private async Task AfterColorProfileSaved(ColorProfileDto colorProfile)
{
await RefreshDisplays();
StateHasChanged();
}
private async Task RefreshDisplays()
{
Displays = await _rpcClientService.CallAsync<List<DisplayInfoDto>>("ColorProfileService", "GetDisplayInfos");
ColorProfileNames = await _rpcClientService.CallAsync<List<string>>("ColorProfileService", "GetAllColorProfileNames");
if (Displays?.Any() == true)
{
SelectedDisplay = Displays.FirstOrDefault(d => d.DisplayId == SelectedDisplayId) ?? Displays.First();
SelectedDisplayId = SelectedDisplay.DisplayId;
ColorProfileNames = ColorProfileNames?.Except(SelectedDisplay.ColorProfiles).ToList();
}
}
}
@@ -1,7 +1,7 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.Shared.Contracts.Game
@using ColorControl.Shared.Services
@using ColorControl.UI.Services
@using ColorControl.UI.Components.Pages.Generic
@using NvAPIWrapper.Native.Display
@using ColorControl.Shared.Common
@@ -13,7 +13,7 @@
@inject NavigationManager _navigationManager;
<div class="modal" id="presetModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" @onfocus="ModalOnFocus">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@(Preset?.id == 0 ? $"Settings for new preset" : $"Settings of preset {Preset?.IdOrName}")</h5>
@@ -1,7 +1,7 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.Shared.Contracts.Game
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@using ColorControl.UI.Components.Pages.Generic
@@ -9,7 +9,7 @@
@inject RpcUiClientService _rpcClientService
<div class="modal" id="settingsModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">NVIDIA settings</h5>
@@ -2,7 +2,7 @@
@using ColorControl.Shared.Contracts;
@using ColorControl.Shared.Contracts.Game;
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.UI.Components.Pages.Generic
@using ColorControl.Shared.Common;
@using ColorControl.UI.Generics
@@ -52,6 +52,10 @@ else
{
<div class="row mb-3">
<div class="col">
@if (!Presets.Any())
{
<span>No presets found</span>
}
<div class="list-group">
@foreach (var preset in Presets)
{
@@ -66,11 +70,21 @@ else
}
</h5>
</div>
<span>
<span class="d-none d-lg-block">
<button class="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => PresetOnDeleteClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Delete</button>
<button class="btn btn-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnCloneClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Clone</button>
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnEditClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Edit</button>
</span>
<div class="d-lg-none" @onclick:preventDefault @onclick:stopPropagation>
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-three-dots-vertical"></i>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => PresetOnDeleteClick(preset)">Delete</a></li>
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnCloneClick(preset)">Clone</a></li>
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnEditClick(preset)">Edit</a></li>
</ul>
</div>
</div>
</a>
}
@@ -1,10 +1,10 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@rendermode InteractiveServer
<FieldsModal ModalId="confirmModal" Fields="Fields" Title="@Title" OnConfirm="ConfirmClick"></FieldsModal>
<FieldsModal ModalId="@ModalId" Fields="Fields" Title="@Title" OnConfirm="ConfirmClick"></FieldsModal>
@code {
[Parameter]
@@ -16,6 +16,9 @@
[Parameter]
public Func<Task>? OnConfirm { get; set; }
[Parameter]
public string ModalId { get; set; } = "confirmModal";
public List<FieldDefinition>? Fields { get; set; }
protected override async Task OnParametersSetAsync()
@@ -1,5 +1,5 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@rendermode InteractiveServer
@@ -1,5 +1,5 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@rendermode InteractiveServer
@@ -7,7 +7,7 @@
@inject NavigationManager _navigationManager;
<div class="modal" id="@ModalId" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" @onfocus="ModalFocus">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@Title</h5>
@@ -1,6 +1,6 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.EventDispatcher
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
<div class="row mx-0 mb-2">
@@ -0,0 +1,96 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.EventDispatcher
@using ColorControl.Shared.Services;
@using ColorControl.Shared.Common;
@using ColorControl.UI.Services
@rendermode InteractiveServer
@inject JSHelper jsHelper;
@inject NotificationService notificationService;
@inject NavigationManager _navigationManager;
@if (Notifications?.Any() == true)
{
<div aria-live="polite" aria-atomic="true" class="position-relative">
<div class="toast-container bottom-0 end-0 p-3">
@foreach (var notification in Notifications)
{
<div class="toast" id="toast_@notification.Id" role="alert" aria-live="assertive" aria-atomic="true" @onclick="() => NotificationOnClick(notification)">
<div class="toast-header" style="background-color: var(--bs-@notification.Level-bg-subtle)">
<span class="w-100 d-flex">
<div class="alert alert-@notification.Level mb-0 py-1">
<strong class="me-auto">@notification.Category</strong>
</div>
</span>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body">
@if (notification.InternalUrl != null)
{
<ul class="list-group-flush p-0 mb-0">
<li class="list-group-item">@notification.Message</li>
</ul>
}
else
{
@notification.Message
}
</div>
</div>
}
</div>
</div>
}
@code {
private List<NotificationDto> Notifications { get; } = [];
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
notificationService.OnNotification += AddNotification;
}
}
protected async Task AddNotification(NotificationDto notification)
{
RemoveOldNotifications();
var minShownAt = DateTime.UtcNow.AddSeconds(-5);
// Suppress duplicate noficiations
if (Notifications.Any(n => n.Message == notification.Message && n.Level == notification.Level && n.ShownAt >= minShownAt))
{
return;
}
Notifications.Add(notification);
await InvokeAsync(StateHasChanged);
await jsHelper.ShowToast($"toast_{notification.Id}");
notification.ShownAt = DateTime.UtcNow;
}
private async Task NotificationOnClick(NotificationDto notification)
{
if (notification.InternalUrl != null)
{
_navigationManager.NavigateTo(notification.InternalUrl);
}
Notifications.Remove(notification);
}
private void RemoveOldNotifications()
{
var minDateTime = DateTime.UtcNow.AddMinutes(-1);
Notifications.RemoveAll(n => n.ShownAt.HasValue && n.ShownAt <= minDateTime);
}
}
@@ -1,6 +1,6 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.EventDispatcher
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@rendermode InteractiveServer
@@ -3,7 +3,7 @@
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.Shared.Contracts.Samsung
@using ColorControl.Shared.EventDispatcher
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@typeparam TPreset where TPreset : PresetBase
@@ -1,6 +1,6 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.EventDispatcher
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@rendermode InteractiveServer
+35 -4
View File
@@ -1,13 +1,18 @@
@page "/"
@using ColorControl.Shared.Common
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.UI.Generics
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@rendermode InteractiveServer
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@inject NavigationManager _navigationManager;
@inject NotificationService _notificationService
@inject ProtectedSessionStorage _sessionStorage
<PageTitle>Home</PageTitle>
@@ -24,10 +29,10 @@ else
<div class="row">
@foreach (var module in Modules.Where(m => m.IsActive))
{
<div class="col">
<div class="card h-100 mb-3">
<div class="col-lg-4 col-xl mb-3">
<div class="card h-100">
<div class="card-header">
@module.DisplayName
<span class="me-2 bi bi-@module.GetIconClass()" aria-hidden="true"></span>@module.DisplayName
</div>
<div class="card-body">
<a href="@module.Link" class="btn btn-primary stretched-link mb-2">Open module</a>
@@ -47,6 +52,8 @@ else
@code {
private Config? Config;
private List<ModuleDto>? Modules;
private InfoDto? Info;
private bool IsUpdateAvailable;
protected override async Task OnInitializedAsync()
{
@@ -57,4 +64,28 @@ else
Modules.ForEach(m => m.Link = m.DisplayName.Split(' ')[0]);
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender || Info != null || Config?.CheckForUpdates != true)
{
return;
}
var checkedForUpdates = await _sessionStorage.TryGet<bool>("checkedForUpdates");
if (!checkedForUpdates)
{
Info = await _rpcClientService.CallAsync<InfoDto>("OptionsService", "GetInfo");
await _sessionStorage.SetAsync("checkedForUpdates", true);
if (!IsUpdateAvailable && Info?.UpdateInfoDto?.UpdateAvailable == true)
{
IsUpdateAvailable = true;
var message = $"Update available: version {Info.UpdateInfoDto.NewVersionNumber}";
_notificationService.SendNotification(new NotificationDto(message) { InternalUrl = "/info", ScheduledAt = DateTime.UtcNow.AddSeconds(5) });
}
}
}
}
+72 -1
View File
@@ -1,13 +1,14 @@
@page "/info"
@using ColorControl.Shared.Common;
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@rendermode InteractiveServer
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@inject NavigationManager _navigationManager;
@inject IHttpContextAccessor _httpContextAccessor
<PageTitle>Logging</PageTitle>
@@ -20,7 +21,41 @@
else
{
<h3 class="mb-2">@info.ApplicationTitle - @info.LegalCopyright</h3>
<div class="d-flex align-items-baseline">
@if (IsUpdateInstalled)
{
if (IsEmbeddedBrowser)
{
<button type="button" class="btn btn-primary me-2" @onclick="RestartOnClick">Restart</button>
}
else
{
<button type="button" class="btn btn-primary me-2" @onclick="ReloadOnClick">Reload</button>
}
<span>New version @info.UpdateInfoDto.NewVersionNumber is installed. Click the button to restart the application.</span>
}
else
{
<button type="button" class="btn btn-primary me-2" disabled="@(!info.UpdateInfoDto.UpdateAvailable || IsInstallingUpdate)" @onclick="InstallUpdateOnClick">
@if (IsInstallingUpdate)
{
<span class="spinner-border spinner-border-sm me-1" aria-hidden="true"></span>
}
<span role="status">Install update</span>
</button>
@if (info.UpdateInfoDto.UpdateAvailable)
{
<span>New version @info.UpdateInfoDto.NewVersionNumber available. Click the button to install the update.</span>
}
else
{
<span>You have the latest version</span>
}
}
</div>
<br/>
<h5>This app contains the following 3rd party plugins:</h5>
<ul class="list-group">
<li class="list-group-item">lgtv.net by gr4b4z</li>
@@ -41,10 +76,46 @@ else
@code {
private InfoDto? info;
private bool IsInstallingUpdate;
private bool IsUpdateInstalled;
private bool IsEmbeddedBrowser;
protected override async Task OnInitializedAsync()
{
info = await _rpcClientService.CallAsync<InfoDto>("OptionsService", "GetInfo");
}
private async Task InstallUpdateOnClick(MouseEventArgs e)
{
IsInstallingUpdate = true;
try
{
var result = await _rpcClientService.CallAsync<bool>("OptionsService", "InstallUpdate");
if (!result)
{
return;
}
IsUpdateInstalled = true;
IsEmbeddedBrowser = _httpContextAccessor.HttpContext?.Request.Headers["user-agent"].ToString().Contains("ColorControlEmbedded") == true;
}
finally
{
IsInstallingUpdate = false;
}
}
private async Task RestartOnClick(MouseEventArgs e)
{
var result = await _rpcClientService.CallAsync<bool>("OptionsService", "RestartAfterUpdate");
}
private async Task ReloadOnClick(MouseEventArgs e)
{
var result = await _rpcClientService.CallAsync<bool>("OptionsService", "RestartAfterUpdate");
_navigationManager.Refresh();
}
}
@@ -1,6 +1,6 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.LG
@using ColorControl.Shared.Services
@using ColorControl.UI.Services
@using NvAPIWrapper.Native.Display
@using ColorControl.Shared.Common
@@ -11,7 +11,7 @@
@inject NavigationManager _navigationManager;
<div class="modal" id="deviceModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@(Device?.Name == null ? $"Settings for new device" : $"Settings of device {Device.Name}")</h5>
@@ -1,6 +1,6 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.LG
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@using ColorControl.UI.Components.Pages.Generic
@@ -1,6 +1,6 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.LG
@using ColorControl.Shared.Services
@using ColorControl.UI.Services
@using ColorControl.UI.Components.Pages.Generic
@using NvAPIWrapper.Native.Display
@using ColorControl.Shared.Common
@@ -13,7 +13,7 @@
@inject NavigationManager _navigationManager;
<div class="modal" id="presetModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" @onfocus="ModalOnFocus">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@(Preset?.id == 0 ? $"Settings for new preset" : $"Settings of preset {Preset?.IdOrName}")</h5>
@@ -1,6 +1,6 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.LG
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@using ColorControl.UI.Components.Pages.Generic
@@ -4,7 +4,7 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.Shared.Contracts.LG
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.UI.Components.Pages.Generic
@using ColorControl.UI.Generics
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@@ -155,6 +155,10 @@ else
</div>
<div class="row mb-3">
<div class="col">
@if (!Presets.Any())
{
<span>No presets found</span>
}
<div class="list-group">
@foreach (var preset in Presets)
{
@@ -168,11 +172,21 @@ else
}
<div><small class="text-muted">@preset.shortcut</small></div>
</div>
<span>
<span class="d-none d-lg-block">
<button class="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => PresetOnDeleteClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Delete</button>
<button class="btn btn-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnCloneClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Clone</button>
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnEditClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Edit</button>
</span>
<div class="d-lg-none" @onclick:preventDefault @onclick:stopPropagation>
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-three-dots-vertical"></i>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => PresetOnDeleteClick(preset)">Delete</a></li>
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnCloneClick(preset)">Clone</a></li>
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnEditClick(preset)">Edit</a></li>
</ul>
</div>
</div>
</a>
}
@@ -215,7 +229,7 @@ else
Devices = await _rpcClientService.CallAsync<List<LgDeviceDto>>("LgService", "GetDevices");
InvokableActions = await _rpcClientService.CallAsync<List<InvokableActionDto<LgPreset>>>("LgService", "GetInvokableActions");
ActionGroups = InvokableActions.GroupBy(a => a.Category).Select(g => new ActionGroup(g.Key, g.ToList())).ToList();
ActionGroups = InvokableActions?.GroupBy(a => a.Category).Select(g => new ActionGroup(g.Key, g.ToList())).ToList() ?? new List<ActionGroup>();
}
protected override async Task OnAfterRenderAsync(bool firstTime)
@@ -1,7 +1,7 @@
@page "/logging"
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@rendermode InteractiveServer
@@ -154,10 +154,13 @@ else
protected override async Task OnInitializedAsync()
{
config = await _rpcClientService.CallAsync<Config>("OptionsService", "GetConfig");
config ??= await _rpcClientService.CallAsync<Config>("OptionsService", "GetConfig");
logLevel = config.LogLevel;
await GetLog();
if (allLines == null)
{
await GetLog();
}
}
private async Task GetLog()
@@ -1,6 +1,6 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using NvAPIWrapper.Native.Display
@using ColorControl.Shared.Common;
@@ -1,6 +1,6 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using NvAPIWrapper.Native.Display
@using ColorControl.Shared.Common;
@@ -11,7 +11,7 @@
@inject NavigationManager _navigationManager;
<div class="modal modal-lg" id="driverSettingsModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" @onfocus="OnFocus">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@(Preset != null ? Preset.IsDisplayPreset ? $"Driver settings for display {Preset.displayName}" : $"Driver settings for {Preset.IdOrName}" : "Driver settings")</h5>
@@ -38,6 +38,12 @@
}
</select>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" @bind="OnlyFoundPrograms" id="onlyFoundPrograms" @bind:after="OnlyFoundProgramsOnChange">
<label class="form-check-label" for="onlyFoundPrograms">
Only show found programs
</label>
</div>
}
<div class="accordion accordion-flush" id="driverSettingsAccordion">
@@ -137,6 +143,8 @@
private bool IsVisible { get; set; }
private bool OnlyFoundPrograms = true;
protected override async Task OnParametersSetAsync()
{
if (_preset != Preset)
@@ -232,12 +240,17 @@
await LoadDriverSettings();
}
private async Task OnlyFoundProgramsOnChange()
{
ProfileNames = await _rpcClientService.CallAsync<List<string>>("NvService", "GetDriverProfileNames", OnlyFoundPrograms);
}
private async Task OnFocus(FocusEventArgs e)
{
if (!IsVisible)
{
IsVisible = true;
ProfileNames ??= await _rpcClientService.CallAsync<List<string>>("NvService", "GetDriverProfileNames");
ProfileNames ??= await _rpcClientService.CallAsync<List<string>>("NvService", "GetDriverProfileNames", OnlyFoundPrograms);
await LoadDriverSettings();
}
}
@@ -1,6 +1,6 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.UI.Components.Pages.Generic
@using NvAPIWrapper.Native.Display
@using ColorControl.Shared.Common;
@@ -12,7 +12,7 @@
@inject NavigationManager _navigationManager;
<div class="modal" id="presetModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" @onfocus="ModalOnFocus">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@(Preset?.IsDisplayPreset == true ? $"Settings for display {Preset?.displayName}" : Preset?.id == 0 ? $"Settings for new preset" : $"Settings of preset {Preset?.IdOrName}")</h5>
@@ -448,6 +448,7 @@
<div class="mb-2">
<label class="form-label" for="extendedColorimetry">Extended colorimetry</label>
<select class="form-select" id="extendedColorimetry" @bind="Preset.HdmiInfoFrameSettings.ExtendedColorimetry" @oninput="HdmiSettingsOnInput">
<option value="">Not selected</option>
@foreach (var extendedColorimetry in Enum.GetValues<InfoFrameVideoExtendedColorimetry>())
{
<option value="@extendedColorimetry.ToString()">@extendedColorimetry.GetDescription()</option>
@@ -458,6 +459,48 @@
</div>
</div>
}
@if (ColorProfileNames?.Any() == true)
{
<div class="accordion-item">
<h2 class="accordion-header">
<span class="d-flex align-items-baseline">
@if (!Preset.IsDisplayPreset)
{
<input class="form-check-input me-1" style="font-size: 1rem" title="Apply these settings" type="checkbox" @bind="Preset.applyOther" id="applyOther">
}
<button class="accordion-button collapsed py-2 @(Preset.applyOther ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#otherSettings">
Other settings
</button>
</span>
</h2>
<div id="otherSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
<div class="accordion-body">
<div class="mb-2">
<label class="form-label" for="colorProfile">Color profile</label>
<select class="form-select" id="colorProfile" @bind="Preset.ColorProfileSettings.ProfileName">
<option value="">Not selected</option>
@foreach (var colorProfile in ColorProfileNames)
{
<option value="@colorProfile">@colorProfile</option>
}
</select>
</div>
<div class="mb-2">
<label class="form-label" for="nvScaling">Scaling</label>
<select class="form-select" id="nvScaling" @bind="Preset.scaling">
<option value="">Not selected</option>
@foreach (var scaling in Enum.GetValues<Scaling>())
{
<option value="@scaling">@scaling.GetDescription()</option>
}
</select>
</div>
</div>
</div>
</div>
}
</div>
}
</form>
@@ -489,6 +532,7 @@
private List<VirtualResolution>? resolutions;
private List<Rational>? refreshRates;
private List<NvPreset>? displayPresets;
private List<string>? ColorProfileNames;
private string WasValidated = "";
private bool IsVisible;
@@ -511,10 +555,17 @@
selectedVirtualResolution = Preset?.DisplayConfig.Resolution?.GetVirtualString();
selectedRefreshRate = Preset?.DisplayConfig.RefreshRate?.ToString();
if (Preset?.IsDisplayPreset != true)
if (Preset?.IsDisplayPreset == false)
{
displayPresets = await _rpcClientService.CallAsync<List<NvPreset>>("NvService", "GetDisplayPresets");
}
var deviceName = Preset?.IsDisplayPreset == true ? Preset.DisplayId : Preset?.DisplayId ?? displayPresets?.FirstOrDefault()?.DisplayId;
if (Preset != null)
{
ColorProfileNames = await _rpcClientService.CallAsync<List<string>>("ColorProfileService", "GetDisplayColorProfiles", deviceName);
}
}
protected override void OnAfterRender(bool firstRender)
@@ -1,6 +1,6 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@using ColorControl.UI.Components.Pages.Generic
@@ -8,7 +8,7 @@
@inject RpcUiClientService _rpcClientService
<div class="modal" id="settingsModal" tabindex="-1" @onfocus="OnFocus">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">NVIDIA settings</h5>
@@ -2,7 +2,7 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.UI.Components.Pages.Generic
@using ColorControl.Shared.Common;
@using ColorControl.UI.Generics
@@ -74,6 +74,21 @@ else
<div class="card-body">
<small class="text-body">@preset.DisplayConfig.Resolution.ToString() @@ @preset.DisplayConfig.RefreshRate.ToString()Hz</small>
<small class="text-muted">@preset.colorData</small>
@if (preset.DitherState != NvDitherState.Auto)
{
<br />
<small class="text-body">Dithering: @preset.DitherState.GetDescription() @preset.NvDitherBits.GetDescription() @preset.NvDitherMode.GetDescription()</small>
}
@if (preset.HDREnabled)
{
<br />
<small class="text-body">HDR enabled @(preset.HdrSettings.OutputMode.HasValue ? $"({preset.HdrSettings.DisplayOutputMode.GetDescription()})" : ""), SDR brightness: @preset.SDRBrightness%</small>
}
@if (!string.IsNullOrEmpty(preset.ColorProfileSettings.ProfileName))
{
<br />
<small class="text-body">Color profile: @preset.ColorProfileSettings.ProfileName</small>
}
</div>
</div>
</div>
@@ -103,10 +118,14 @@ else
</div>
<div class="row mb-3">
<div class="col">
@if (!Presets.Any())
{
<span>No presets found</span>
}
<div class="list-group">
@foreach (var preset in Presets)
{
<a class="list-group-item list-group-item-action" @onclick="() => PresetOnClick(preset)" @onclick:preventDefault @onclick:stopPropagation>
<span class="list-group-item list-group-item-action" @onclick="() => PresetOnClick(preset)" @onclick:preventDefault @onclick:stopPropagation>
<div class="d-flex align-items-start justify-content-between">
<div>
<h5 class="mb-1">
@@ -117,34 +136,68 @@ else
}
</h5>
</div>
<span>
<span class="d-none d-lg-block">
<button class="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => PresetOnDeleteClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Delete</button>
<button class="btn btn-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnCloneClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Clone</button>
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnEditClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Edit</button>
</span>
<div class="d-lg-none" @onclick:preventDefault @onclick:stopPropagation>
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-three-dots-vertical"></i>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => PresetOnDeleteClick(preset)">Delete</a></li>
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnCloneClick(preset)">Clone</a></li>
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnEditClick(preset)">Edit</a></li>
</ul>
</div>
</div>
<p class="mb-1">
@preset.DisplayConfig.Resolution.ToString() @@ @preset.DisplayConfig.RefreshRate.ToString()Hz
@(preset.applyColorData ? " " + preset.colorData : "")
@if (preset.ApplyColorEnhancements)
{
<span>, @preset.ColorEnhancementSettings</span>
}
@if (preset.applyHDR)
{
<span>, HDR: @preset.GetHdrDescription()</span>
}
@if (preset.applyDriverSettings)
{
<span>, @preset.driverSettings.Count changed driver settings</span>
}
@if (preset.ApplyNovideoSettings)
{
<span>, @preset.NovideoSettings</span>
}
</p>
<small>
<ul class="list-group list-group-horizontal-md mb-1">
@if (preset.DisplayConfig.ApplyResolution || preset.DisplayConfig.ApplyRefreshRate)
{
<li class="list-group-item p-1">@preset.DisplayConfig.Resolution.ToString() @@ @preset.DisplayConfig.RefreshRate.ToString()Hz</li>
}
@if (preset.applyColorData)
{
<li class="list-group-item p-1">Color settings: @preset.colorData</li>
}
@if (preset.applyDithering)
{
<li class="list-group-item p-1">Dithering: @preset.GetDitheringDescription()</li>
}
@if (preset.ApplyColorEnhancements)
{
<li class="list-group-item p-1">@preset.ColorEnhancementSettings</li>
}
@if (preset.applyHDR)
{
<li class="list-group-item p-1">HDR: @preset.GetHdrDescription()</li>
}
@if (preset.applyDriverSettings)
{
<li class="list-group-item p-1">@preset.driverSettings.Count changed driver settings</li>
}
@if (preset.ApplyNovideoSettings)
{
<li class="list-group-item p-1">@preset.NovideoSettings</li>
}
@if (preset.applyHdmiSettings)
{
<li class="list-group-item p-1">@preset.GetHdmiSettingsDescription()</li>
}
@if (preset.applyOverclocking)
{
<li class="list-group-item p-1">@preset.GetOverclockingSettingsDescription()</li>
}
@if (preset.applyOther && !string.IsNullOrEmpty(preset.ColorProfileSettings.ProfileName))
{
<li class="list-group-item p-1">Color profile: @preset.ColorProfileSettings.ProfileName</li>
}
</ul>
</small>
<small class="text-muted">@preset.shortcut</small>
</a>
</span>
}
</div>
</div>
@@ -1,7 +1,7 @@
@page "/options"
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@using ColorControl.UI.Components.Pages.Generic
@@ -9,7 +9,8 @@
@inject RpcUiClientService _rpcClientService
@inject AppState AppState
@inject NavigationManager _navigationManager;
@inject NavigationManager _navigationManager
@inject NotificationService _notificationService
<PageTitle>Options</PageTitle>
@@ -250,6 +251,11 @@ else
{
_navigationManager.NavigateTo("/options", true);
}
else if (module.IsActive)
{
_notificationService.SendNotification(new NotificationDto("Cannot activate the module: check the log for more info", Constants.Danger) { InternalUrl = "/logging" });
module.IsActive = false;
}
}
private async Task PollingIntervalOnInput()
@@ -1,6 +1,6 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.Samsung
@using ColorControl.Shared.Services
@using ColorControl.UI.Services
@using NvAPIWrapper.Native.Display
@using ColorControl.Shared.Common
@@ -11,7 +11,7 @@
@inject NavigationManager _navigationManager;
<div class="modal" id="deviceModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" @onfocus="ModalOnFocus">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@(Device?.Name == null ? $"Add new device" : $"Settings of device {Device.Name}")</h5>
@@ -1,6 +1,6 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.Samsung
@using ColorControl.Shared.Services
@using ColorControl.UI.Services
@using ColorControl.UI.Components.Pages.Generic
@using NvAPIWrapper.Native.Display
@using ColorControl.Shared.Common
@@ -13,7 +13,7 @@
@inject NavigationManager _navigationManager;
<div class="modal" id="presetModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" @onfocus="ModalOnFocus">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@(Preset?.id == 0 ? $"Settings for new preset" : $"Settings of preset {Preset?.IdOrName}")</h5>
@@ -1,6 +1,6 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.Samsung
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@using ColorControl.UI.Components.Pages.Generic
@@ -1,6 +1,6 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.Samsung
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.Shared.Common;
@using ColorControl.UI.Components.Pages.Generic
@@ -4,7 +4,7 @@
@using ColorControl.Shared.Contracts
@using ColorControl.Shared.Contracts.NVIDIA
@using ColorControl.Shared.Contracts.Samsung
@using ColorControl.Shared.Services;
@using ColorControl.UI.Services;
@using ColorControl.UI.Components.Pages.Generic
@using ColorControl.UI.Generics
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
@@ -153,6 +153,10 @@ else
</div>
<div class="row mb-3">
<div class="col">
@if (!Presets.Any())
{
<span>No presets found</span>
}
<div class="list-group">
@foreach (var preset in Presets)
{
@@ -166,11 +170,21 @@ else
}
<div><small class="text-muted">@preset.shortcut</small></div>
</div>
<span>
<span class="d-none d-lg-block">
<button class="btn btn-danger btn-sm" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => PresetOnDeleteClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Delete</button>
<button class="btn btn-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnCloneClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Clone</button>
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnEditClick(preset)" @onclick:preventDefault @onclick:stopPropagation>Edit</button>
</span>
<div class="d-lg-none" @onclick:preventDefault @onclick:stopPropagation>
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-three-dots-vertical"></i>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#confirmModal" @onclick="() => PresetOnDeleteClick(preset)">Delete</a></li>
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnCloneClick(preset)">Clone</a></li>
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => PresetOnEditClick(preset)">Edit</a></li>
</ul>
</div>
</div>
</a>
}