mirror of
https://github.com/Maassoft/ColorControl.git
synced 2026-10-22 00:22:18 -05:00
add pages for new UI, fix issues
This commit is contained in:
1 parent
9a9424f1af
commit
4ed670c01b
159 files changed
+18672
-9709
No files matched your search
@@ -0,0 +1,391 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.AMD
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
@using ColorControl.Shared.Common;
|
||||
|
||||
@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-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>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="() => IsVisible = false"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="row g-1 needs-validation @WasValidated" novalidate>
|
||||
@if (Preset == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (!Preset.IsDisplayPreset)
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="name">Name</label>
|
||||
<input class="form-control" type="text" @bind="Preset.name" required />
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="Preset.ShowInQuickAccess" id="showInQuickAccess">
|
||||
<label class="form-check-label" for="showInQuickAccess">
|
||||
Show in Quick Access
|
||||
</label>
|
||||
</div>
|
||||
<ShortcutInput Label="Shortcut" @bind-Shortcut="Preset.shortcut" />
|
||||
}
|
||||
<div class="accordion accordion-flush" id="presetAccordion">
|
||||
|
||||
@if (!Preset.IsDisplayPreset)
|
||||
{
|
||||
<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; visibility: hidden" type="checkbox">
|
||||
}
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#displaySettings">
|
||||
Display
|
||||
</button>
|
||||
</span>
|
||||
</h2>
|
||||
<div id="displaySettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="mb-2">
|
||||
<input class="form-check-input" type="checkbox" @bind="Preset.primaryDisplay" id="primaryDisplay">
|
||||
<label class="form-check-label" for="primaryDisplay">
|
||||
Primary display
|
||||
</label>
|
||||
</div>
|
||||
@if (displayPresets != null)
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="mode">Display</label>
|
||||
<select class="form-select" id="mode" @bind="Preset.DisplayId" disabled="@(Preset.primaryDisplay)">
|
||||
@foreach (var display in displayPresets)
|
||||
{
|
||||
<option value="@display.DisplayId">@display.displayName</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<input class="form-check-input" type="checkbox" @bind="Preset.DisplayConfig.IsPrimary" id="setAsPrimaryDisplay" disabled="@(Preset.primaryDisplay)">
|
||||
<label class="form-check-label" for="setAsPrimaryDisplay">
|
||||
Set display as primary display
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (resolutions != null)
|
||||
{
|
||||
<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.DisplayConfig.ApplyResolution" id="applyResolution">
|
||||
}
|
||||
<button class="accordion-button py-2 @(Preset.DisplayConfig.ApplyResolution ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#resolutionSettings">
|
||||
Resolution
|
||||
</button>
|
||||
</span>
|
||||
</h2>
|
||||
<div id="resolutionSettings" class="accordion-collapse collapse @(Preset.IsDisplayPreset ? "show" : "")" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
@if (resolutions != null)
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="activeResolution">Active resolution</label>
|
||||
<select class="form-select" id="activeResolution" @bind="selectedActiveResolution">
|
||||
@foreach (var resolution in resolutions)
|
||||
{
|
||||
<option value="@resolution.GetActiveString()">@resolution.GetActiveString()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="virtualResolution">Virtual resolution</label>
|
||||
<select class="form-select" id="virtualResolution" @bind="selectedVirtualResolution">
|
||||
@foreach (var resolution in resolutions)
|
||||
{
|
||||
<option value="@resolution.GetVirtualString()">@resolution.GetVirtualString()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="scaling">Scaling/Aspect ratio</label>
|
||||
<select class="form-select" id="scaling" @bind="Preset.DisplayConfig.Scaling">
|
||||
@foreach (var scaling in Enum.GetValues<Shared.Native.CCD.DisplayConfigScaling>())
|
||||
{
|
||||
<option value="@scaling.ToString()">@scaling.ToString()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="rotation">Rotation</label>
|
||||
<select class="form-select" id="rotation" @bind="Preset.DisplayConfig.Rotation">
|
||||
@foreach (var rotation in Enum.GetValues<Shared.Native.CCD.DisplayConfigRotation>())
|
||||
{
|
||||
<option value="@rotation.ToString()">@rotation.ToString()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (refreshRates != null)
|
||||
{
|
||||
<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.DisplayConfig.ApplyRefreshRate" id="applyRefreshRate">
|
||||
}
|
||||
<button class="accordion-button collapsed py-2 @(Preset.DisplayConfig.ApplyRefreshRate ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#refreshRateSettings">
|
||||
Refresh Rate
|
||||
</button>
|
||||
</span>
|
||||
</h2>
|
||||
<div id="refreshRateSettings" class="accordion-collapse collapse show" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="refreshRate">Refresh Rate</label>
|
||||
<select class="form-select" id="refreshRate" @bind="selectedRefreshRate">
|
||||
@foreach (var refreshRate in refreshRates)
|
||||
{
|
||||
<option value="@refreshRate.ToString()">@refreshRate.ToString() Hz</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<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.applyColorData" id="applyColorData">
|
||||
}
|
||||
<button class="accordion-button collapsed py-2 @(Preset.applyColorData ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#colorSettings">
|
||||
Color settings
|
||||
</button>
|
||||
</span>
|
||||
</h2>
|
||||
<div id="colorSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="bitDepth">Bit depth</label>
|
||||
<select class="form-select" id="bitDepth" @bind="Preset.colorDepth">
|
||||
@foreach (var bitDepth in Enum.GetValues<ADLColorDepth>())
|
||||
{
|
||||
<option value="@bitDepth.ToString()">@bitDepth.GetDescription()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="colorFormat">Format</label>
|
||||
<select class="form-select" id="colorFormat" @bind="Preset.pixelFormat">
|
||||
@foreach (var format in Enum.GetValues<ADLPixelFormat>())
|
||||
{
|
||||
<option value="@format.ToString()">@format.ToString()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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.applyDithering" id="applyDithering">
|
||||
}
|
||||
<button class="accordion-button collapsed py-2 @(Preset.applyDithering ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#ditherSettings">
|
||||
Dithering
|
||||
</button>
|
||||
</span>
|
||||
</h2>
|
||||
<div id="ditherSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="state">State</label>
|
||||
<select class="form-select" id="state" @bind="Preset.ditherState">
|
||||
@foreach (var state in Enum.GetValues<ADLDitherState>())
|
||||
{
|
||||
<option value="@state.ToString()">@state.GetDescription()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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.applyHDR" id="applyHDR">
|
||||
}
|
||||
<button class="accordion-button collapsed py-2 @(Preset.applyHDR ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#hdrSettings">
|
||||
HDR
|
||||
</button>
|
||||
</span>
|
||||
</h2>
|
||||
<div id="hdrSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="mb-2">
|
||||
<input class="form-check-input" type="checkbox" @bind="Preset.HDREnabled" id="hdrEnabled">
|
||||
<label class="form-check-label" for="hdrEnabled">
|
||||
HDR enabled
|
||||
</label>
|
||||
</div>
|
||||
@if (!Preset.IsDisplayPreset) {
|
||||
<div class="mb-2">
|
||||
<input class="form-check-input" type="checkbox" @bind="Preset.toggleHDR" id="toggleHdr">
|
||||
<label class="form-check-label" for="toggleHdr">
|
||||
Toggle HDR
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
<div class="mb-2">
|
||||
<label class="form-range" for="sdrBrightness">SDR brightness (@Preset.SDRBrightness%)</label>
|
||||
<input class="form-range" type="range" min="0" max="100" @bind="Preset.SDRBrightness" id="sdrBrightness">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="() => IsVisible = false">Close</button>
|
||||
<button type="submit" class="btn btn-primary" data-bs-dismiss="@(IsValid() ? "modal" : "")" @onclick="ApplyClick">@(Preset?.IsDisplayPreset == true ? "Apply" : "Save")</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
public AmdPreset? Preset { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public AmdPreset? PresetParam { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<AmdPreset, Task>? AfterApply { get; set; }
|
||||
|
||||
private string? selectedActiveResolution;
|
||||
private string? selectedVirtualResolution;
|
||||
private string? selectedRefreshRate;
|
||||
|
||||
private List<VirtualResolution>? resolutions;
|
||||
private List<Rational>? refreshRates;
|
||||
private List<AmdPreset>? displayPresets;
|
||||
|
||||
private string WasValidated = "";
|
||||
private bool IsVisible;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
Preset = PresetParam == null ? null : PresetParam.CloneWithId();
|
||||
if (Preset != null && PresetParam != null)
|
||||
{
|
||||
resolutions = await _rpcClientService.CallAsync<List<VirtualResolution>>("AmdService", "GetAvailableResolutionsV2", Preset);
|
||||
refreshRates = await _rpcClientService.CallAsync<List<Rational>>("AmdService", "GetAvailableRefreshRatesV2", Preset);
|
||||
}
|
||||
selectedActiveResolution = Preset?.DisplayConfig.Resolution?.GetActiveString();
|
||||
selectedVirtualResolution = Preset?.DisplayConfig.Resolution?.GetVirtualString();
|
||||
selectedRefreshRate = Preset?.DisplayConfig.RefreshRate?.ToString();
|
||||
|
||||
if (Preset?.IsDisplayPreset != true)
|
||||
{
|
||||
displayPresets = await _rpcClientService.CallAsync<List<AmdPreset>>("AmdService", "GetDisplayPresets");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ApplyClick(MouseEventArgs e)
|
||||
{
|
||||
if (Preset == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsValid())
|
||||
{
|
||||
WasValidated = "was-validated";
|
||||
return;
|
||||
}
|
||||
|
||||
var newResolution = resolutions?.FirstOrDefault(r => r.GetActiveString() == selectedActiveResolution);
|
||||
if (newResolution != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(selectedVirtualResolution) && selectedActiveResolution != selectedVirtualResolution)
|
||||
{
|
||||
newResolution = new VirtualResolution(newResolution.ActiveWidth, newResolution.ActiveHeight);
|
||||
var parts = selectedVirtualResolution.Split('x');
|
||||
newResolution.VirtualWidth = uint.Parse(parts[0]);
|
||||
newResolution.VirtualHeight = uint.Parse(parts[1]);
|
||||
}
|
||||
Preset.DisplayConfig.Resolution = newResolution;
|
||||
}
|
||||
|
||||
var newRefreshRate = refreshRates?.FirstOrDefault(r => r.ToString() == selectedRefreshRate);
|
||||
if (newRefreshRate != null)
|
||||
{
|
||||
Preset.DisplayConfig.RefreshRate = newRefreshRate;
|
||||
}
|
||||
|
||||
if (Preset.IsDisplayPreset)
|
||||
{
|
||||
//Preset.UpdateAutoApplySettings(PresetParam);
|
||||
|
||||
await ApplyDisplayPreset();
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await _rpcClientService.CallAsync<bool>("AmdService", "UpdatePreset", Preset);
|
||||
|
||||
if (AfterApply != null)
|
||||
{
|
||||
await AfterApply.Invoke(Preset);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ApplyDisplayPreset()
|
||||
{
|
||||
var result = await _rpcClientService.CallAsync<bool>("AmdService", "ApplyPreset", Preset);
|
||||
}
|
||||
|
||||
private void ModalOnFocus(FocusEventArgs e)
|
||||
{
|
||||
if (!IsVisible)
|
||||
{
|
||||
WasValidated = "";
|
||||
IsVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValid()
|
||||
{
|
||||
return Preset?.IsDisplayPreset == true || !string.IsNullOrEmpty(Preset?.name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.AMD
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
|
||||
@rendermode InteractiveServer
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
|
||||
<div class="modal" id="settingsModal" tabindex="-1" @onfocus="OnFocus">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">AMD settings</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="() => Config = null"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="row g-1">
|
||||
@if (Config == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<ShortcutInput Label="Quick Access Shortcut" @bind-Shortcut="Config.QuickAccessShortcut" />
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="() => Config = null">Close</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" @onclick="ApplyClick">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private AmdServiceConfig? Config { get; set; }
|
||||
|
||||
private async Task ApplyClick(MouseEventArgs e)
|
||||
{
|
||||
if (Config == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await _rpcClientService.CallAsync<bool>("AmdService", "UpdateConfig", Config);
|
||||
|
||||
if (result)
|
||||
{
|
||||
Config = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnFocus(FocusEventArgs e)
|
||||
{
|
||||
if (Config != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Config = await _rpcClientService.CallAsync<AmdServiceConfig>("AmdService", "GetConfig");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
@page "/AMD"
|
||||
|
||||
@using ColorControl.Shared.Common
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.AMD
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
@using ColorControl.UI.Generics
|
||||
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
@inject ProtectedLocalStorage _localStorage
|
||||
|
||||
<PageTitle>AMD controller</PageTitle>
|
||||
|
||||
<AmdSettings />
|
||||
|
||||
<div class="d-flex align-items-center">
|
||||
<h1>AMD controller</h1>
|
||||
<div class="d-flex ms-auto">
|
||||
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#settingsModal">
|
||||
Settings
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Displays</h2>
|
||||
|
||||
<AmdPresetPage PresetParam="CurrentPreset" AfterApply="AfterPresetApplied" />
|
||||
<ConfirmModal Title="Delete preset" Message="Are you sure you want to delete the preset?" OnConfirm="PresetOnDeleteConfirmed" />
|
||||
|
||||
@if (DisplayPresets == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
@if (!DisplayPresets.Any())
|
||||
{
|
||||
<span>No displays found</span>
|
||||
}
|
||||
@foreach (var preset in DisplayPresets)
|
||||
{
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
@preset.displayName
|
||||
@if (preset.DisplayConfig.IsPrimary == true)
|
||||
{
|
||||
<small class="text-muted ms-2">Primary</small>
|
||||
}
|
||||
<button type="button" class="btn btn-sm btn-primary ms-2" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => DisplayPresetOnClick(preset)">
|
||||
Settings
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<small class="text-body">@preset.DisplayConfig.Resolution.ToString() @@ @preset.DisplayConfig.RefreshRate.ToString()Hz</small>
|
||||
<small class="text-muted">@preset.colorDepth, @preset.pixelFormat</small>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Presets == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mb-2 d-flex align-items-center">
|
||||
<h2>Presets</h2>
|
||||
<div class="d-flex ms-auto">
|
||||
<select class="form-select me-2" id="presetOrder" @bind="PresetOrder" @bind:after="PresetOrderChanged">
|
||||
@foreach (var order in Enum.GetValues<PresetOrder>())
|
||||
{
|
||||
<option value="@order">Order by: @order.GetDescription()</option>
|
||||
}
|
||||
</select>
|
||||
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => AddPresetOnClick()">
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<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>
|
||||
<div class="d-flex align-items-start justify-content-between">
|
||||
<div>
|
||||
<h5 class="mb-1">
|
||||
@preset.name <small class="text-muted">@(preset.primaryDisplay ? "Primary display" : preset.displayName)</small>
|
||||
@if (preset.ShowInQuickAccess)
|
||||
{
|
||||
<span class="ms-2 badge text-bg-warning" title="Show in Quick Access">QA</span>
|
||||
}
|
||||
</h5>
|
||||
</div>
|
||||
<span>
|
||||
<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>
|
||||
<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>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<AmdPreset>? Presets;
|
||||
private List<AmdPreset>? DisplayPresets;
|
||||
private List<VirtualResolution>? Resolutions;
|
||||
|
||||
private AmdPreset? CurrentPreset;
|
||||
|
||||
private PresetOrder PresetOrder;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (firstTime)
|
||||
{
|
||||
PresetOrder = await _localStorage.TryGet<PresetOrder>("AmdPresetOrder");
|
||||
Presets = Presets?.OrderPresetsBy(PresetOrder);
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PresetOnClick(AmdPreset preset)
|
||||
{
|
||||
await _rpcClientService.CallAsync<bool>("AmdService", "ApplyPresetWithId", preset.id);
|
||||
}
|
||||
|
||||
private async Task PresetOnEditClick(AmdPreset preset)
|
||||
{
|
||||
CurrentPreset = preset;
|
||||
}
|
||||
|
||||
private async Task PresetOnCloneClick(AmdPreset preset)
|
||||
{
|
||||
CurrentPreset = new AmdPreset(preset);
|
||||
CurrentPreset.SetCloneValues();
|
||||
}
|
||||
|
||||
private async Task PresetOnDeleteClick(AmdPreset preset)
|
||||
{
|
||||
CurrentPreset = preset;
|
||||
}
|
||||
|
||||
private async Task PresetOnDeleteConfirmed()
|
||||
{
|
||||
if (CurrentPreset == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _rpcClientService.CallAsync<bool>("AmdService", "DeletePreset", CurrentPreset.id);
|
||||
await RefreshPresets();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task DisplayPresetOnClick(AmdPreset preset)
|
||||
{
|
||||
CurrentPreset = preset;
|
||||
}
|
||||
|
||||
private async Task AddPresetOnClick()
|
||||
{
|
||||
CurrentPreset = new AmdPreset();
|
||||
}
|
||||
|
||||
private async Task AfterPresetApplied(AmdPreset preset)
|
||||
{
|
||||
await RefreshPresets();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task PresetOrderChanged()
|
||||
{
|
||||
await _localStorage.SetAsync("AmdPresetOrder", PresetOrder);
|
||||
await RefreshPresets();
|
||||
}
|
||||
|
||||
private async Task RefreshPresets()
|
||||
{
|
||||
Presets = await _rpcClientService.CallAsync<List<AmdPreset>>("AmdService", "GetPresets");
|
||||
|
||||
Presets = Presets?.OrderPresetsBy(PresetOrder);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
@page "/counter"
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<PageTitle>Counter</PageTitle>
|
||||
|
||||
<h1>Counter</h1>
|
||||
|
||||
<p role="status">Current count: @currentCount</p>
|
||||
|
||||
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
|
||||
|
||||
@code {
|
||||
private int currentCount = 0;
|
||||
|
||||
private void IncrementCount()
|
||||
{
|
||||
currentCount++;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.NVIDIA
|
||||
@using ColorControl.Shared.Contracts.Game
|
||||
@using ColorControl.Shared.Services
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
@using NvAPIWrapper.Native.Display
|
||||
@using ColorControl.Shared.Common
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
@inject AppState AppState
|
||||
@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-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(Preset?.id == 0 ? $"Settings for new preset" : $"Settings of preset {Preset?.IdOrName}")</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="() => IsVisible = false"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="row g-1 needs-validation @WasValidated" novalidate>
|
||||
@if (Preset == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="name">Name</label>
|
||||
<input class="form-control" id="name" type="text" @bind="Preset.name" required />
|
||||
</div>
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox" @bind="Preset.ShowInQuickAccess" id="showInQuickAccess">
|
||||
<label class="form-check-label" for="showInQuickAccess">
|
||||
Show in Quick Access
|
||||
</label>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="path">File/URI</label>
|
||||
<div class="input-group mb-2">
|
||||
<input class="form-control" id="path" type="text" @bind="Preset.Path" required />
|
||||
<button type="button" class="btn btn-primary" @onclick="SelectFileOnClick" disabled="@SelectingFile">
|
||||
<span class="@(SelectingFile ? "spinner-border spinner-border-sm" : "")" aria-hidden="true"></span>
|
||||
<span role="status">Select...</span>
|
||||
</button>
|
||||
</div>
|
||||
<div id="selectFileHelp" class="form-text">
|
||||
Selecting a file opens a file dialog outside of the browser on the system where the ColorControl application is running.
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="parameters">Parameters</label>
|
||||
<input class="form-control" id="parameters" type="text" @bind="Preset.Parameters" />
|
||||
</div>
|
||||
|
||||
<div class="accordion accordion-flush" id="presetAccordion">
|
||||
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#advancedSettings">
|
||||
Advanced settings
|
||||
</button>
|
||||
</h2>
|
||||
<div id="advancedSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox" @bind="Preset.RunAsAdministrator" id="runAsAdministrator">
|
||||
<label class="form-check-label" for="runAsAdministrator">
|
||||
Run as administrator
|
||||
</label>
|
||||
</div>
|
||||
<FlagsInput Label="Process affinity" Labels="CpuAffinityLabels" @bind-Value="Preset.ProcessAffinityMask"></FlagsInput>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="priority">Process priority</label>
|
||||
<select class="form-select" id="priority" @bind="Preset.GameProcessPriorityClass">
|
||||
@foreach (var priority in Enum.GetValues<GamePriorityClass>())
|
||||
{
|
||||
<option value="@priority.ToString()">@priority.GetDescription()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#autoApplySettings">
|
||||
Auto-apply settings
|
||||
</button>
|
||||
</h2>
|
||||
<div id="autoApplySettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox" @bind="Preset.AutoSettings.AllowAutoApply" id="autoApply">
|
||||
<label class="form-check-label" for="autoApply">
|
||||
Automatically execute preset when process starts
|
||||
</label>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="autoApplyAction">Auto-apply action</label>
|
||||
<select class="form-select" id="priority" @bind="Preset.AutoSettings.ProcessAutoAction">
|
||||
@foreach (var action in Enum.GetValues<ProcessAutoAction>())
|
||||
{
|
||||
<option value="@action.ToString()">@action.GetDescription()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#steps">
|
||||
Steps
|
||||
</button>
|
||||
</h2>
|
||||
<div id="steps" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
<StepsInput Label="Pre-launch" @bind-Steps="Preset.PreLaunchSteps" TPreset="GamePreset"></StepsInput>
|
||||
<StepsInput Label="Post-launch" @bind-Steps="Preset.PostLaunchSteps" TPreset="GamePreset"></StepsInput>
|
||||
<StepsInput Label="Finalize" @bind-Steps="Preset.FinalizeSteps" TPreset="GamePreset"></StepsInput>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="() => IsVisible = false">Close</button>
|
||||
<button type="submit" class="btn btn-primary" data-bs-dismiss="@(IsValid() ? "modal" : "")" @onclick="ApplyClick">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private GamePreset? Preset;
|
||||
|
||||
[Parameter]
|
||||
public GamePreset? PresetParam { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<GamePreset, Task>? AfterApply { get; set; }
|
||||
|
||||
public List<string> CpuAffinityLabels = Enumerable.Range(0, Environment.ProcessorCount).Select(i => $"CPU #{i}").ToList();
|
||||
|
||||
public bool SelectingFile { get; set; }
|
||||
|
||||
private string WasValidated = "";
|
||||
private bool IsVisible;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (PresetParam == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Preset = PresetParam.CloneWithId();
|
||||
}
|
||||
|
||||
private async Task ApplyClick(MouseEventArgs e)
|
||||
{
|
||||
if (Preset == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsValid())
|
||||
{
|
||||
WasValidated = "was-validated";
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await _rpcClientService.CallAsync<bool>("GameService", "UpdatePreset", Preset);
|
||||
|
||||
if (AfterApply != null)
|
||||
{
|
||||
await AfterApply.Invoke(Preset);
|
||||
}
|
||||
|
||||
IsVisible = false;
|
||||
}
|
||||
|
||||
private bool IsValid()
|
||||
{
|
||||
return !string.IsNullOrEmpty(Preset?.name) && !string.IsNullOrEmpty(Preset?.Path);
|
||||
}
|
||||
|
||||
private async Task SelectFileOnClick(MouseEventArgs e)
|
||||
{
|
||||
if (Preset == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SelectingFile = true;
|
||||
try
|
||||
{
|
||||
var options = new RpcUiClientOptions
|
||||
{
|
||||
Timeout = 60000
|
||||
};
|
||||
|
||||
var result = await _rpcClientService.CallWithOptionsAsync<string>("GameService", "SelectPath", options);
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
Preset.Path = result;
|
||||
if (string.IsNullOrEmpty(Preset.name))
|
||||
{
|
||||
Preset.name = Path.GetFileNameWithoutExtension(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
SelectingFile = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ModalOnFocus(FocusEventArgs e)
|
||||
{
|
||||
if (!IsVisible)
|
||||
{
|
||||
WasValidated = "";
|
||||
IsVisible = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.NVIDIA
|
||||
@using ColorControl.Shared.Contracts.Game
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
|
||||
@rendermode InteractiveServer
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
|
||||
<div class="modal" id="settingsModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">NVIDIA settings</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="row g-1">
|
||||
@if (Config == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<ShortcutInput Label="Quick Access Shortcut" @bind-Shortcut="Config.QuickAccessShortcut" />
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="Config.ApplyExternallyLaunched" id="applyExternallyLaunched">
|
||||
<label class="form-check-label" for="applyExternallyLaunched">
|
||||
Apply presets when launching games externally
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" @onclick="ApplyClick">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private GameServiceConfig? Config { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
Config = await _rpcClientService.CallAsync<GameServiceConfig>("GameService", "GetConfig");
|
||||
}
|
||||
|
||||
private async Task ApplyClick(MouseEventArgs e)
|
||||
{
|
||||
if (Config == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await _rpcClientService.CallAsync<bool>("GameService", "UpdateConfig", Config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
@page "/Game"
|
||||
|
||||
@using ColorControl.Shared.Contracts;
|
||||
@using ColorControl.Shared.Contracts.Game;
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
@using ColorControl.Shared.Common;
|
||||
@using ColorControl.UI.Generics
|
||||
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
@inject ProtectedLocalStorage _localStorage
|
||||
|
||||
<PageTitle>Game launcher</PageTitle>
|
||||
|
||||
<GameSettings />
|
||||
|
||||
<div class="d-flex align-items-center">
|
||||
<h1>Game launcher</h1>
|
||||
<div class="d-flex ms-auto">
|
||||
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#settingsModal">
|
||||
Settings
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-2 d-flex align-items-center">
|
||||
<h2>Presets</h2>
|
||||
<div class="d-flex ms-auto">
|
||||
<select class="form-select me-2" id="presetOrder" @bind="PresetOrder" @bind:after="PresetOrderChanged">
|
||||
@foreach (var order in Enum.GetValues<PresetOrder>())
|
||||
{
|
||||
<option value="@order">Order by: @order.GetDescription()</option>
|
||||
}
|
||||
</select>
|
||||
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => AddPresetOnClick()">
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<GamePresetPage PresetParam="CurrentPreset" AfterApply="AfterPresetApplied" />
|
||||
<ConfirmModal Title="Delete preset" Message="Are you sure you want to delete the preset?" OnConfirm="PresetOnDeleteConfirmed" />
|
||||
|
||||
@if (Presets == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<div class="list-group">
|
||||
@foreach (var preset in Presets)
|
||||
{
|
||||
<a class="list-group-item list-group-item-action" @onclick="() => PresetOnClick(preset)">
|
||||
<div class="d-flex align-items-start justify-content-between">
|
||||
<div>
|
||||
<h5 class="mb-1">
|
||||
@preset.name
|
||||
@if (preset.ShowInQuickAccess)
|
||||
{
|
||||
<span class="ms-2 badge text-bg-warning" title="Show in Quick Access">QA</span>
|
||||
}
|
||||
</h5>
|
||||
</div>
|
||||
<span>
|
||||
<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>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<GamePreset>? Presets;
|
||||
private GamePreset? CurrentPreset;
|
||||
|
||||
private PresetOrder PresetOrder;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await RefreshPresets();
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstTime)
|
||||
{
|
||||
if (firstTime)
|
||||
{
|
||||
PresetOrder = await _localStorage.TryGet<PresetOrder>("GamePresetOrder");
|
||||
Presets = Presets?.OrderPresetsBy(PresetOrder);
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PresetOnClick(GamePreset preset)
|
||||
{
|
||||
await _rpcClientService.CallAsync<bool>("GameService", "ApplyPresetWithId", preset.id);
|
||||
}
|
||||
|
||||
private async Task PresetOnEditClick(GamePreset preset)
|
||||
{
|
||||
CurrentPreset = preset;
|
||||
}
|
||||
|
||||
private async Task PresetOnCloneClick(GamePreset preset)
|
||||
{
|
||||
CurrentPreset = new GamePreset(preset);
|
||||
CurrentPreset.SetCloneValues();
|
||||
}
|
||||
|
||||
private async Task PresetOnDeleteClick(GamePreset preset)
|
||||
{
|
||||
CurrentPreset = preset;
|
||||
}
|
||||
|
||||
private async Task PresetOnDeleteConfirmed()
|
||||
{
|
||||
if (CurrentPreset == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _rpcClientService.CallAsync<bool>("GameService", "DeletePreset", CurrentPreset.id);
|
||||
await RefreshPresets();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task AddPresetOnClick()
|
||||
{
|
||||
CurrentPreset = new GamePreset();
|
||||
}
|
||||
|
||||
private async Task AfterPresetApplied(GamePreset preset)
|
||||
{
|
||||
await RefreshPresets();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task PresetOrderChanged()
|
||||
{
|
||||
await _localStorage.SetAsync("GamePresetOrder", PresetOrder);
|
||||
await RefreshPresets();
|
||||
}
|
||||
|
||||
private async Task RefreshPresets()
|
||||
{
|
||||
Presets = await _rpcClientService.CallAsync<List<GamePreset>>("GameService", "GetPresets");
|
||||
|
||||
Presets = Presets?.OrderPresetsBy(PresetOrder);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<FieldsModal ModalId="confirmModal" Fields="Fields" Title="@Title" OnConfirm="ConfirmClick"></FieldsModal>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public string? Title { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? Message { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<Task>? OnConfirm { get; set; }
|
||||
|
||||
public List<FieldDefinition>? Fields { get; set; }
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (Message != null)
|
||||
{
|
||||
Fields = [new FieldDefinition { FieldType = FieldType.Label, Label = Message }];
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConfirmClick()
|
||||
{
|
||||
if (OnConfirm != null)
|
||||
{
|
||||
await OnConfirm();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject NavigationManager _navigationManager;
|
||||
|
||||
@if (Fields == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@foreach (var field in Fields)
|
||||
{
|
||||
if (field.FieldType == FieldType.Text)
|
||||
{
|
||||
var fieldId = $"fld_{Random.Shared.Next()}";
|
||||
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="@fieldId">@field.Label</label>
|
||||
<input class="form-control" type="text" @bind="field.StringValue" id="@fieldId">
|
||||
@if (field.SubLabel != null)
|
||||
{
|
||||
<div id="@(fieldId)_help" class="form-text">
|
||||
@field.SubLabel
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
if (field.FieldType == FieldType.Label)
|
||||
{
|
||||
var fieldId = $"fld_{Random.Shared.Next()}";
|
||||
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="@fieldId">@field.Label</label>
|
||||
@if (field.SubLabel != null)
|
||||
{
|
||||
<div id="@(fieldId)_help" class="form-text">
|
||||
@field.SubLabel
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else if (field.FieldType == FieldType.DropDown)
|
||||
{
|
||||
var fieldId = $"fld_{Random.Shared.Next()}";
|
||||
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="@fieldId">@field.Label</label>
|
||||
<select class="form-select" id="@fieldId" @bind="field.StringValue">
|
||||
@if (field.ValueType != null)
|
||||
{
|
||||
@foreach (var item in Enum.GetValues(field.ValueType))
|
||||
{
|
||||
<option value="@item.ToString()">@((item as IConvertible).GetDescription())</option>
|
||||
}
|
||||
}
|
||||
else if (field.Values != null)
|
||||
{
|
||||
@foreach (var item in field.Values)
|
||||
{
|
||||
<option value="@item.ToString()">@item.ToString()</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
@if (field.SubLabel != null)
|
||||
{
|
||||
<div id="@(fieldId)_help" class="form-text">
|
||||
@field.SubLabel
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else if (field.FieldType == FieldType.Shortcut)
|
||||
{
|
||||
<ShortcutInput Label="@field.Label" @bind-Shortcut="field.StringValue"></ShortcutInput>
|
||||
}
|
||||
else if (field.FieldType == FieldType.CheckBox)
|
||||
{
|
||||
var fieldId = $"fld_{Random.Shared.Next()}";
|
||||
|
||||
<div class="mb-3 form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="field.BoolValue" id="@fieldId">
|
||||
<label class="form-check-label" for="@fieldId">
|
||||
@field.Label
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
else if (field.FieldType == FieldType.Numeric && (field.MaxValue - field.MinValue) <= 50)
|
||||
{
|
||||
var fieldId = $"fld_{Random.Shared.Next()}";
|
||||
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="@fieldId">@field.Label</label>
|
||||
<input class="form-control" type="number" @bind="field.IntValue" min="@((int)field.MinValue)" max="@((int)field.MaxValue)" id="@fieldId">
|
||||
@if (field.SubLabel != null)
|
||||
{
|
||||
<div id="@(fieldId)_help" class="form-text">
|
||||
@field.SubLabel
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else if (field.FieldType == FieldType.Numeric && (field.MaxValue - field.MinValue) > 50)
|
||||
{
|
||||
var fieldId = $"fld_{Random.Shared.Next()}";
|
||||
|
||||
<div class="mb-2">
|
||||
<label class="form-range" for="@fieldId">@field.Label (Current value: @field.IntValue)</label>
|
||||
<input class="form-range" type="range" min="@((int)field.MinValue)" max="@((int)field.MaxValue)" @bind="field.IntValue" id="@fieldId">
|
||||
@if (field.SubLabel != null)
|
||||
{
|
||||
<div id="@(fieldId)_help" class="form-text">
|
||||
@field.SubLabel
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public List<FieldDefinition>? Fields { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@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-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@Title</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="OnClose"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
@if (IsVisible)
|
||||
{
|
||||
<FieldDefs Fields="Fields"></FieldDefs>
|
||||
}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="OnClose">Cancel</button>
|
||||
@if (SecondaryCaption != null)
|
||||
{
|
||||
<button type="button" class="btn btn-secondary" @onclick="SecondaryClick">@SecondaryCaption</button>
|
||||
}
|
||||
<button class="btn btn-primary" type="button" disabled="@IsNextDisabled" data-bs-dismiss="@Dismiss" @onclick="ConfirmClick">
|
||||
@if (IsNextDisabled)
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm me-1" aria-hidden="true"></span>
|
||||
}
|
||||
<span role="status">@ConfirmCaption</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public string? ModalId { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? ConfirmCaption { get; set; } = "Yes";
|
||||
|
||||
[Parameter]
|
||||
public string? SecondaryCaption { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? Dismiss { get; set; } = "modal";
|
||||
|
||||
[Parameter]
|
||||
public string? Title { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? Message { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public List<FieldDefinition>? Fields { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<Task>? OnConfirm { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<Task>? OnSecondary { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<Task>? OnShow { get; set; }
|
||||
|
||||
private bool IsVisible { get; set; }
|
||||
private bool IsNextDisabled { get; set; }
|
||||
|
||||
private async Task ConfirmClick(MouseEventArgs e)
|
||||
{
|
||||
if (OnConfirm == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dismissing = Dismiss == "modal";
|
||||
IsNextDisabled = Dismiss == null;
|
||||
try
|
||||
{
|
||||
await OnConfirm();
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsNextDisabled = false;
|
||||
}
|
||||
|
||||
if (dismissing)
|
||||
{
|
||||
IsVisible = false;
|
||||
Dismiss = "modal";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SecondaryClick(MouseEventArgs e)
|
||||
{
|
||||
if (OnSecondary == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await OnSecondary();
|
||||
}
|
||||
|
||||
private async Task ModalFocus(FocusEventArgs e)
|
||||
{
|
||||
if (!IsVisible)
|
||||
{
|
||||
IsVisible = true;
|
||||
|
||||
if (OnShow != null)
|
||||
{
|
||||
await OnShow();
|
||||
//StateHasChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnClose(MouseEventArgs e)
|
||||
{
|
||||
IsVisible = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.EventDispatcher
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
|
||||
<div class="row mx-0 mb-2">
|
||||
<label class="form-label ps-0">@Label</label>
|
||||
|
||||
@if (Labels != null)
|
||||
{
|
||||
var index = 1;
|
||||
var checkValue = 1;
|
||||
foreach (var label in Labels)
|
||||
{
|
||||
var name = $"check{index}";
|
||||
var checkIndex = index;
|
||||
|
||||
<div class="form-check form-check-inline col-@(Col)">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="@name" checked="@((Value & checkValue) == checkValue)" @onchange="(ChangeEventArgs e) => CheckOnChange(e, checkIndex)">
|
||||
<label class="form-check-label" for="@name">@label</label>
|
||||
</div>
|
||||
|
||||
index++;
|
||||
checkValue *= 2;
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public List<string>? Labels { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? Label { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public uint Value { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<uint> ValueChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public int Col { get; set; } = 4;
|
||||
|
||||
private async Task CheckOnChange(ChangeEventArgs e, int index)
|
||||
{
|
||||
var checkValue = (uint)Math.Pow(2, index - 1);
|
||||
|
||||
Value += (uint)((e.Value is bool boolValue && boolValue) ? checkValue : -checkValue);
|
||||
|
||||
await ValueChanged.InvokeAsync(Value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.EventDispatcher
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@if (Label != null)
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="name">@Label</label>
|
||||
<input class="form-control" type="text" readonly value="@Shortcut"
|
||||
@onchange="(ChangeEventArgs e) => ShortcutOnChange(e)"
|
||||
@onkeydown="ShortcutOnKeyDown"
|
||||
@onkeyup="(KeyboardEventArgs e) => ShortcutOnKeyUp(e)"
|
||||
@onfocus="ShortcutOnFocus"
|
||||
@onblur="ShortcutOnBlur"/>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public string? Label { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? Shortcut { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<string?> ShortcutChanged { get; set; }
|
||||
|
||||
private async Task ShortcutOnChange(ChangeEventArgs e)
|
||||
{
|
||||
Shortcut = e.Value?.ToString();
|
||||
await ShortcutChanged.InvokeAsync(Shortcut);
|
||||
}
|
||||
|
||||
private void ShortcutOnKeyDown(KeyboardEventArgs e)
|
||||
{
|
||||
if (e.Key == "Tab")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var text = KeyboardShortcutDispatcher.FormatKeyboardShortcut(e.ShiftKey, e.CtrlKey, e.AltKey, e.Key, e.Code);
|
||||
Shortcut = text;
|
||||
}
|
||||
|
||||
private async Task ShortcutOnKeyUp(KeyboardEventArgs e)
|
||||
{
|
||||
//KeyboardShortcutDispatcher.HandleKeyboardShortcutUp()
|
||||
await ShortcutChanged.InvokeAsync(Shortcut);
|
||||
}
|
||||
|
||||
private void ShortcutOnFocus(FocusEventArgs e)
|
||||
{
|
||||
KeyboardShortcutDispatcher.IsActive = false;
|
||||
}
|
||||
|
||||
private void ShortcutOnBlur(FocusEventArgs e)
|
||||
{
|
||||
KeyboardShortcutDispatcher.IsActive = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,402 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.LG
|
||||
@using ColorControl.Shared.Contracts.NVIDIA
|
||||
@using ColorControl.Shared.Contracts.Samsung
|
||||
@using ColorControl.Shared.EventDispatcher
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
|
||||
@typeparam TPreset where TPreset : PresetBase
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
|
||||
@if (Label != null && Steps != null)
|
||||
{
|
||||
var index = 0;
|
||||
|
||||
<div class="mb-2">
|
||||
<div class="d-flex align-items-center">
|
||||
<label class="form-label" for="name">@Label</label>
|
||||
<div class="d-flex ms-auto">
|
||||
@if (NvPresets != null || SamsungPresets != null || CustomItems != null)
|
||||
{
|
||||
<div class="dropdown dropend mb-1">
|
||||
<button type="button" class="btn btn-primary btn-sm dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside">Add</button>
|
||||
<form class="dropdown-menu p-4 dropdown-menu-end" style="width: 200px">
|
||||
@if (NvPresets != null && NvPresetsEnabled)
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="nvPreset">NVIDIA preset</label>
|
||||
<select class="form-select" id="nvPreset" @bind="NvPresetToAddId">
|
||||
<option value="null">Select preset</option>
|
||||
@foreach (var preset in NvPresets)
|
||||
{
|
||||
<option value="@preset.id">@preset.IdOrName</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
@if (SamsungPresets != null && SamsungPresetsEnabled)
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="samsungPreset">Samsung preset</label>
|
||||
<select class="form-select" id="samsungPreset" @bind="SamsungPresetToAddId">
|
||||
<option value="null">Select preset</option>
|
||||
@foreach (var preset in SamsungPresets)
|
||||
{
|
||||
<option value="@preset.id">@preset.IdOrName</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
@if (LgPresets != null && LgPresetsEnabled)
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="lgPreset">LG preset</label>
|
||||
<select class="form-select" id="lgPreset" @bind="LgPresetToAddId">
|
||||
<option value="null">Select preset</option>
|
||||
@foreach (var preset in LgPresets)
|
||||
{
|
||||
<option value="@preset.id">@preset.IdOrName</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
@if (CustomItems != null)
|
||||
{
|
||||
foreach (var customItem in CustomItems)
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="customItem">@customItem.Name</label>
|
||||
<select class="form-select" id="customItem" @bind="customItem.SelectedItem">
|
||||
<option value="null">Select item</option>
|
||||
@foreach (var item in customItem.Items)
|
||||
{
|
||||
<option value="@item">@item</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@if (ActionItems != null)
|
||||
{
|
||||
foreach (var customItem in ActionItems)
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="advancedCustomItem">@customItem.Name</label>
|
||||
<select class="form-select" id="advancedCustomItem" @bind="customItem.SelectedActionName" @bind:after="() => AdvancedItemChanged(customItem)">
|
||||
<option value="null">Select action</option>
|
||||
@foreach (var keyValue in customItem.GroupedActions)
|
||||
{
|
||||
<optgroup label="@keyValue.Key">
|
||||
@foreach (var item in keyValue.Value)
|
||||
{
|
||||
<option value="@item.Name">@item.Title</option>
|
||||
}
|
||||
</optgroup>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<FieldDefs Fields="ActionFields"></FieldDefs>
|
||||
}
|
||||
}
|
||||
<button type="button" class="btn btn-sm btn-primary" @onclick="() => AddPresetOnClick()">Add items</button>
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<nav>
|
||||
<div class="nav nav-tabs" id="nav-tab" role="tablist">
|
||||
<button class="nav-link active" id="nav-home-tab" data-bs-toggle="tab" data-bs-target="#nav-click" type="button" role="tab" aria-controls="nav-click" aria-selected="true">Visual steps</button>
|
||||
<button class="nav-link" id="nav-profile-tab" data-bs-toggle="tab" data-bs-target="#nav-raw" type="button" role="tab" aria-controls="nav-raw" aria-selected="false">Raw text</button>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="tab-content" id="nav-tabContent">
|
||||
<div class="tab-pane fade show active" id="nav-click" role="tabpanel" aria-labelledby="nav-click-tab" tabindex="0">
|
||||
<div class="btn-group btn-group-sm d-inline-flex flex-wrap" role="group" aria-label="Small button group">
|
||||
|
||||
@foreach (var step in PresetSteps)
|
||||
{
|
||||
var stepIndex = index;
|
||||
|
||||
<button type="button" class="btn btn-outline-primary dropdown-toggle text-break text-wrap" data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside">@step.Raw</button>
|
||||
<form class="dropdown-menu p-4 dropdown-menu-end" style="width: 200px">
|
||||
<div class="mb-2">
|
||||
<button type="button" class="btn btn-primary btn-sm" data-bs-dismiss="dropdown" @onclick="() => RemoveOnClick(stepIndex)">Remove</button>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="delay">Delay (@(step.Delay)ms)</label>
|
||||
<input class="form-range" id="delay" type="range" min="0" max="10000" step="50" @bind="step.Delay" @bind:after="() => AfterDelayChanged()">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
index++;
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="nav-raw" role="tabpanel" aria-labelledby="nav-raw-tab" tabindex="0">
|
||||
<textarea class="form-control" id="rawStepText" rows="3" @onchange="RawTextOnChange" @onblur="RawTextOnBlur" value="@RawText"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public string? Label { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public List<string>? Steps { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<List<string>?> StepsChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public List<CustomStepItem>? CustomItems { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public List<ActionStepItem>? ActionItems { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool NvPresetsEnabled { get; set; } = true;
|
||||
|
||||
[Parameter]
|
||||
public bool SamsungPresetsEnabled { get; set; } = true;
|
||||
|
||||
[Parameter]
|
||||
public bool LgPresetsEnabled { get; set; } = true;
|
||||
|
||||
public class ActionStepItem
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public List<InvokableActionDto<TPreset>> Actions { get; private set; }
|
||||
public string? SelectedActionName { get; set; }
|
||||
public Dictionary<string, List<InvokableActionDto<TPreset>>> GroupedActions;
|
||||
|
||||
public ActionStepItem(string name, List<InvokableActionDto<TPreset>> actions)
|
||||
{
|
||||
Name = name;
|
||||
Actions = actions;
|
||||
|
||||
GroupedActions = actions.GroupBy(a => a.Category).ToDictionary(k => k.Key, v => v.ToList());
|
||||
}
|
||||
|
||||
public string GetStepString(List<FieldDefinition>? fields)
|
||||
{
|
||||
if (string.IsNullOrEmpty(SelectedActionName))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
var action = Actions.FirstOrDefault(a => a.Name == SelectedActionName);
|
||||
|
||||
if (action == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
var field = fields?.FirstOrDefault();
|
||||
|
||||
if (field == null || field.Value == null)
|
||||
{
|
||||
return $"{action.Name}";
|
||||
}
|
||||
|
||||
return $"{action.Name}({field.Value?.ToString()})";
|
||||
}
|
||||
}
|
||||
|
||||
private List<NvPreset>? NvPresets { get; set; }
|
||||
private List<SamsungPreset>? SamsungPresets { get; set; }
|
||||
private List<LgPreset>? LgPresets { get; set; }
|
||||
|
||||
private int? NvPresetToAddId { get; set; }
|
||||
private int? SamsungPresetToAddId { get; set; }
|
||||
private int? LgPresetToAddId { get; set; }
|
||||
|
||||
private List<PresetStep> PresetSteps = new();
|
||||
|
||||
private string? RawText;
|
||||
|
||||
private List<FieldDefinition>? ActionFields;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (Steps != null)
|
||||
{
|
||||
PresetSteps.Clear();
|
||||
PresetSteps.AddRange(Steps.Select(s => PresetStep.Parse(s)));
|
||||
RawText = string.Join(", ", PresetSteps);
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var config = await _rpcClientService.CallAsync<Config>("OptionsService", "GetConfig");
|
||||
|
||||
if (config.HasModule(Module.NvModule))
|
||||
{
|
||||
NvPresets = (await _rpcClientService.CallAsync<List<NvPreset>>("NvService", "GetPresets"))?.OrderBy(p => p.name).ToList();
|
||||
}
|
||||
|
||||
if (config.HasModule(Module.SamsungModule))
|
||||
{
|
||||
SamsungPresets = (await _rpcClientService.CallAsync<List<SamsungPreset>>("SamsungService", "GetPresets"))?.OrderBy(p => p.name).ToList();
|
||||
}
|
||||
|
||||
if (config.HasModule(Module.LgModule))
|
||||
{
|
||||
LgPresets = (await _rpcClientService.CallAsync<List<LgPreset>>("LgService", "GetPresets"))?.OrderBy(p => p.name).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RemoveOnClick(int index)
|
||||
{
|
||||
if (PresetSteps.Count > index)
|
||||
{
|
||||
PresetSteps.RemoveAt(index);
|
||||
}
|
||||
|
||||
await EmitSteps();
|
||||
}
|
||||
|
||||
private async Task AfterDelayChanged()
|
||||
{
|
||||
await EmitSteps();
|
||||
}
|
||||
|
||||
private async Task AddPresetOnClick()
|
||||
{
|
||||
AddPreset(NvPresets, "NvPreset", NvPresetToAddId);
|
||||
AddPreset(SamsungPresets, "SamsungPreset", SamsungPresetToAddId);
|
||||
AddPreset(LgPresets, "LgPreset", LgPresetToAddId);
|
||||
|
||||
if (CustomItems != null && Steps != null)
|
||||
{
|
||||
foreach (var customItem in CustomItems)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(customItem.SelectedItem))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
PresetSteps.Add(PresetStep.Parse(customItem.SelectedItem));
|
||||
//customItem.SelectedItem = null;
|
||||
}
|
||||
}
|
||||
if (ActionItems != null && Steps != null)
|
||||
{
|
||||
foreach (var customItem in ActionItems)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(customItem.SelectedActionName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var stepString = customItem.GetStepString(ActionFields);
|
||||
|
||||
PresetSteps.Add(PresetStep.Parse(stepString));
|
||||
//customItem.SelectedItem = null;
|
||||
}
|
||||
}
|
||||
|
||||
await EmitSteps();
|
||||
}
|
||||
|
||||
private void AddPreset<T>(List<T>? presets, string type, int? id) where T : PresetBase
|
||||
{
|
||||
var preset = presets?.FirstOrDefault(p => p.id == id);
|
||||
|
||||
if (preset == null || Steps == null || preset.id == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PresetSteps.Add(PresetStep.Parse($"{type}({preset.name})"));
|
||||
}
|
||||
|
||||
private async Task EmitSteps()
|
||||
{
|
||||
if (Steps != null)
|
||||
{
|
||||
Steps.Clear();
|
||||
Steps.AddRange(PresetSteps.Select(ps => ps.UpdatedRaw));
|
||||
RawText = string.Join(", ", PresetSteps);
|
||||
|
||||
await StepsChanged.InvokeAsync(Steps);
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task RawTextOnBlur(FocusEventArgs e)
|
||||
{
|
||||
var newSteps = new List<string>();
|
||||
|
||||
Utils.ParseWords(newSteps, RawText);
|
||||
|
||||
PresetSteps.Clear();
|
||||
PresetSteps.AddRange(newSteps.Select(s => PresetStep.Parse(s)));
|
||||
|
||||
await EmitSteps();
|
||||
}
|
||||
|
||||
private async Task RawTextOnChange(ChangeEventArgs e)
|
||||
{
|
||||
RawText = e.Value?.ToString();
|
||||
}
|
||||
|
||||
private void AdvancedItemChanged(ActionStepItem customItem)
|
||||
{
|
||||
var action = customItem.Actions.FirstOrDefault(a => a.Name == customItem.SelectedActionName);
|
||||
|
||||
if (action == null)
|
||||
{
|
||||
ActionFields = null;
|
||||
return;
|
||||
}
|
||||
|
||||
ActionFields = [CreateField(action)];
|
||||
}
|
||||
|
||||
private FieldDefinition CreateField(InvokableActionDto<TPreset> action)
|
||||
{
|
||||
var fieldType = FieldType.Numeric;
|
||||
var strValue = default(string);
|
||||
var subLabel = default(string);
|
||||
var label = action.Title;
|
||||
|
||||
if (action.EnumType != null)
|
||||
{
|
||||
fieldType = FieldType.DropDown;
|
||||
strValue = Enum.GetNames(action.EnumType).First();
|
||||
subLabel = "Note: not all values are available on all models.";
|
||||
}
|
||||
else if (action.MinValue == action.MaxValue)
|
||||
{
|
||||
fieldType = FieldType.Label;
|
||||
label = $"This will execute {action.Title}";
|
||||
}
|
||||
|
||||
var field = new FieldDefinition
|
||||
{
|
||||
FieldType = fieldType,
|
||||
Label = label,
|
||||
SubLabel = subLabel,
|
||||
ValueType = action.EnumType,
|
||||
MinValue = action.MinValue,
|
||||
MaxValue = action.MaxValue,
|
||||
};
|
||||
|
||||
if (fieldType == FieldType.Numeric)
|
||||
{
|
||||
field.IntValue = action.CurrentValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
field.StringValue = strValue;
|
||||
}
|
||||
|
||||
return field;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.EventDispatcher
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@if (Triggers != null)
|
||||
{
|
||||
foreach (var trigger in Triggers)
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="triggerType">Trigger type</label>
|
||||
<select class="form-select" id="triggerType" @bind="trigger.Trigger">
|
||||
@foreach (var type in Enum.GetValues<PresetTriggerType>())
|
||||
{
|
||||
<option value="@type.ToString()">@type.GetDescription()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<FlagsInput Label="Conditions" Value="@((uint)trigger.Conditions)" Labels="ConditionLabels" Col="6" ValueChanged="ConditionsChanged"></FlagsInput>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="includedProcesses">Included processes</label>
|
||||
<input class="form-control" type="text" @bind="trigger.IncludedProcessesAsString" />
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="excludedProcesses">Excluded processes</label>
|
||||
<input class="form-control" type="text" @bind="trigger.ExcludedProcessesAsString" />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public List<PresetTrigger>? Triggers { get; set; }
|
||||
|
||||
private List<string> ConditionLabels = Utils.GetDescriptions<PresetConditionType>(fromValue: 1).ToList();
|
||||
|
||||
public void ConditionsChanged(uint value)
|
||||
{
|
||||
if (Triggers == null || !Triggers.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var trigger = Triggers.First();
|
||||
trigger.Conditions = (PresetConditionType)value;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,60 @@
|
||||
@page "/"
|
||||
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Services;
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
@inject AppState AppState
|
||||
@inject NavigationManager _navigationManager;
|
||||
|
||||
<PageTitle>Home</PageTitle>
|
||||
|
||||
<h1>Hello, world!</h1>
|
||||
<h1>Color Control</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
<h2>Modules</h2>
|
||||
|
||||
@if (Modules == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
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="card-header">
|
||||
@module.DisplayName
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<a href="@module.Link" class="btn btn-primary stretched-link mb-2">Open module</a>
|
||||
<ul>
|
||||
@foreach (var info in module.Info)
|
||||
{
|
||||
<li>@info</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
private Config? Config;
|
||||
private List<ModuleDto>? Modules;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
Config = await _rpcClientService.CallAsync<Config>("OptionsService", "GetConfig");
|
||||
Modules = await _rpcClientService.CallAsync<List<ModuleDto>>("OptionsService", "GetModules");
|
||||
if (Modules != null)
|
||||
{
|
||||
Modules.ForEach(m => m.Link = m.DisplayName.Split(' ')[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
@page "/info"
|
||||
|
||||
@using ColorControl.Shared.Common;
|
||||
@using ColorControl.Shared.Services;
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
@inject AppState AppState
|
||||
@inject NavigationManager _navigationManager;
|
||||
|
||||
<PageTitle>Logging</PageTitle>
|
||||
|
||||
<h1>Information</h1>
|
||||
|
||||
@if (info == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<h3 class="mb-2">@info.ApplicationTitle - @info.LegalCopyright</h3>
|
||||
|
||||
<h5>This app contains the following 3rd party plugins:</h5>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">lgtv.net by gr4b4z</li>
|
||||
<li class="list-group-item">Newtonsoft.Json by James Newton-King</li>
|
||||
<li class="list-group-item">NLog by Jarek Kowalski, Kim Christensen, Julian Verdurmen</li>
|
||||
<li class="list-group-item">NvAPIWrapper.Net by Soroush Falahati</li>
|
||||
<li class="list-group-item">NWin32 by zmjack</li>
|
||||
<li class="list-group-item">TaskScheduler by David Hall</li>
|
||||
<li class="list-group-item">NVIDIA Profile Inspector by Orbmu2k</li>
|
||||
<li class="list-group-item">NvidiaML wrapper by LibreHardwareMonitor</li>
|
||||
<li class="list-group-item">Novideo sRGB by ledoge</li>
|
||||
<li class="list-group-item">NLogViewer by dojo90</li>
|
||||
<li class="list-group-item">WPFDarkTheme by AngryCarrot789</li>
|
||||
<li class="list-group-item">Little-CMS by mm2</li>
|
||||
<li class="list-group-item">MHC2 by dantmnf</li>
|
||||
</ul>
|
||||
}
|
||||
|
||||
@code {
|
||||
private InfoDto? info;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
info = await _rpcClientService.CallAsync<InfoDto>("OptionsService", "GetInfo");
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.LG
|
||||
@using ColorControl.Shared.Services
|
||||
@using NvAPIWrapper.Native.Display
|
||||
@using ColorControl.Shared.Common
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
@inject AppState AppState
|
||||
@inject NavigationManager _navigationManager;
|
||||
|
||||
<div class="modal" id="deviceModal" tabindex="-1">
|
||||
<div class="modal-dialog 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>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="row g-1">
|
||||
@if (Device == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="name">Name</label>
|
||||
<input class="form-control" id="name" type="text" @bind="Device.Name" />
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="Device.IsCustom" id="isCustom">
|
||||
<label class="form-check-label" for="isCustom">
|
||||
Is custom device
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="ipAddress">IP-address</label>
|
||||
<input class="form-control" id="ipAddress" type="text" @bind="Device.IpAddress" disabled="@(!Device.IsCustom)" />
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="macAddress">MAC-address</label>
|
||||
<input class="form-control" id="macAddress" type="text" @bind="Device.MacAddress" disabled="@(!Device.IsCustom)" />
|
||||
</div>
|
||||
@if (!Device.IsConnected)
|
||||
{
|
||||
<div class="mb-2">
|
||||
<button class="btn btn-primary me-2" type="button" disabled="@IsTestingConnection" @onclick="TestConnectionClick">
|
||||
@if (IsTestingConnection)
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm me-1" aria-hidden="true"></span>
|
||||
}
|
||||
<span role="status">Test connection</span>
|
||||
</button>
|
||||
@if (IsTestConnectionSuccessful != null)
|
||||
{
|
||||
if (IsTestConnectionSuccessful == true)
|
||||
{
|
||||
<span class="text-success">Connection test was successful!</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-danger">Connection failed!</span>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="accordion accordion-flush" id="accordion">
|
||||
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#deviceOptions">
|
||||
Device options
|
||||
</button>
|
||||
</h2>
|
||||
<div id="deviceOptions" class="accordion-collapse collapse" data-bs-parent="#accordion">
|
||||
<div class="accordion-body">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOnAfterStartup" @bind="Device.Options.PowerOnAfterStartup">
|
||||
<label class="form-check-label" for="checkPowerOnAfterStartup">Power on after startup</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOnAfterResume" @bind="Device.Options.PowerOnAfterResume">
|
||||
<label class="form-check-label" for="checkPowerOnAfterResume">Power on after resume from standby</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOffOnShutdown" @bind="Device.Options.PowerOffOnShutdown">
|
||||
<label class="form-check-label" for="checkPowerOffOnShutdown">Power off on shutdown</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOffOnStandby" @bind="Device.Options.PowerOffOnStandby">
|
||||
<label class="form-check-label" for="checkPowerOffOnStandby">Power off on standby</label>
|
||||
</div>
|
||||
<div class="form-check form-switch mb-2">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOffOnScreensaverStart" @bind="Device.Options.PowerOffOnScreenSaver">
|
||||
<label class="form-check-label" for="checkPowerOffOnScreensaverStart">Power off when screensaver activates</label>
|
||||
<button class="btn btn-sm btn-primary ms-2" type="button" data-bs-toggle="collapse" data-bs-target="#screenSaverSettings" aria-expanded="false" aria-controls="screenSaverSettings">
|
||||
Settings
|
||||
</button>
|
||||
</div>
|
||||
<div class="collapse mb-3" id="screenSaverSettings">
|
||||
<div class="card card-body">
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="minScreenSaverDuration">Minimal screen saver duration (seconds)</label>
|
||||
<input class="form-control" id="minScreenSaverDuration" type="number" @bind="Device.Options.ScreenSaverMinimalDuration" />
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkTurnScreenOffInsteadOfPowerOff" @bind="Device.Options.TurnScreenOffOnScreenSaver">
|
||||
<label class="form-check-label" for="checkTurnScreenOffInsteadOfPowerOff">Turn screen off instead of power off</label>
|
||||
<div class="form-text">NOTE: turning the screen off might not work correctly on all models</div>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkTurnScreenOnInsteadOfPowerOn" @bind="Device.Options.TurnScreenOnAfterScreenSaver">
|
||||
<label class="form-check-label" for="checkTurnScreenOnInsteadOfPowerOn">Turn screen on instead of power on</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkPerformActionOnManualScreenSaver" @bind="Device.Options.HandleManualScreenSaver">
|
||||
<label class="form-check-label" for="checkPerformActionOnManualScreenSaver">Perform action even on manually executed screen saver</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOnOnScreensaverEnd" @bind="Device.Options.PowerOnAfterScreenSaver">
|
||||
<label class="form-check-label" for="checkPowerOnOnScreensaverEnd">Power on when screensaver deactivates</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOnAfterManualPowerOff" @bind="Device.Options.PowerOnAfterManualPowerOff">
|
||||
<label class="form-check-label" for="checkPowerOnAfterManualPowerOff">Power on even after manual power off</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkAllowTriggers" @bind="Device.Options.TriggersEnabled">
|
||||
<label class="form-check-label" for="checkAllowTriggers">Allow triggers to be fired for this device</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkWindowsPowerOn" @bind="Device.Options.PowerOnByWindows">
|
||||
<label class="form-check-label" for="checkWindowsPowerOn">Use Windows power settings to power on</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkWindowsPowerOff" @bind="Device.Options.PowerOffByWindows">
|
||||
<label class="form-check-label" for="checkWindowsPowerOff">Use Windows power settings to power off</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkUseSecureConnection" @bind="Device.Options.UseSecureConnection">
|
||||
<label class="form-check-label" for="checkUseSecureConnection">Use secure connection</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" @onclick="ApplyClick">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private LgDeviceDto? _device;
|
||||
|
||||
[Parameter]
|
||||
public LgDeviceDto? Device { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<LgDeviceDto, Task>? AfterApply { get; set; }
|
||||
|
||||
private bool IsTestingConnection;
|
||||
private bool? IsTestConnectionSuccessful;
|
||||
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (_device != Device)
|
||||
{
|
||||
_device = Device;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ApplyClick(MouseEventArgs e)
|
||||
{
|
||||
if (Device == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await _rpcClientService.CallAsync<bool>("LgService", "UpdateDevice", Device);
|
||||
|
||||
if (AfterApply != null)
|
||||
{
|
||||
await AfterApply.Invoke(Device);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TestConnectionClick(MouseEventArgs e)
|
||||
{
|
||||
IsTestingConnection = true;
|
||||
try
|
||||
{
|
||||
IsTestConnectionSuccessful = await _rpcClientService.CallWithOptionsAsync<bool>("LgService", "TestDevice", new RpcUiClientOptions { Timeout = 120000 }, Device);
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsTestingConnection = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.LG
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
|
||||
<FieldsModal ModalId="executeActionModal" Fields="Fields" Title="@Title" ConfirmCaption="Apply" OnConfirm="ConfirmClick"></FieldsModal>
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter]
|
||||
public InvokableActionDto<LgPreset>? Action { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public List<LgDeviceDto>? Devices { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<Task>? OnConfirm { get; set; }
|
||||
|
||||
public string? Title { get; set; }
|
||||
|
||||
public List<FieldDefinition>? Fields { get; set; }
|
||||
|
||||
private FieldDefinition? Field;
|
||||
private FieldDefinition? DeviceField;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (Action == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Title = $"Execute action {Action.Title}";
|
||||
|
||||
Field = CreateField(Action);
|
||||
|
||||
if (Devices?.Any() == true)
|
||||
{
|
||||
DeviceField = FieldDefinition.CreateDropDownField("Device", Devices.Select(d => d.ToString()).ToList());
|
||||
DeviceField.Value = Devices.FirstOrDefault(d => d.IsSelected)?.ToString();
|
||||
|
||||
Fields = [DeviceField, Field];
|
||||
}
|
||||
else
|
||||
{
|
||||
Fields = [Field];
|
||||
}
|
||||
}
|
||||
|
||||
private FieldDefinition CreateField(InvokableActionDto<LgPreset> action)
|
||||
{
|
||||
var fieldType = FieldType.Numeric;
|
||||
var strValue = default(string);
|
||||
var subLabel = default(string);
|
||||
var label = action.Title;
|
||||
|
||||
if (action.EnumType != null)
|
||||
{
|
||||
fieldType = FieldType.DropDown;
|
||||
strValue = Enum.GetNames(action.EnumType).First();
|
||||
subLabel = "Note: not all values are available on all models.";
|
||||
}
|
||||
else if (action.MinValue == action.MaxValue)
|
||||
{
|
||||
fieldType = FieldType.Label;
|
||||
label = $"This will execute {action.Title}. Press Apply to continue.";
|
||||
}
|
||||
|
||||
var field = new FieldDefinition
|
||||
{
|
||||
FieldType = fieldType,
|
||||
Label = label,
|
||||
SubLabel = subLabel,
|
||||
ValueType = action.EnumType,
|
||||
MinValue = action.MinValue,
|
||||
MaxValue = action.MaxValue,
|
||||
};
|
||||
|
||||
if (fieldType == FieldType.Numeric)
|
||||
{
|
||||
field.IntValue = action.CurrentValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
field.StringValue = strValue;
|
||||
}
|
||||
|
||||
return field;
|
||||
}
|
||||
|
||||
private async Task ConfirmClick()
|
||||
{
|
||||
if (Action == null || Field == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Devices != null && DeviceField != null)
|
||||
{
|
||||
Action.SelectedDevice = Devices.FirstOrDefault(d => d.ToString() == DeviceField.Value?.ToString());
|
||||
}
|
||||
|
||||
var parameters = new List<string> { Field.StringValue };
|
||||
|
||||
await _rpcClientService.CallAsync<bool>("LgService", "ExecuteInvokableAction", Action, parameters);
|
||||
|
||||
if (OnConfirm != null)
|
||||
{
|
||||
await OnConfirm();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.LG
|
||||
@using ColorControl.Shared.Services
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
@using NvAPIWrapper.Native.Display
|
||||
@using ColorControl.Shared.Common
|
||||
@using static ColorControl.UI.Components.Pages.Generic.StepsInput<ColorControl.Shared.Contracts.LG.LgPreset>
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
@inject AppState AppState
|
||||
@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-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(Preset?.id == 0 ? $"Settings for new preset" : $"Settings of preset {Preset?.IdOrName}")</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="() => IsVisible = false"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="row g-1 needs-validation @WasValidated" novalidate>
|
||||
@if (Preset == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="name">Name</label>
|
||||
<input class="form-control" id="name" type="text" @bind="Preset.name" required />
|
||||
</div>
|
||||
@if (Devices != null)
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="device">Device</label>
|
||||
<select class="form-select" id="device" @bind="Preset.DeviceMacAddress">
|
||||
<option value="">Globally selected device</option>
|
||||
@foreach (var device in Devices)
|
||||
{
|
||||
<option value="@device.MacAddress">@device.Name</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox" @bind="Preset.ShowInQuickAccess" id="showInQuickAccess">
|
||||
<label class="form-check-label" for="showInQuickAccess">
|
||||
Show in Quick Access
|
||||
</label>
|
||||
</div>
|
||||
<ShortcutInput Label="Shortcut" @bind-Shortcut="Preset.shortcut" />
|
||||
|
||||
<div class="accordion accordion-flush" id="presetAccordion">
|
||||
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#advancedSettings">
|
||||
Advanced settings
|
||||
</button>
|
||||
</h2>
|
||||
<div id="advancedSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
<TriggerInput Triggers="Preset.Triggers"></TriggerInput>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#steps">
|
||||
Steps
|
||||
</button>
|
||||
</h2>
|
||||
<div id="steps" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
<StepsInput Label="Steps" @bind-Steps="Preset.steps" CustomItems="CustomItems" ActionItems="CustomActions" LgPresetsEnabled="false" TPreset="LgPreset"></StepsInput>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="description">Description</label>
|
||||
<textarea class="form-control" id="description" type="text" @bind="Preset.Description" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="() => IsVisible = false">Close</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="@(!IsValid() ? "" : "modal")" @onclick="ApplyClick">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private LgPreset? Preset;
|
||||
|
||||
[Parameter]
|
||||
public LgPreset? PresetParam { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public List<LgDeviceDto>? Devices { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<LgPreset, Task>? AfterApply { get; set; }
|
||||
|
||||
private List<CustomStepItem>? CustomItems;
|
||||
private List<ActionStepItem>? CustomActions;
|
||||
|
||||
private string WasValidated = "";
|
||||
private bool IsVisible;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (PresetParam == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Preset = PresetParam.CloneWithId();
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
CustomItems = [new CustomStepItem("RC buttons", Enum.GetValues<ButtonType>().Select(b =>
|
||||
{
|
||||
var result = b.ToString();
|
||||
|
||||
if (result[0] == '_')
|
||||
{
|
||||
result = result.Substring(1);
|
||||
}
|
||||
|
||||
return result;
|
||||
}).ToList())];
|
||||
|
||||
|
||||
var actions = await _rpcClientService.CallAsync<List<InvokableActionDto<LgPreset>>>("LgService", "GetInvokableActions");
|
||||
|
||||
if (actions != null)
|
||||
{
|
||||
CustomActions = [new ActionStepItem("Actions", actions.Where(a => !a.Advanced).ToList())];
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ApplyClick(MouseEventArgs e)
|
||||
{
|
||||
if (Preset == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsValid())
|
||||
{
|
||||
WasValidated = "was-validated";
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await _rpcClientService.CallAsync<bool>("LgService", "UpdatePreset", Preset);
|
||||
|
||||
if (AfterApply != null)
|
||||
{
|
||||
await AfterApply.Invoke(Preset);
|
||||
}
|
||||
}
|
||||
|
||||
private void ModalOnFocus(FocusEventArgs e)
|
||||
{
|
||||
if (!IsVisible)
|
||||
{
|
||||
WasValidated = "";
|
||||
IsVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValid()
|
||||
{
|
||||
return !string.IsNullOrEmpty(Preset?.name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.LG
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
|
||||
@rendermode InteractiveServer
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
|
||||
<FieldsModal Title="Lg settings" ConfirmCaption="Save" ModalId="settingsModal" Fields="Fields" OnConfirm="ApplyClick" OnShow="ModalOnShow"></FieldsModal>
|
||||
|
||||
@code {
|
||||
private LgServiceConfigDto? Config { get; set; }
|
||||
private List<FieldDefinition>? Fields { get; set; }
|
||||
|
||||
protected async Task ModalOnShow()
|
||||
{
|
||||
Config = await _rpcClientService.CallAsync<LgServiceConfigDto>("LgService", "GetConfig");
|
||||
Fields = LgFields.GetSettingsFields(Config);
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task ApplyClick()
|
||||
{
|
||||
if (Config == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FieldDefinition.UpdateObject(Fields, Config);
|
||||
|
||||
var result = await _rpcClientService.CallAsync<bool>("LgService", "UpdateConfig", Config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
@page "/LG"
|
||||
|
||||
@using ColorControl.Shared.Common
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.NVIDIA
|
||||
@using ColorControl.Shared.Contracts.LG
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
@using ColorControl.UI.Generics
|
||||
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
@inject ProtectedLocalStorage _localStorage
|
||||
|
||||
<PageTitle>LG controller</PageTitle>
|
||||
|
||||
<LgSettings />
|
||||
<LgExecuteActionModal Action="@CurrentInvokableAction" Devices="Devices" />
|
||||
|
||||
<div class="d-flex align-items-center">
|
||||
<h1>LG controller</h1>
|
||||
<div class="d-flex ms-auto">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-sm btn-primary dropdown-toggle me-2" data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside">
|
||||
Buttons
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
@foreach (var button in Enum.GetValues<ButtonType>())
|
||||
{
|
||||
<li>
|
||||
<a class="dropdown-item" @onclick="() => ButtonOnClick(button)">@button.GetDescription()</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@if (ActionGroups != null)
|
||||
{
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-sm btn-primary dropdown-toggle me-2" data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside">
|
||||
Expert
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
@foreach (var group in ActionGroups)
|
||||
{
|
||||
<li class="dropstart">
|
||||
<a class="dropdown-item dropdown-toggle" data-bs-toggle="dropdown" href="#">@group.Name</a>
|
||||
<ul class="dropdown-menu dropdown-submenu">
|
||||
@foreach (var action in group.Actions)
|
||||
{
|
||||
<li><a class="dropdown-item" @onclick="() => ActionOnclick(action)" data-bs-toggle="modal" data-bs-target="#executeActionModal">@action.Title</a></li>
|
||||
}
|
||||
</ul>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#settingsModal">
|
||||
Settings
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<LgDevicePage Device="CurrentDevice" />
|
||||
<LgPresetPage PresetParam="CurrentPreset" Devices="Devices" AfterApply="AfterPresetApplied" />
|
||||
<ConfirmModal Title="Delete preset" Message="Are you sure you want to delete the preset?" OnConfirm="PresetOnDeleteConfirmed" />
|
||||
|
||||
@if (Devices == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mb-2 d-flex align-items-center">
|
||||
<h2>Devices</h2>
|
||||
<div class="d-flex ms-auto">
|
||||
<button type="button" class="btn btn-sm btn-primary me-2" disabled="@IsRefreshDisabled" @onclick="RefreshDevicesOnClick">
|
||||
@if (IsRefreshDisabled)
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm me-1" aria-hidden="true"></span>
|
||||
}
|
||||
<span role="status">Refresh</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#deviceModal" @onclick="() => AddDeviceOnClick()">
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
@if (!Devices.Any())
|
||||
{
|
||||
<span>No devices found</span>
|
||||
}
|
||||
@foreach (var device in Devices)
|
||||
{
|
||||
<div class="col-lg-6 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-header d-flex align-items-baseline">
|
||||
<span>
|
||||
@device.Name
|
||||
@if (device.IsConnected)
|
||||
{
|
||||
<span class="ms-2 badge text-bg-success" title="Device is connected">Connected</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="ms-2 badge text-bg-danger" title="Device is disconnected">Disconnected</span>
|
||||
}
|
||||
@if (device.IsSelected)
|
||||
{
|
||||
<span class="ms-2 badge text-bg-primary" title="Global device">Primary</span>
|
||||
}
|
||||
</span>
|
||||
<div class="d-flex ms-auto">
|
||||
@if (!device.IsSelected)
|
||||
{
|
||||
<button class="btn btn-sm btn-secondary ms-2" @onclick="() => SetAsPrimaryOnClick(device)">Set as primary</button>
|
||||
}
|
||||
<button type="button" class="btn btn-sm btn-primary ms-2" data-bs-toggle="modal" data-bs-target="#deviceModal" @onclick="() => DeviceOnClick(device)">
|
||||
Settings
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<small class="text-muted">@device.IpAddress (@(device.MacAddress ?? "No MAC-address entered"))</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Presets == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mb-2 d-flex align-items-center">
|
||||
<h2>Presets</h2>
|
||||
<div class="d-flex ms-auto">
|
||||
<select class="form-select me-2" id="presetOrder" @bind="PresetOrder" @bind:after="PresetOrderChanged">
|
||||
@foreach (var order in Enum.GetValues<PresetOrder>())
|
||||
{
|
||||
<option value="@order">Order by: @order.GetDescription()</option>
|
||||
}
|
||||
</select>
|
||||
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => AddPresetOnClick()">
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<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>
|
||||
<div class="d-flex align-items-start justify-content-between">
|
||||
<div>
|
||||
<h5 class="mb-1">@preset.name <small class="text-muted">@GetDeviceName(preset)</small></h5>
|
||||
@if (preset.Triggers.Any(t => t.Trigger != PresetTriggerType.None))
|
||||
{
|
||||
<div><small class="text-muted">Trigger: @preset.Triggers[0].ToString()</small></div>
|
||||
}
|
||||
<div><small class="text-muted">@preset.shortcut</small></div>
|
||||
</div>
|
||||
<span>
|
||||
<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>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
private class ActionGroup
|
||||
{
|
||||
public string Name { get; }
|
||||
public List<InvokableActionDto<LgPreset>> Actions { get; }
|
||||
|
||||
public ActionGroup(string name, List<InvokableActionDto<LgPreset>> actions)
|
||||
{
|
||||
Name = (name ?? "Misc");
|
||||
Actions = actions;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private List<LgPreset>? Presets;
|
||||
private List<LgDeviceDto>? Devices;
|
||||
private List<InvokableActionDto<LgPreset>>? InvokableActions;
|
||||
private List<ActionGroup>? ActionGroups;
|
||||
|
||||
private LgServiceConfigDto? Config;
|
||||
private LgPreset? CurrentPreset;
|
||||
private LgDeviceDto? CurrentDevice;
|
||||
private bool IsRefreshDisabled;
|
||||
private InvokableActionDto<LgPreset>? CurrentInvokableAction;
|
||||
|
||||
private PresetOrder PresetOrder;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
Config = await _rpcClientService.CallAsync<LgServiceConfigDto>("LgService", "GetConfig");
|
||||
await RefreshPresets();
|
||||
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();
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstTime)
|
||||
{
|
||||
if (firstTime)
|
||||
{
|
||||
PresetOrder = await _localStorage.TryGet<PresetOrder>("LgPresetOrder");
|
||||
Presets = Presets?.OrderPresetsBy(PresetOrder);
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PresetOnClick(LgPreset preset)
|
||||
{
|
||||
await _rpcClientService.CallAsync<bool>("LgService", "ApplyPresetWithId", preset.id);
|
||||
}
|
||||
|
||||
private async Task PresetOnEditClick(LgPreset preset)
|
||||
{
|
||||
CurrentPreset = preset;
|
||||
}
|
||||
|
||||
private async Task PresetOnCloneClick(LgPreset preset)
|
||||
{
|
||||
CurrentPreset = new LgPreset(preset);
|
||||
CurrentPreset.SetCloneValues();
|
||||
}
|
||||
|
||||
private async Task PresetOnDeleteClick(LgPreset preset)
|
||||
{
|
||||
CurrentPreset = preset;
|
||||
}
|
||||
|
||||
private async Task PresetOnDeleteConfirmed()
|
||||
{
|
||||
if (CurrentPreset == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _rpcClientService.CallAsync<bool>("LgService", "DeletePreset", CurrentPreset.id);
|
||||
await RefreshPresets();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task DeviceOnClick(LgDeviceDto device)
|
||||
{
|
||||
CurrentDevice = device;
|
||||
}
|
||||
|
||||
private async Task AddDeviceOnClick()
|
||||
{
|
||||
CurrentDevice = new LgDeviceDto { IsCustom = true };
|
||||
}
|
||||
|
||||
private async Task AddPresetOnClick()
|
||||
{
|
||||
CurrentPreset = new LgPreset();
|
||||
}
|
||||
|
||||
private async Task AfterPresetApplied(LgPreset preset)
|
||||
{
|
||||
await RefreshPresets();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task RefreshDevicesOnClick(MouseEventArgs e)
|
||||
{
|
||||
IsRefreshDisabled = true;
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
await _rpcClientService.CallAsync<bool>("LgService", "RefreshDevices");
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
Devices = await _rpcClientService.CallAsync<List<LgDeviceDto>>("LgService", "GetDevices");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsRefreshDisabled = false;
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task ActionOnclick(InvokableActionDto<LgPreset> action)
|
||||
{
|
||||
CurrentInvokableAction = action;
|
||||
}
|
||||
|
||||
private string GetDeviceName(LgPreset preset)
|
||||
{
|
||||
if (string.IsNullOrEmpty(preset.DeviceMacAddress))
|
||||
{
|
||||
return "Global";
|
||||
}
|
||||
|
||||
return Devices?.FirstOrDefault(d => d.MacAddress == preset.DeviceMacAddress)?.Name ?? preset.DeviceMacAddress;
|
||||
}
|
||||
|
||||
private async Task SetAsPrimaryOnClick(LgDeviceDto device)
|
||||
{
|
||||
var result = await _rpcClientService.CallAsync<bool>("LgService", "SetSelectedDevice", device);
|
||||
|
||||
if (result)
|
||||
{
|
||||
Devices = await _rpcClientService.CallAsync<List<LgDeviceDto>>("LgService", "GetDevices");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PresetOrderChanged()
|
||||
{
|
||||
await _localStorage.SetAsync("LgPresetOrder", PresetOrder);
|
||||
await RefreshPresets();
|
||||
}
|
||||
|
||||
private async Task RefreshPresets()
|
||||
{
|
||||
Presets = await _rpcClientService.CallAsync<List<LgPreset>>("LgService", "GetPresets");
|
||||
|
||||
Presets = Presets?.OrderPresetsBy(PresetOrder);
|
||||
}
|
||||
|
||||
private async Task ButtonOnClick(ButtonType button)
|
||||
{
|
||||
var preset = new LgPreset(button.ToString(), null, [button.ToString()]);
|
||||
|
||||
await _rpcClientService.CallAsync<bool>("LgService", "ApplyPreset", preset);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
@page "/logging"
|
||||
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Services;
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
@inject AppState AppState
|
||||
@inject NavigationManager _navigationManager;
|
||||
|
||||
<PageTitle>Logging</PageTitle>
|
||||
|
||||
<h1>Logging</h1>
|
||||
|
||||
@if (config == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row mb-3">
|
||||
<div class="col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Settings</h5>
|
||||
</div>
|
||||
<form class="ms-3 me-3">
|
||||
<div class="mb-3 col-md-3">
|
||||
<label class="form-label" for="logLevel">
|
||||
Log Level
|
||||
</label>
|
||||
<select class="form-select" @bind="logLevel" id="logLevel" @oninput="LogLevelInput">
|
||||
@foreach (var level in logLevels)
|
||||
{
|
||||
<option value="@level.Name">@level.Name</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-3">
|
||||
<label class="form-label" for="logSource">
|
||||
Show log from
|
||||
</label>
|
||||
<select class="form-select" @bind="logSource" id="logSource" @bind:after="GetLog">
|
||||
<option value="0">Main application</option>
|
||||
<option value="1">Service</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<label class="form-label" for="logOrder">
|
||||
Order
|
||||
</label>
|
||||
<select class="form-select" @bind="logOrder" id="logOrder" @bind:after="GetLog">
|
||||
<option value="0">Newest first</option>
|
||||
<option value="1">Oldest first</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<label class="form-label" for="maxLines">
|
||||
Maximum number of lines to show
|
||||
</label>
|
||||
<select class="form-select" @bind="maxLines" id="maxLines" @bind:after="GetLog">
|
||||
<option value="1000">1000</option>
|
||||
<option value="10000">10000</option>
|
||||
<option value="999999999">Show all</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">TimeStamp</th>
|
||||
<th scope="col">
|
||||
<select class="form-select fw-bold" id="logLevel" @oninput="LogLevelFilterInput">
|
||||
@foreach (var level in logLevels)
|
||||
{
|
||||
<option class="fw-bold" value="@level.Name">@level.Name</option>
|
||||
}
|
||||
</select>
|
||||
</th>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Message</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var logLine in logLines.Where(l => l.Show(logLevelFilter)))
|
||||
{
|
||||
<tr class="table-@logLine.BsClass">
|
||||
<td style="min-width: 220px">@logLine.TimeStamp</td>
|
||||
<td style="min-width: 120px">@logLine.Level</td>
|
||||
<td>@logLine.Name</td>
|
||||
<td>@logLine.Message</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@code {
|
||||
private Config? config;
|
||||
private string logLevel = "0";
|
||||
private int logSource = 0;
|
||||
private int maxLines = 1000;
|
||||
private int logOrder = 0;
|
||||
private List<string>? allLines;
|
||||
private List<LogLine> logLines = new();
|
||||
private string logLevelFilterName = NLog.LogLevel.Debug.Name;
|
||||
private NLog.LogLevel logLevelFilter = NLog.LogLevel.Debug;
|
||||
private List<NLog.LogLevel> logLevels = [NLog.LogLevel.Trace, NLog.LogLevel.Debug, NLog.LogLevel.Info, NLog.LogLevel.Warn, NLog.LogLevel.Error, NLog.LogLevel.Fatal];
|
||||
|
||||
private class LogLine
|
||||
{
|
||||
public string TimeStamp { get; }
|
||||
public string Level { get; }
|
||||
public string Name { get; }
|
||||
public string Message { get; }
|
||||
public NLog.LogLevel LogLevel { get; }
|
||||
|
||||
public LogLine(string timeStamp, string level, string name, string message)
|
||||
{
|
||||
TimeStamp = timeStamp;
|
||||
Level = level;
|
||||
Name = name;
|
||||
Message = message;
|
||||
LogLevel = NLog.LogLevel.FromString(level);
|
||||
}
|
||||
|
||||
public string BsClass => Level switch
|
||||
{
|
||||
"TRACE" => "dark",
|
||||
"DEBUG" => "secondary",
|
||||
"INFO" => "info",
|
||||
"WARN" => "warning",
|
||||
"ERROR" => "danger",
|
||||
"FATAL" => "secondary",
|
||||
};
|
||||
|
||||
public bool Show(NLog.LogLevel level) => LogLevel >= level;
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
config = await _rpcClientService.CallAsync<Config>("OptionsService", "GetConfig");
|
||||
logLevel = config.LogLevel;
|
||||
|
||||
await GetLog();
|
||||
}
|
||||
|
||||
private async Task GetLog()
|
||||
{
|
||||
allLines = await _rpcClientService.CallAsync<List<string>>("LoggingService", "LoadLog", logSource, maxLines, logOrder);
|
||||
|
||||
ProcessLines();
|
||||
}
|
||||
|
||||
private void ProcessLines()
|
||||
{
|
||||
logLines.Clear();
|
||||
if (allLines == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var lastLogLine = default(LogLine);
|
||||
|
||||
foreach (var line in allLines)
|
||||
{
|
||||
var strings = line.Split('|');
|
||||
|
||||
if (strings.Count() == 4)
|
||||
{
|
||||
lastLogLine = new LogLine(strings[0], strings[1], strings[2], strings[3]);
|
||||
|
||||
logLines.Add(lastLogLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task LogLevelInput(ChangeEventArgs e)
|
||||
{
|
||||
var value = e.Value ?? NLog.LogLevel.Debug.Name;
|
||||
await _rpcClientService.CallAsync<bool>("LoggingService", "SetLogLevel", value);
|
||||
}
|
||||
|
||||
private async Task LogLevelFilterInput(ChangeEventArgs e)
|
||||
{
|
||||
var value = e.Value as string ?? NLog.LogLevel.Debug.Name;
|
||||
logLevelFilter = NLog.LogLevel.FromString(value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.NVIDIA
|
||||
@using ColorControl.Shared.Services;
|
||||
@using NvAPIWrapper.Native.Display
|
||||
@using ColorControl.Shared.Common;
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
@inject AppState AppState
|
||||
@inject NavigationManager _navigationManager;
|
||||
|
||||
<div class="modal modal-lg" id="ditheringModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Test dithering</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="row g-1">
|
||||
<div class="row">
|
||||
@if (displayPresets != null)
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="mode">Display</label>
|
||||
<select class="form-select" id="mode" @bind="SelectedDisplayId" disabled="@(displayPresets.Count <= 1)">
|
||||
@foreach (var display in displayPresets)
|
||||
{
|
||||
<option value="@display.DisplayId">@display.displayName</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (Preset == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="state">State</label>
|
||||
<select class="form-select" id="state" @bind="Preset.NvDitherState" @bind:after="ApplyNormalPreset">
|
||||
@foreach (var state in Enum.GetValues<NvDitherState>())
|
||||
{
|
||||
<option value="@state.ToString()">@state.GetDescription()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="mode">Mode</label>
|
||||
<select class="form-select" id="mode" @bind="Preset.NvDitherMode" @bind:after="ApplyNormalPreset">
|
||||
@foreach (var mode in Enum.GetValues<NvDitherMode>())
|
||||
{
|
||||
<option value="@mode.ToString()">@mode.GetDescription()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="bits">Bit depth</label>
|
||||
<select class="form-select" id="bits" @bind="Preset.NvDitherBits" @bind:after="ApplyNormalPreset">
|
||||
@foreach (var bits in Enum.GetValues<NvDitherBits>())
|
||||
{
|
||||
<option value="@bits.ToString()">@bits.GetDescription()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<div class="btn-group ms-2 mb-2">
|
||||
<button type="button" class="btn btn-sm btn-primary dropdown-toggle me-2" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Gradient
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" @onclick="() => GradientColor = GrayGradient">Grayscale</a></li>
|
||||
<li><a class="dropdown-item" @onclick="() => GradientColor = RedGradient">Red</a></li>
|
||||
<li><a class="dropdown-item" @onclick="() => GradientColor = GreenGradient">Green</a></li>
|
||||
<li><a class="dropdown-item" @onclick="() => GradientColor = BlueGradient">Blue</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mb-2 col-24 mx-2" style="height: 200px; background: linear-gradient(to right, #000000 0%, @GradientColor 100%)">
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<span class="me-auto">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="SetDitherRegistryKeyOnClick">Set dither registry key</button>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="RestartDriverOnClick">Restart driver</button>
|
||||
</span>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
public const string GrayGradient = "#ffffff";
|
||||
public const string RedGradient = "#ff0000";
|
||||
public const string GreenGradient = "#00ff00";
|
||||
public const string BlueGradient = "#0000ff";
|
||||
|
||||
private bool _visible;
|
||||
|
||||
[Parameter]
|
||||
public bool Visible { get; set; }
|
||||
|
||||
public NvPreset? Preset { get; set; }
|
||||
|
||||
private string? SelectedDisplayId { get; set; }
|
||||
private List<NvPreset>? displayPresets;
|
||||
private string GradientColor { get; set; } = GrayGradient;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (_visible != Visible)
|
||||
{
|
||||
_visible = Visible;
|
||||
|
||||
if (Visible)
|
||||
{
|
||||
await GetSettingsAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task GetSettingsAsync()
|
||||
{
|
||||
displayPresets = await _rpcClientService.CallAsync<List<NvPreset>>("NvService", "GetDisplayPresets");
|
||||
if (displayPresets?.Any() == true)
|
||||
{
|
||||
Preset = displayPresets.FirstOrDefault(d => d.DisplayConfig.IsPrimary == true) ?? displayPresets.First();
|
||||
SelectedDisplayId = Preset.DisplayId;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DitherOnChange(EventArgs e)
|
||||
{
|
||||
if (Preset == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Preset.applyDithering = true;
|
||||
|
||||
await _rpcClientService.CallAsync<bool>("NvService", "ApplyPresetWithId", Preset.id);
|
||||
}
|
||||
|
||||
private async Task ApplyNormalPreset()
|
||||
{
|
||||
await ApplyPreset();
|
||||
}
|
||||
|
||||
private async Task SetDitherRegistryKeyOnClick(MouseEventArgs e)
|
||||
{
|
||||
await ApplyPreset(true);
|
||||
}
|
||||
|
||||
private async Task RestartDriverOnClick(MouseEventArgs e)
|
||||
{
|
||||
await ApplyPreset(true, true);
|
||||
}
|
||||
|
||||
private async Task ApplyPreset(bool setRegistryKey = false, bool restartDriver = false)
|
||||
{
|
||||
if (Preset == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Preset.applyDithering = true;
|
||||
Preset.SetDitherRegistryKey = setRegistryKey;
|
||||
Preset.RestartDriver = restartDriver;
|
||||
|
||||
await _rpcClientService.CallAsync<bool>("NvService", "ApplyPreset", Preset);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.NVIDIA
|
||||
@using ColorControl.Shared.Services;
|
||||
@using NvAPIWrapper.Native.Display
|
||||
@using ColorControl.Shared.Common;
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
@inject AppState AppState
|
||||
@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-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>
|
||||
@if (Preset == null)
|
||||
{
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="() => IsVisible = false"></button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="btn-close" data-bs-toggle="modal" data-bs-target="#presetModal" aria-label="Close" @onclick="() => IsVisible = false"></button>
|
||||
}
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="row g-1">
|
||||
|
||||
@if (ProfileNames != null)
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="profileName">Profile</label>
|
||||
<select class="form-select" id="profileName" @bind="CurrentProfileName" @bind:after="ProfileOnChange">
|
||||
@foreach (var profileName in ProfileNames)
|
||||
{
|
||||
<option value="@profileName">@profileName</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="accordion accordion-flush" id="driverSettingsAccordion">
|
||||
@if (Groups != null)
|
||||
{
|
||||
foreach (var group in Groups)
|
||||
{
|
||||
var groupId = $"{group.Name.Replace(' ', '_')}Settings";
|
||||
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed py-2 @(group.HasChangedSettings ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#@groupId">
|
||||
@group.Name
|
||||
</button>
|
||||
</h2>
|
||||
<div id="@groupId" class="accordion-collapse collapse" data-bs-parent="#driverSettingsAccordion">
|
||||
<div class="accordion-body">
|
||||
@foreach (var setting in group.DriverSettings)
|
||||
{
|
||||
if (NvSettingConstants.RangeDriverSettings.Contains(setting.SettingId) && setting.Values != null)
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label @(setting.Value != setting.DefaultValue && setting.Value != NvSettingConstants.UnsetDwordValue ? "text-warning fw-bold" : "")" for="@setting.SettingId">@setting.SettingText (@setting.Value)</label>
|
||||
<input class="form-range" type="range" min="@setting.Values.Min(v => v.Value)" max="@setting.Values.Max(v => v.Value)" @bind="setting.Value" id="@setting.SettingId">
|
||||
</div>
|
||||
}
|
||||
else if (setting.Values != null)
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label @(setting.Value != setting.DefaultValue && setting.Value != NvSettingConstants.UnsetDwordValue ? "text-warning fw-bold" : "")" for="@setting.SettingId">@setting.SettingText</label>
|
||||
<select class="form-select" id="@setting.SettingId" @bind="setting.Value">
|
||||
@if (Preset != null)
|
||||
{
|
||||
<option value="@(NvSettingConstants.UnsetDwordValue)">Unchanged</option>
|
||||
}
|
||||
@foreach (var settingValue in setting.Values)
|
||||
{
|
||||
<option value="@settingValue.Value" class="@(setting.DefaultValue == settingValue.Value ? "text-success" : "")">@settingValue.ValueName</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@if (Preset == null)
|
||||
{
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="() => IsVisible = false">Close</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" @onclick="ApplyClick">Apply</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="btn btn-secondary" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => IsVisible = false">Close</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="ApplyClick">Save</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
||||
public class GroupedSettings
|
||||
{
|
||||
public string Name { get; private set; }
|
||||
public List<NvSettingItemDto> DriverSettings { get; private set; }
|
||||
public bool HasChangedSettings { get; private set; }
|
||||
|
||||
public GroupedSettings(string name, List<NvSettingItemDto> settings, bool hasChangedSettings)
|
||||
{
|
||||
Name = name;
|
||||
DriverSettings = settings;
|
||||
HasChangedSettings = hasChangedSettings;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private NvPreset? _preset;
|
||||
|
||||
[Parameter]
|
||||
public NvPreset? Preset { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<NvPreset, Task>? AfterApply { get; set; }
|
||||
|
||||
private List<GroupedSettings>? Groups { get; set; }
|
||||
private List<NvSettingItemDto>? DriverSettings { get; set; }
|
||||
private List<string>? ProfileNames { get; set; }
|
||||
private string? CurrentProfileName { get; set; }
|
||||
|
||||
private bool IsVisible { get; set; }
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (_preset != Preset)
|
||||
{
|
||||
_preset = Preset;
|
||||
CurrentProfileName = Preset?.DriverProfileName;
|
||||
DriverSettings = null;
|
||||
await LoadDriverSettings();
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task LoadDriverSettings()
|
||||
{
|
||||
if (DriverSettings == null)
|
||||
{
|
||||
DriverSettings = await _rpcClientService.CallAsync<List<NvSettingItemDto>>("NvService", "GetDriverSettings", CurrentProfileName);
|
||||
if (DriverSettings != null)
|
||||
{
|
||||
DriverSettings.ForEach(s => s.ValueChanged = false);
|
||||
}
|
||||
}
|
||||
|
||||
if (DriverSettings != null)
|
||||
{
|
||||
if (Preset != null)
|
||||
{
|
||||
foreach (var setting in DriverSettings)
|
||||
{
|
||||
setting.Value = setting.DefaultValue;
|
||||
setting.ValueChanged = false;
|
||||
}
|
||||
|
||||
foreach (var presetSetting in Preset.driverSettings)
|
||||
{
|
||||
var setting = DriverSettings.FirstOrDefault(s => s.SettingId == presetSetting.Key);
|
||||
|
||||
if (setting == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
setting.Value = presetSetting.Value;
|
||||
//setting.ValueChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
Groups = DriverSettings.OrderBy(s => s.GroupName).GroupBy(s => s.GroupName).Select(g =>
|
||||
{
|
||||
var settings = g.ToList();
|
||||
var changedSettings = settings.Any(s => s.Value != s.DefaultValue && s.Value != NvSettingConstants.UnsetDwordValue);
|
||||
|
||||
var group = new GroupedSettings(g.Key, g.ToList(), changedSettings);
|
||||
|
||||
return group;
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task ApplyClick(MouseEventArgs e)
|
||||
{
|
||||
if (DriverSettings == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var changedSettings = DriverSettings.Where(s => s.ValueChanged);
|
||||
|
||||
if (Preset != null)
|
||||
{
|
||||
Preset.UpdateDriverSettings(changedSettings);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var changedSettingsDictionary = changedSettings.ToDictionary(k => k.SettingId, v => v.Value);
|
||||
|
||||
var result = await _rpcClientService.CallAsync<bool>("NvService", "SetDriverSettings", changedSettingsDictionary, CurrentProfileName);
|
||||
}
|
||||
|
||||
private async Task ProfileOnChange()
|
||||
{
|
||||
if (CurrentProfileName?.Contains("Base") == true)
|
||||
{
|
||||
CurrentProfileName = null;
|
||||
}
|
||||
|
||||
if (Preset != null)
|
||||
{
|
||||
Preset.DriverProfileName = CurrentProfileName;
|
||||
}
|
||||
|
||||
DriverSettings = null;
|
||||
|
||||
await LoadDriverSettings();
|
||||
}
|
||||
|
||||
private async Task OnFocus(FocusEventArgs e)
|
||||
{
|
||||
if (!IsVisible)
|
||||
{
|
||||
IsVisible = true;
|
||||
ProfileNames ??= await _rpcClientService.CallAsync<List<string>>("NvService", "GetDriverProfileNames");
|
||||
await LoadDriverSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,618 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.NVIDIA
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
@using NvAPIWrapper.Native.Display
|
||||
@using ColorControl.Shared.Common;
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
@inject AppState AppState
|
||||
@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-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>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="() => IsVisible = false"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="row g-1 needs-validation @WasValidated" novalidate>
|
||||
@if (Preset == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (!Preset.IsDisplayPreset)
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="name">Name</label>
|
||||
<input class="form-control" type="text" @bind="Preset.name" required />
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="Preset.ShowInQuickAccess" id="showInQuickAccess">
|
||||
<label class="form-check-label" for="showInQuickAccess">
|
||||
Show in Quick Access
|
||||
</label>
|
||||
</div>
|
||||
<ShortcutInput Label="Shortcut" @bind-Shortcut="Preset.shortcut" />
|
||||
}
|
||||
<div class="accordion accordion-flush" id="presetAccordion">
|
||||
|
||||
@if (!Preset.IsDisplayPreset)
|
||||
{
|
||||
<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; visibility: hidden" type="checkbox">
|
||||
}
|
||||
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#displaySettings">
|
||||
Display
|
||||
</button>
|
||||
</span>
|
||||
</h2>
|
||||
<div id="displaySettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="mb-2">
|
||||
<input class="form-check-input" type="checkbox" @bind="Preset.primaryDisplay" id="primaryDisplay">
|
||||
<label class="form-check-label" for="primaryDisplay">
|
||||
Primary display
|
||||
</label>
|
||||
</div>
|
||||
@if (displayPresets != null)
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="mode">Display</label>
|
||||
<select class="form-select" id="mode" @bind="Preset.DisplayId" disabled="@(Preset.primaryDisplay)">
|
||||
@foreach (var display in displayPresets)
|
||||
{
|
||||
<option value="@display.DisplayId">@display.displayName</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<input class="form-check-input" type="checkbox" @bind="Preset.DisplayConfig.IsPrimary" id="setAsPrimaryDisplay" disabled="@(Preset.primaryDisplay)">
|
||||
<label class="form-check-label" for="setAsPrimaryDisplay">
|
||||
Set display as primary display
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (resolutions != null)
|
||||
{
|
||||
<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.DisplayConfig.ApplyResolution" id="applyResolution">
|
||||
}
|
||||
<button class="accordion-button py-2 @(!Preset.IsDisplayPreset ? "collapsed" : "") @(Preset.DisplayConfig.ApplyResolution ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#resolutionSettings">
|
||||
Resolution
|
||||
</button>
|
||||
</span>
|
||||
</h2>
|
||||
<div id="resolutionSettings" class="accordion-collapse collapse @(Preset.IsDisplayPreset ? "show" : "")" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
@if (resolutions != null)
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="activeResolution">Active resolution</label>
|
||||
<select class="form-select" id="activeResolution" @bind="selectedActiveResolution">
|
||||
<option value="0x0">Not set</option>
|
||||
@foreach (var resolution in resolutions)
|
||||
{
|
||||
<option value="@resolution.GetActiveString()">@resolution.GetActiveString()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="virtualResolution">Virtual resolution</label>
|
||||
<select class="form-select" id="virtualResolution" @bind="selectedVirtualResolution">
|
||||
<option value="0x0">Not set</option>
|
||||
@foreach (var resolution in resolutions)
|
||||
{
|
||||
<option value="@resolution.GetVirtualString()">@resolution.GetVirtualString()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="scaling">Scaling/Aspect ratio</label>
|
||||
<select class="form-select" id="scaling" @bind="Preset.DisplayConfig.Scaling">
|
||||
@foreach (var scaling in Enum.GetValues<Shared.Native.CCD.DisplayConfigScaling>().Where(s => s != Shared.Native.CCD.DisplayConfigScaling.ForceUint32))
|
||||
{
|
||||
<option value="@scaling.ToString()">@scaling.ToString()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="rotation">Rotation</label>
|
||||
<select class="form-select" id="rotation" @bind="Preset.DisplayConfig.Rotation">
|
||||
@foreach (var rotation in Enum.GetValues<Shared.Native.CCD.DisplayConfigRotation>().Where(s => s != Shared.Native.CCD.DisplayConfigRotation.ForceUint32))
|
||||
{
|
||||
<option value="@rotation.ToString()">@rotation.ToString()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (refreshRates != null)
|
||||
{
|
||||
<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.DisplayConfig.ApplyRefreshRate" id="applyRefreshRate">
|
||||
}
|
||||
<button class="accordion-button collapsed py-2 @(Preset.DisplayConfig.ApplyRefreshRate ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#refreshRateSettings">
|
||||
Refresh Rate
|
||||
</button>
|
||||
</span>
|
||||
</h2>
|
||||
<div id="refreshRateSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
@if (refreshRates != null)
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="refreshRate">Refresh Rate</label>
|
||||
<select class="form-select" id="refreshRate" @bind="selectedRefreshRate">
|
||||
@foreach (var refreshRate in refreshRates)
|
||||
{
|
||||
<option value="@refreshRate.ToString()">@refreshRate.ToString() Hz</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<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.applyColorData" id="applyColorData">
|
||||
}
|
||||
<button class="accordion-button collapsed py-2 @(Preset.applyColorData ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#colorSettings">
|
||||
Color settings
|
||||
</button>
|
||||
</span>
|
||||
</h2>
|
||||
<div id="colorSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="mb-2">
|
||||
<input class="form-check-input" type="checkbox" @bind="Preset.colorData.UseDefaultSettings" id="useDefaultSettings">
|
||||
<label class="form-check-label" for="useDefaultSettings">
|
||||
Use default settings
|
||||
</label>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="bitDepth">Bit depth</label>
|
||||
<select class="form-select" id="bitDepth" @bind="Preset.colorData.ColorDepth" disabled="@Preset.colorData.UseDefaultSettings">
|
||||
@foreach (var bitDepth in Enum.GetValues<ColorDataDepth>())
|
||||
{
|
||||
<option value="@bitDepth.ToString()">@bitDepth.ToString()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="colorFormat">Format</label>
|
||||
<select class="form-select" id="colorFormat" @bind="Preset.colorData.ColorFormat" disabled="@Preset.colorData.UseDefaultSettings">
|
||||
@foreach (var format in Enum.GetValues<ColorDataFormat>())
|
||||
{
|
||||
<option value="@format.ToString()">@format.ToString()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="dynamicRange">Dynamic range</label>
|
||||
<select class="form-select" id="dynamicRange" @bind="Preset.colorData.DynamicRange" disabled="@Preset.colorData.UseDefaultSettings">
|
||||
@foreach (var range in Enum.GetValues<ColorDataDynamicRange>())
|
||||
{
|
||||
<option value="@range.ToString()">@range.ToString()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="colorimetry">Color space</label>
|
||||
<select class="form-select" id="colorimetry" @bind="Preset.colorData.Colorimetry" disabled="@Preset.colorData.UseDefaultSettings">
|
||||
@foreach (var space in Enum.GetValues<ColorDataColorimetry>())
|
||||
{
|
||||
<option value="@space.ToString()">@space.ToString()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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.ApplyColorEnhancements" id="ApplyColorEnhancements">
|
||||
}
|
||||
<button class="accordion-button collapsed py-2 @(Preset.ApplyColorEnhancements ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#colorEnhancements">
|
||||
Color enhancements
|
||||
</button>
|
||||
</span>
|
||||
</h2>
|
||||
<div id="colorEnhancements" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="digitalVibrance">Digital vibrance: @Preset.ColorEnhancementSettings.DigitalVibranceLevel</label>
|
||||
<input class="form-range" type="range" min="0" max="100" @bind="Preset.ColorEnhancementSettings.DigitalVibranceLevel" id="digitalVibrance">
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="hue">Hue: @Preset.ColorEnhancementSettings.HueAngle°</label>
|
||||
<input class="form-range" type="range" min="0" max="359" @bind="Preset.ColorEnhancementSettings.HueAngle" id="hue">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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.applyDriverSettings" id="applyDriverSettings">
|
||||
}
|
||||
<button class="accordion-button collapsed py-2 @(Preset.applyDriverSettings ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#driverSettings">
|
||||
Driver settings
|
||||
</button>
|
||||
</span>
|
||||
</h2>
|
||||
<div id="driverSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="mb-3">
|
||||
<button class="btn btn-secondary btn-sm" type="button" data-bs-target="#driverSettingsModal" data-bs-toggle="modal" @onclick="DriverSettingsOnClick">Open driver settings</button>
|
||||
<span>@Preset.driverSettings.Count settings configured</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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.ApplyNovideoSettings" id="applyNovideo">
|
||||
}
|
||||
<button class="accordion-button collapsed py-2 @(Preset.ApplyNovideoSettings ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#novideoSettings">
|
||||
Novideo settings
|
||||
</button>
|
||||
</span>
|
||||
</h2>
|
||||
<div id="novideoSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="mb-2">
|
||||
<input class="form-check-input" type="checkbox" @bind="Preset.NovideoSettings.ApplyClamp" id="applyClamp">
|
||||
<label class="form-check-label" for="applyClamp">
|
||||
Apply clamp
|
||||
</label>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="novideoColorSpace">Target color space</label>
|
||||
<select class="form-select" id="novideoColorSpace" @bind="Preset.NovideoSettings.ColorSpace">
|
||||
@foreach (var colorSpace in Enum.GetValues<NovideoColorSpace>())
|
||||
{
|
||||
<option value="@colorSpace.ToString()">@colorSpace.ToString()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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.applyDithering" id="applyDithering">
|
||||
}
|
||||
<button class="accordion-button collapsed py-2 @(Preset.applyDithering ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#ditherSettings">
|
||||
Dithering
|
||||
</button>
|
||||
</span>
|
||||
</h2>
|
||||
<div id="ditherSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="state">State</label>
|
||||
<select class="form-select" id="state" @bind="Preset.NvDitherState">
|
||||
@foreach (var state in Enum.GetValues<NvDitherState>())
|
||||
{
|
||||
<option value="@state.ToString()">@state.GetDescription()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="mode">Mode</label>
|
||||
<select class="form-select" id="mode" @bind="Preset.NvDitherMode">
|
||||
@foreach (var mode in Enum.GetValues<NvDitherMode>())
|
||||
{
|
||||
<option value="@mode.ToString()">@mode.GetDescription()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="bits">Bit depth</label>
|
||||
<select class="form-select" id="bits" @bind="Preset.NvDitherBits">
|
||||
@foreach (var bits in Enum.GetValues<NvDitherBits>())
|
||||
{
|
||||
<option value="@bits.ToString()">@bits.GetDescription()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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.applyHDR" id="applyHDR">
|
||||
}
|
||||
<button class="accordion-button collapsed py-2 @(Preset.applyHDR ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#hdrSettings">
|
||||
HDR
|
||||
</button>
|
||||
</span>
|
||||
</h2>
|
||||
<div id="hdrSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="mb-2">
|
||||
<input class="form-check-input" type="checkbox" @bind="Preset.HDREnabled" id="hdrEnabled">
|
||||
<label class="form-check-label" for="hdrEnabled">
|
||||
HDR enabled
|
||||
</label>
|
||||
</div>
|
||||
@if (!Preset.IsDisplayPreset) {
|
||||
<div class="mb-2">
|
||||
<input class="form-check-input" type="checkbox" @bind="Preset.toggleHDR" id="toggleHdr">
|
||||
<label class="form-check-label" for="toggleHdr">
|
||||
Toggle HDR
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="mode">Output mode</label>
|
||||
<select class="form-select" id="mode" @bind="Preset.HdrSettings.DisplayOutputMode">
|
||||
@foreach (var mode in Enum.GetValues<NvOutputMode>())
|
||||
{
|
||||
<option value="@mode.ToString()">@mode.GetDescription()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-range" for="sdrBrightness">SDR brightness (@Preset.SDRBrightness%)</label>
|
||||
<input class="form-range" type="range" min="0" max="100" @bind="Preset.SDRBrightness" id="sdrBrightness">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (!Preset.IsDisplayPreset || Preset.MonitorConnectionType == NvAPIWrapper.Native.GPU.MonitorConnectionType.HDMI)
|
||||
{
|
||||
<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.applyHdmiSettings" id="applyHdmiSettings">
|
||||
}
|
||||
<button class="accordion-button collapsed py-2 @(Preset.applyHdmiSettings ? "text-warning fw-bold" : "")" type="button" data-bs-toggle="collapse" data-bs-target="#hdmiSettings">
|
||||
HDMI settings
|
||||
</button>
|
||||
</span>
|
||||
</h2>
|
||||
<div id="hdmiSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="contentType">Content type</label>
|
||||
<select class="form-select" id="contentType" @bind="Preset.HdmiInfoFrameSettings.ContentType" @oninput="HdmiSettingsOnInput">
|
||||
@foreach (var type in Enum.GetValues<InfoFrameVideoContentType>())
|
||||
{
|
||||
<option value="@type.ToString()">@type.GetDescription()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="colorimetry">Colorimetry</label>
|
||||
<select class="form-select" id="colorimetry" @bind="Preset.HdmiInfoFrameSettings.Colorimetry" @oninput="HdmiSettingsOnInput">
|
||||
@foreach (var colorimetry in Enum.GetValues<InfoFrameVideoColorimetry>())
|
||||
{
|
||||
<option value="@colorimetry.ToString()">@colorimetry.GetDescription()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<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">
|
||||
@foreach (var extendedColorimetry in Enum.GetValues<InfoFrameVideoExtendedColorimetry>())
|
||||
{
|
||||
<option value="@extendedColorimetry.ToString()">@extendedColorimetry.GetDescription()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="() => IsVisible = false">Close</button>
|
||||
<button type="submit" class="btn btn-primary" data-bs-dismiss="@(IsValid() ? "modal" : "")" @onclick="ApplyClick">@(Preset?.IsDisplayPreset == true ? "Apply" : "Save")</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
public NvPreset? Preset { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public NvPreset? PresetParam { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<NvPreset, Task>? AfterApply { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<NvPreset, Task>? SetDriverSettingsPreset { get; set; }
|
||||
|
||||
private string? selectedActiveResolution;
|
||||
private string? selectedVirtualResolution;
|
||||
private string? selectedRefreshRate;
|
||||
|
||||
private List<VirtualResolution>? resolutions;
|
||||
private List<Rational>? refreshRates;
|
||||
private List<NvPreset>? displayPresets;
|
||||
|
||||
private string WasValidated = "";
|
||||
private bool IsVisible;
|
||||
private bool IsDriverSettingsOpened;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (IsDriverSettingsOpened && Preset != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Preset = PresetParam == null ? null : PresetParam.CloneWithId();
|
||||
if (Preset != null && PresetParam != null)
|
||||
{
|
||||
resolutions = await _rpcClientService.CallAsync<List<VirtualResolution>>("NvService", "GetAvailableResolutionsV2", Preset);
|
||||
refreshRates = await _rpcClientService.CallAsync<List<Rational>>("NvService", "GetAvailableRefreshRatesV2", Preset);
|
||||
}
|
||||
selectedActiveResolution = Preset?.DisplayConfig.Resolution?.GetActiveString();
|
||||
selectedVirtualResolution = Preset?.DisplayConfig.Resolution?.GetVirtualString();
|
||||
selectedRefreshRate = Preset?.DisplayConfig.RefreshRate?.ToString();
|
||||
|
||||
if (Preset?.IsDisplayPreset != true)
|
||||
{
|
||||
displayPresets = await _rpcClientService.CallAsync<List<NvPreset>>("NvService", "GetDisplayPresets");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnAfterRender(bool firstRender)
|
||||
{
|
||||
// if (IsVisible && Preset?.IsDisplayPreset == false)
|
||||
// {
|
||||
// Preset.UpdateAutoApplySettings(keepChanges: true);
|
||||
// }
|
||||
}
|
||||
|
||||
private async Task ApplyClick(MouseEventArgs e)
|
||||
{
|
||||
if (Preset == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsValid())
|
||||
{
|
||||
WasValidated = "was-validated";
|
||||
return;
|
||||
}
|
||||
|
||||
var newResolution = resolutions?.FirstOrDefault(r => r.GetActiveString() == selectedActiveResolution);
|
||||
if (newResolution != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(selectedVirtualResolution) && selectedActiveResolution != selectedVirtualResolution)
|
||||
{
|
||||
newResolution = new VirtualResolution(newResolution.ActiveWidth, newResolution.ActiveHeight);
|
||||
var parts = selectedVirtualResolution.Split('x');
|
||||
newResolution.VirtualWidth = uint.Parse(parts[0]);
|
||||
newResolution.VirtualHeight = uint.Parse(parts[1]);
|
||||
}
|
||||
Preset.DisplayConfig.Resolution = newResolution;
|
||||
}
|
||||
else
|
||||
{
|
||||
Preset.DisplayConfig.Resolution = new VirtualResolution(0, 0);
|
||||
}
|
||||
|
||||
var newRefreshRate = refreshRates?.FirstOrDefault(r => r.ToString() == selectedRefreshRate);
|
||||
if (newRefreshRate != null)
|
||||
{
|
||||
Preset.DisplayConfig.RefreshRate = newRefreshRate;
|
||||
}
|
||||
|
||||
Preset.ApplyNovideoSettings = Preset.NovideoSettings.ApplyClamp;
|
||||
|
||||
if (Preset.IsDisplayPreset)
|
||||
{
|
||||
Preset.UpdateAutoApplySettings(PresetParam);
|
||||
|
||||
await ApplyDisplayPreset();
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = await _rpcClientService.CallAsync<bool>("NvService", "UpdatePreset", Preset);
|
||||
}
|
||||
|
||||
if (AfterApply != null)
|
||||
{
|
||||
await AfterApply.Invoke(Preset);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ApplyDisplayPreset()
|
||||
{
|
||||
var result = await _rpcClientService.CallAsync<bool>("NvService", "ApplyPreset", Preset);
|
||||
}
|
||||
|
||||
private void HdmiSettingsOnInput(ChangeEventArgs e)
|
||||
{
|
||||
Preset!.applyHdmiSettings = true;
|
||||
}
|
||||
|
||||
private async Task DriverSettingsOnClick(MouseEventArgs e)
|
||||
{
|
||||
IsDriverSettingsOpened = true;
|
||||
SetDriverSettingsPreset?.Invoke(Preset!);
|
||||
}
|
||||
|
||||
private void ModalOnFocus(FocusEventArgs e)
|
||||
{
|
||||
if (IsVisible && IsDriverSettingsOpened)
|
||||
{
|
||||
IsDriverSettingsOpened = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
if (!IsVisible)
|
||||
{
|
||||
WasValidated = "";
|
||||
IsVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValid()
|
||||
{
|
||||
return Preset?.IsDisplayPreset == true || !string.IsNullOrEmpty(Preset?.name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.NVIDIA
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
|
||||
@rendermode InteractiveServer
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
|
||||
<div class="modal" id="settingsModal" tabindex="-1" @onfocus="OnFocus">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">NVIDIA settings</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="() => Config = null"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="row g-1">
|
||||
@if (Config == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<ShortcutInput Label="Quick Access Shortcut" @bind-Shortcut="Config.QuickAccessShortcut" />
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="Config.ShowOverclocking" id="showOverclocking">
|
||||
<label class="form-check-label" for="showOverclocking">
|
||||
Show overclocking
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="Config.ApplyNovideoOnStartup" id="applyNovideoOnStartup">
|
||||
<label class="form-check-label" for="applyNovideoOnStartup">
|
||||
Apply Novideo settings on startup
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="() => Config = null">Close</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" @onclick="ApplyClick">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private NvServiceConfig? Config { get; set; }
|
||||
|
||||
private async Task ApplyClick(MouseEventArgs e)
|
||||
{
|
||||
if (Config == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await _rpcClientService.CallAsync<bool>("NvService", "UpdateConfig", Config);
|
||||
|
||||
if (result)
|
||||
{
|
||||
Config = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnFocus(FocusEventArgs e)
|
||||
{
|
||||
if (Config != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Config = await _rpcClientService.CallAsync<NvServiceConfig>("NvService", "GetConfig");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
@page "/NVIDIA"
|
||||
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.NVIDIA
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
@using ColorControl.Shared.Common;
|
||||
@using ColorControl.UI.Generics
|
||||
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
@inject ProtectedLocalStorage _localStorage
|
||||
|
||||
<PageTitle>NVIDIA controller</PageTitle>
|
||||
|
||||
<NvidiaSettings />
|
||||
<NvidiaDithering Visible="DitheringVisible" />
|
||||
<NvidiaDriverSettings Preset="CurrentDriverSettingsPreset" />
|
||||
|
||||
<div class="d-flex align-items-center">
|
||||
<h1>NVIDIA controller</h1>
|
||||
<div class="d-flex ms-auto">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-sm btn-primary dropdown-toggle me-2" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Tools
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#ditheringModal" @onclick="() => DitheringVisible = true">Test dithering</a></li>
|
||||
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#driverSettingsModal" @onclick="() => CurrentDriverSettingsPreset = null">Driver settings</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#settingsModal">
|
||||
Settings
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Displays</h2>
|
||||
|
||||
<NvidiaPreset PresetParam="CurrentPreset" AfterApply="AfterPresetApplied" SetDriverSettingsPreset="SetDriverSettingsPreset" />
|
||||
<ConfirmModal Title="Delete preset" Message="Are you sure you want to delete the preset?" OnConfirm="PresetOnDeleteConfirmed" />
|
||||
|
||||
@if (DisplayPresets == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
@if (!DisplayPresets.Any())
|
||||
{
|
||||
<span>No displays found</span>
|
||||
}
|
||||
@foreach (var preset in DisplayPresets)
|
||||
{
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
@preset.displayName
|
||||
@if (preset.DisplayConfig.IsPrimary == true)
|
||||
{
|
||||
<span class="ms-2 badge text-bg-primary" title="Primary display">Primary</span>
|
||||
}
|
||||
<button type="button" class="btn btn-sm btn-primary ms-2" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => DisplayPresetOnClick(preset)">
|
||||
Settings
|
||||
</button>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Presets == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mb-2 d-flex align-items-center">
|
||||
<h2>Presets</h2>
|
||||
<div class="d-flex ms-auto">
|
||||
<select class="form-select me-2" id="presetOrder" @bind="PresetOrder" @bind:after="PresetOrderChanged">
|
||||
@foreach (var order in Enum.GetValues<PresetOrder>())
|
||||
{
|
||||
<option value="@order">Order by: @order.GetDescription()</option>
|
||||
}
|
||||
</select>
|
||||
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => AddPresetOnClick()">
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<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>
|
||||
<div class="d-flex align-items-start justify-content-between">
|
||||
<div>
|
||||
<h5 class="mb-1">
|
||||
@preset.name <small class="text-muted">@(preset.primaryDisplay ? "Primary display" : preset.displayName)</small>
|
||||
@if (preset.ShowInQuickAccess)
|
||||
{
|
||||
<span class="ms-2 badge text-bg-warning" title="Show in Quick Access">QA</span>
|
||||
}
|
||||
</h5>
|
||||
</div>
|
||||
<span>
|
||||
<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>
|
||||
<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 class="text-muted">@preset.shortcut</small>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<NvPreset>? Presets;
|
||||
private List<NvPreset>? DisplayPresets;
|
||||
|
||||
private NvPreset? CurrentPreset;
|
||||
private NvPreset? CurrentDriverSettingsPreset;
|
||||
private bool DitheringVisible;
|
||||
|
||||
private PresetOrder PresetOrder;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await RefreshPresets();
|
||||
DisplayPresets = await _rpcClientService.CallAsync<List<NvPreset>>("NvService", "GetDisplayPresets");
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstTime)
|
||||
{
|
||||
if (firstTime)
|
||||
{
|
||||
PresetOrder = await _localStorage.TryGet<PresetOrder>("NvidiaPresetOrder");
|
||||
Presets = Presets?.OrderPresetsBy(PresetOrder);
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PresetOnClick(NvPreset preset)
|
||||
{
|
||||
await _rpcClientService.CallAsync<bool>("NvService", "ApplyPresetWithId", preset.id);
|
||||
}
|
||||
|
||||
private async Task PresetOnEditClick(NvPreset preset)
|
||||
{
|
||||
CurrentPreset = preset;
|
||||
}
|
||||
|
||||
private async Task PresetOnCloneClick(NvPreset preset)
|
||||
{
|
||||
CurrentPreset = new NvPreset(preset);
|
||||
CurrentPreset.SetCloneValues();
|
||||
}
|
||||
|
||||
private async Task PresetOnDeleteClick(NvPreset preset)
|
||||
{
|
||||
CurrentPreset = preset;
|
||||
}
|
||||
|
||||
private async Task PresetOnDeleteConfirmed()
|
||||
{
|
||||
if (CurrentPreset == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _rpcClientService.CallAsync<bool>("NvService", "DeletePreset", CurrentPreset.id);
|
||||
await RefreshPresets();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task DisplayPresetOnClick(NvPreset preset)
|
||||
{
|
||||
CurrentPreset = preset;
|
||||
}
|
||||
|
||||
private async Task AddPresetOnClick()
|
||||
{
|
||||
CurrentPreset = new NvPreset();
|
||||
}
|
||||
|
||||
private async Task AfterPresetApplied(NvPreset preset)
|
||||
{
|
||||
if (preset.IsDisplayPreset)
|
||||
{
|
||||
DisplayPresets = await _rpcClientService.CallAsync<List<NvPreset>>("NvService", "GetDisplayPresets");
|
||||
}
|
||||
else
|
||||
{
|
||||
await RefreshPresets();
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task SetDriverSettingsPreset(NvPreset preset)
|
||||
{
|
||||
CurrentDriverSettingsPreset = preset;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task PresetOrderChanged()
|
||||
{
|
||||
await _localStorage.SetAsync("NvidiaPresetOrder", PresetOrder);
|
||||
await RefreshPresets();
|
||||
}
|
||||
|
||||
private async Task RefreshPresets()
|
||||
{
|
||||
Presets = await _rpcClientService.CallAsync<List<NvPreset>>("NvService", "GetPresets");
|
||||
|
||||
Presets = Presets?.OrderPresetsBy(PresetOrder);
|
||||
}
|
||||
}
|
||||
@@ -1,52 +1,320 @@
|
||||
@page "/options"
|
||||
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
|
||||
@rendermode InteractiveServer
|
||||
@inject OptionsService _optionsService
|
||||
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
@inject AppState AppState
|
||||
@inject NavigationManager _navigationManager;
|
||||
|
||||
<PageTitle>Options</PageTitle>
|
||||
|
||||
<h1>Options</h1>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">General</h5>
|
||||
@if (config == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-lg-6 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">General</h5>
|
||||
<form>
|
||||
<div class="mb-3 form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="config.AutoStart" id="startAfterLogin" @oninput="StartAfterLoginInput">
|
||||
<label class="form-check-label" for="startAfterLogin">
|
||||
Automatically start after login
|
||||
</label>
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="config.CheckForUpdates" id="checkForUpdates" @oninput="CheckForUpdatesInput">
|
||||
<label class="form-check-label" for="checkForUpdates">
|
||||
Automatically check for updates
|
||||
</label>
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="config.AutoInstallUpdates" id="autoInstallUpdates" @oninput="AutoInstallUpdatesInput">
|
||||
<label class="form-check-label" for="autoInstallUpdates">
|
||||
Automatically install updates
|
||||
</label>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="elevationMethod">
|
||||
Elevation Method
|
||||
</label>
|
||||
<select class="form-select" @bind="elevationMethod" id="elevationMethod" @oninput="ElevationMethodInput" aria-describedby="elevationMethodHelp">
|
||||
<option value="0">None</option>
|
||||
<option value="1">Run as administrator</option>
|
||||
<option value="2">Use service</option>
|
||||
<option value="3">Use elevated process</option>
|
||||
</select>
|
||||
<div id="elevationMethodHelp" class="form-text">
|
||||
Elevation is needed in some cases where ColorControl needs administrator rights.
|
||||
Some operations like installing a service, changing the priority of a process or creating a temporary IP-route for improved WOL-functionality will not work without those rights.
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">UI-settings</h5>
|
||||
<form>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="uiType">Default UI</label>
|
||||
<select class="form-select" id="uiType" @bind="config.UiType" @oninput="UiTypeOnInput">
|
||||
@foreach (var type in Enum.GetValues<UiType>())
|
||||
{
|
||||
<option value="@type.ToString()">@type.GetDescription()</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="webUiPort">
|
||||
Web UI port
|
||||
</label>
|
||||
<input class="form-control" type="number" @bind="config.UiPort" id="webUiPort" @bind:after="UiPortBlur">
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="config.MinimizeOnClose" id="minimizeOnClose" @oninput="MinimizeOnCloseInput">
|
||||
<label class="form-check-label" for="minimizeOnClose">
|
||||
Minimize on close
|
||||
</label>
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="config.StartMinimized" id="startMinimized" @oninput="StartMinimizedInput">
|
||||
<label class="form-check-label" for="startMinimized">
|
||||
Start minimized
|
||||
</label>
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="config.MinimizeToTray" id="minimizeToTray" @oninput="MinimizeToTrayInput">
|
||||
<label class="form-check-label" for="minimizeToTray">
|
||||
Minimize to system tray
|
||||
</label>
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="config.UseDarkMode" id="useDarkMode" @oninput="UseDarkModeInput">
|
||||
<label class="form-check-label" for="useDarkMode">
|
||||
Dark Mode
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Modules</h5>
|
||||
<form>
|
||||
@foreach (var module in config.Modules)
|
||||
{
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="@("module_" + module.DisplayName)" @bind="module.IsActive" @oninput="(ChangeEventArgs e) => ModuleOnInput(module, e)">
|
||||
<label class="form-check-label" for="@("module_" + module.DisplayName)">@module.DisplayName</label>
|
||||
</div>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Advanced</h5>
|
||||
<form>
|
||||
<div class="mb-3 form-check">
|
||||
<label class="form-range" for="processMonitorPollingInterval">
|
||||
Polling interval of process monitor (@config.ProcessMonitorPollingInterval milliseconds)
|
||||
</label>
|
||||
<input class="form-range" type="range" min="50" max="10000" step="50" @bind="config.ProcessMonitorPollingInterval" id="processMonitorPollingInterval" aria-describedby="processMonitorPollingIntervalHelp" @bind:after="PollingIntervalOnInput">
|
||||
<div id="processMonitorPollingIntervalHelp" class="form-text">Decreasing this delay may execute triggered presets sooner but can cause a higher CPU load</div>
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="config.UseRawInput" id="useRawInput" aria-describedby="useRawInputHelp" @oninput="UseRawInputInput">
|
||||
<label class="form-check-label" for="useRawInput">
|
||||
Use Raw Input for shortcuts (hot keys)
|
||||
</label>
|
||||
<div id="useRawInputHelp" class="form-text">This enables shortcuts to work during applications/games that block certain keys (like WinKey or Control). NOTE: if the application in the foreground runs with higher privileges than ColorControl, Raw Input does not work and normal hot keys are used</div>
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="config.SetMinTmlAndMaxTml" id="setMinTmlAndMaxTml" @oninput="SetMinMaxTmlInput">
|
||||
<label class="form-check-label" for="setMinTmlAndMaxTml">
|
||||
Set MinTML and MaxTML when applying color profiles
|
||||
</label>
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="config.DisableErrorPopupWhenApplyingPreset" id="disableErrorPopupWhenApplyingPreset" @oninput="DisableErrorPopupInput">
|
||||
<label class="form-check-label" for="disableErrorPopupWhenApplyingPreset">
|
||||
Disable error popup when applying presets
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Miscellaneous</h5>
|
||||
<form>
|
||||
<div class="mb-3 form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="config.FixChromeFonts" id="fixChromeFonts" aria-describedby="fixChromeFontsHelp" @bind:after="FixChromeFontsInput">
|
||||
<label class="form-check-label" for="fixChromeFonts">
|
||||
ClearType: fix the bad fonts in Chrome when using a monitor with a different pixel structure
|
||||
</label>
|
||||
<div id="fixChromeFontsHelp" class="form-text">This will add the parameter --disable-lcd-text to Chrome which enables grayscale anti-aliasing</div>
|
||||
</div>
|
||||
<ShortcutInput Label="Shortcut to start the blank screensaver" @bind-Shortcut="config.ScreenSaverShortcut" @bind-Shortcut:after="UpdateScreenSaverShortcut" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form class="ms-3">
|
||||
<div class="mb-3 form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="config.AutoStart" id="startAfterLogin">
|
||||
<label class="form-check-label" for="startAfterLogin">
|
||||
Automatically start after login
|
||||
</label>
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="config.StartMinimized" id="startMinimized">
|
||||
<label class="form-check-label" for="startMinimized">
|
||||
Start minimized
|
||||
</label>
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="config.MinimizeToTray" id="minimizeToTray">
|
||||
<label class="form-check-label" for="minimizeToTray">
|
||||
Minimize to tray
|
||||
</label>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<button class="btn btn-primary" @onclick="SaveConfig">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@code {
|
||||
private Config config;
|
||||
private Config? config;
|
||||
private string elevationMethod = "0";
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
config = _optionsService.GetConfig();
|
||||
config = await _rpcClientService.CallAsync<Config>("OptionsService", "GetConfig");
|
||||
elevationMethod = ((int)config.ElevationMethod).ToString();
|
||||
}
|
||||
|
||||
private void SaveConfig()
|
||||
private async Task SaveConfig()
|
||||
{
|
||||
_optionsService.SetConfig(config);
|
||||
await _rpcClientService.CallAsync<bool>("OptionsService", "SetConfig", config);
|
||||
}
|
||||
|
||||
private async Task UseDarkModeInput(ChangeEventArgs e)
|
||||
{
|
||||
var value = e.Value != null ? (bool)e.Value : false;
|
||||
var result = await UpdateBoolSetting("SetDarkMode", e);
|
||||
|
||||
if (result)
|
||||
{
|
||||
AppState.SetTheme(value ? "dark" : "light");
|
||||
//ThemeTransitionName = AppState.SelectedTheme == "light" ? "Dark theme" : "Light theme";
|
||||
_navigationManager.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StartAfterLoginInput(ChangeEventArgs e)
|
||||
{
|
||||
await UpdateBoolSetting("SetStartAfterLogin", e);
|
||||
}
|
||||
|
||||
private async Task MinimizeOnCloseInput(ChangeEventArgs e)
|
||||
{
|
||||
await UpdateBoolSetting("SetMinimizeOnClose", e);
|
||||
}
|
||||
|
||||
private async Task StartMinimizedInput(ChangeEventArgs e)
|
||||
{
|
||||
await UpdateBoolSetting("SetStartMinimized", e);
|
||||
}
|
||||
|
||||
private async Task MinimizeToTrayInput(ChangeEventArgs e)
|
||||
{
|
||||
await UpdateBoolSetting("SetMinimizeToTray", e);
|
||||
}
|
||||
|
||||
private async Task CheckForUpdatesInput(ChangeEventArgs e)
|
||||
{
|
||||
await UpdateBoolSetting("SetCheckForUpdates", e);
|
||||
}
|
||||
|
||||
private async Task AutoInstallUpdatesInput(ChangeEventArgs e)
|
||||
{
|
||||
await UpdateBoolSetting("SetAutoInstallUpdates", e);
|
||||
}
|
||||
|
||||
private async Task ModuleOnInput(Module module, ChangeEventArgs e)
|
||||
{
|
||||
module.IsActive = e.Value != null ? (bool)e.Value : false;
|
||||
|
||||
var result = await _rpcClientService.CallAsync<bool>("OptionsService", "UpdateModule", module);
|
||||
|
||||
if (result)
|
||||
{
|
||||
_navigationManager.NavigateTo("/options", true);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PollingIntervalOnInput()
|
||||
{
|
||||
await _rpcClientService.CallAsync<bool>("OptionsService", "SetProcessMonitorPollingInterval", config!.ProcessMonitorPollingInterval);
|
||||
}
|
||||
|
||||
private async Task UseRawInputInput(ChangeEventArgs e)
|
||||
{
|
||||
await UpdateBoolSetting("SetUseRawInput", e);
|
||||
}
|
||||
|
||||
private async Task SetMinMaxTmlInput(ChangeEventArgs e)
|
||||
{
|
||||
await UpdateBoolSetting("SetMinTmlAndMaxTml", e);
|
||||
}
|
||||
|
||||
private async Task DisableErrorPopupInput(ChangeEventArgs e)
|
||||
{
|
||||
await UpdateBoolSetting("SetDisableErrorPopupWhenApplyingPreset", e);
|
||||
}
|
||||
|
||||
private async Task FixChromeFontsInput()
|
||||
{
|
||||
await UpdateBoolSetting2("SetFixChromeFonts", config!.FixChromeFonts);
|
||||
}
|
||||
|
||||
private async Task UpdateScreenSaverShortcut()
|
||||
{
|
||||
await _rpcClientService.CallAsync<bool>("OptionsService", "SetScreenSaverShortcut", config!.ScreenSaverShortcut);
|
||||
}
|
||||
|
||||
private async Task<bool> UpdateBoolSetting(string method, ChangeEventArgs e)
|
||||
{
|
||||
var value = e.Value != null ? (bool)e.Value : false;
|
||||
return await UpdateBoolSetting2(method, value);
|
||||
}
|
||||
|
||||
private async Task<bool> UpdateBoolSetting2(string method, bool value)
|
||||
{
|
||||
return await _rpcClientService.CallAsync<bool>("OptionsService", method, value);
|
||||
}
|
||||
|
||||
private async Task ElevationMethodInput(ChangeEventArgs e)
|
||||
{
|
||||
var value = e.Value != null ? (ElevationMethod)int.Parse((string)e.Value) : ElevationMethod.None;
|
||||
await _rpcClientService.CallAsync<bool>("OptionsService", "SetElevationMethod", value);
|
||||
|
||||
config!.ElevationMethod = value;
|
||||
}
|
||||
|
||||
private async Task UiTypeOnInput(ChangeEventArgs e)
|
||||
{
|
||||
if (Enum.TryParse<UiType>(e.Value?.ToString(), out var uiType))
|
||||
{
|
||||
await _rpcClientService.CallAsync<bool>("OptionsService", "SetUiType", uiType);
|
||||
|
||||
config!.UiType = uiType;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UiPortBlur()
|
||||
{
|
||||
await _rpcClientService.CallAsync<bool>("OptionsService", "SetUiPort", config!.UiPort);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.Samsung
|
||||
@using ColorControl.Shared.Services
|
||||
@using NvAPIWrapper.Native.Display
|
||||
@using ColorControl.Shared.Common
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
@inject AppState AppState
|
||||
@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-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(Device?.Name == null ? $"Add new device" : $"Settings of device {Device.Name}")</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="() => IsVisible = false"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="row g-1 needs-validation @WasValidated" novalidate>
|
||||
@if (Device == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="name">Name</label>
|
||||
<input class="form-control" id="name" type="text" @bind="Device.Name" required />
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" @bind="Device.IsCustom" id="isCustom">
|
||||
<label class="form-check-label" for="isCustom">
|
||||
Is custom device
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="ipAddress">IP-address</label>
|
||||
<input class="form-control" id="ipAddress" type="text" @bind="Device.IpAddress" disabled="@(!Device.IsCustom)" required="Device.IsCustom" />
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="macAddress">MAC-address</label>
|
||||
<input class="form-control" id="macAddress" type="text" @bind="Device.MacAddress" disabled="@(!Device.IsCustom)" />
|
||||
</div>
|
||||
@if (!Device.IsConnected)
|
||||
{
|
||||
<div class="mb-2">
|
||||
<button class="btn btn-primary me-2" type="button" disabled="@IsTestingConnection" @onclick="TestConnectionClick">
|
||||
@if (IsTestingConnection)
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm me-1" aria-hidden="true"></span>
|
||||
}
|
||||
<span role="status">Test connection</span>
|
||||
</button>
|
||||
@if (IsTestConnectionSuccessful != null)
|
||||
{
|
||||
if (IsTestConnectionSuccessful == true)
|
||||
{
|
||||
<span class="text-success">Connection test was successful!</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-danger">Connection failed!</span>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="accordion accordion-flush" id="accordion">
|
||||
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#deviceOptions">
|
||||
Device options
|
||||
</button>
|
||||
</h2>
|
||||
<div id="deviceOptions" class="accordion-collapse collapse" data-bs-parent="#accordion">
|
||||
<div class="accordion-body">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOnAfterStartup" @bind="Device.Options.PowerOnAfterStartup">
|
||||
<label class="form-check-label" for="checkPowerOnAfterStartup">Power on after startup</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOnAfterResume" @bind="Device.Options.PowerOnAfterResume">
|
||||
<label class="form-check-label" for="checkPowerOnAfterResume">Power on after resume from standby</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOffOnShutdown" @bind="Device.Options.PowerOffOnShutdown">
|
||||
<label class="form-check-label" for="checkPowerOffOnShutdown">Power off on shutdown</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOffOnStandby" @bind="Device.Options.PowerOffOnStandby">
|
||||
<label class="form-check-label" for="checkPowerOffOnStandby">Power off on standby</label>
|
||||
</div>
|
||||
<div class="form-check form-switch mb-2">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOffOnScreensaverStart" @bind="Device.Options.PowerOffOnScreenSaver">
|
||||
<label class="form-check-label" for="checkPowerOffOnScreensaverStart">Power off when screensaver activates</label>
|
||||
<button class="btn btn-sm btn-primary ms-2" type="button" data-bs-toggle="collapse" data-bs-target="#screenSaverSettings" aria-expanded="false" aria-controls="screenSaverSettings">
|
||||
Settings
|
||||
</button>
|
||||
</div>
|
||||
<div class="collapse mb-3" id="screenSaverSettings">
|
||||
<div class="card card-body">
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="minScreenSaverDuration">Minimal screen saver duration (seconds)</label>
|
||||
<input class="form-control" id="minScreenSaverDuration" type="number" @bind="Device.Options.ScreenSaverMinimalDuration" />
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkTurnScreenOffInsteadOfPowerOff" @bind="Device.Options.TurnScreenOffOnScreenSaver">
|
||||
<label class="form-check-label" for="checkTurnScreenOffInsteadOfPowerOff">Turn screen off instead of power off</label>
|
||||
<div class="form-text">NOTE: turning the screen off might not work correctly on all models</div>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkTurnScreenOnInsteadOfPowerOn" @bind="Device.Options.TurnScreenOnAfterScreenSaver">
|
||||
<label class="form-check-label" for="checkTurnScreenOnInsteadOfPowerOn">Turn screen on instead of power on</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkPerformActionOnManualScreenSaver" @bind="Device.Options.HandleManualScreenSaver">
|
||||
<label class="form-check-label" for="checkPerformActionOnManualScreenSaver">Perform action even on manually executed screen saver</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOnOnScreensaverEnd" @bind="Device.Options.PowerOnAfterScreenSaver">
|
||||
<label class="form-check-label" for="checkPowerOnOnScreensaverEnd">Power on when screensaver deactivates</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkPowerOnAfterManualPowerOff" @bind="Device.Options.PowerOnAfterManualPowerOff">
|
||||
<label class="form-check-label" for="checkPowerOnAfterManualPowerOff">Power on even after manual power off</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkAllowTriggers" @bind="Device.Options.TriggersEnabled">
|
||||
<label class="form-check-label" for="checkAllowTriggers">Allow triggers to be fired for this device</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkWindowsPowerOn" @bind="Device.Options.PowerOnByWindows">
|
||||
<label class="form-check-label" for="checkWindowsPowerOn">Use Windows power settings to power on</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkWindowsPowerOff" @bind="Device.Options.PowerOffByWindows">
|
||||
<label class="form-check-label" for="checkWindowsPowerOff">Use Windows power settings to power off</label>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="checkUseSecureConnection" @bind="Device.Options.UseSecureConnection">
|
||||
<label class="form-check-label" for="checkUseSecureConnection">Use secure connection</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="() => IsVisible = false">Close</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="@(IsValid() ? "modal" : "")" @onclick="ApplyClick">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private SamsungDeviceDto? _device;
|
||||
|
||||
[Parameter]
|
||||
public SamsungDeviceDto? Device { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<SamsungDeviceDto, Task>? AfterApply { get; set; }
|
||||
|
||||
private bool IsTestingConnection;
|
||||
private bool? IsTestConnectionSuccessful;
|
||||
|
||||
private string WasValidated = "";
|
||||
private bool IsVisible;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (_device != Device)
|
||||
{
|
||||
_device = Device;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ApplyClick(MouseEventArgs e)
|
||||
{
|
||||
if (Device == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsValid())
|
||||
{
|
||||
WasValidated = "was-validated";
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await _rpcClientService.CallAsync<bool>("SamsungService", "UpdateDevice", Device);
|
||||
|
||||
if (AfterApply != null)
|
||||
{
|
||||
await AfterApply.Invoke(Device);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TestConnectionClick(MouseEventArgs e)
|
||||
{
|
||||
IsTestingConnection = true;
|
||||
try
|
||||
{
|
||||
IsTestConnectionSuccessful = await _rpcClientService.CallWithOptionsAsync<bool>("SamsungService", "TestDevice", new RpcUiClientOptions { Timeout = 120000 }, Device);
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsTestingConnection = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ModalOnFocus(FocusEventArgs e)
|
||||
{
|
||||
if (!IsVisible)
|
||||
{
|
||||
WasValidated = "";
|
||||
IsVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValid()
|
||||
{
|
||||
return !string.IsNullOrEmpty(Device?.Name) && (Device?.IsCustom != true || !string.IsNullOrEmpty(Device?.IpAddress));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.Samsung
|
||||
@using ColorControl.Shared.Services
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
@using NvAPIWrapper.Native.Display
|
||||
@using ColorControl.Shared.Common
|
||||
@using static ColorControl.UI.Components.Pages.Generic.StepsInput<ColorControl.Shared.Contracts.Samsung.SamsungPreset>
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
@inject AppState AppState
|
||||
@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-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@(Preset?.id == 0 ? $"Settings for new preset" : $"Settings of preset {Preset?.IdOrName}")</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="() => IsVisible = false"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="row g-1 needs-validation @WasValidated" novalidate>
|
||||
@if (Preset == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="name">Name</label>
|
||||
<input class="form-control" id="name" type="text" @bind="Preset.name" required />
|
||||
</div>
|
||||
@if (Devices != null)
|
||||
{
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="device">Device</label>
|
||||
<select class="form-select" id="device" @bind="Preset.DeviceMacAddress">
|
||||
<option value="">Globally selected device</option>
|
||||
@foreach (var device in Devices)
|
||||
{
|
||||
<option value="@device.MacAddress">@device.Name</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox" @bind="Preset.ShowInQuickAccess" id="showInQuickAccess">
|
||||
<label class="form-check-label" for="showInQuickAccess">
|
||||
Show in Quick Access
|
||||
</label>
|
||||
</div>
|
||||
<ShortcutInput Label="Shortcut" @bind-Shortcut="Preset.shortcut" />
|
||||
|
||||
<div class="accordion accordion-flush" id="presetAccordion">
|
||||
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#advancedSettings">
|
||||
Advanced settings
|
||||
</button>
|
||||
</h2>
|
||||
<div id="advancedSettings" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
<TriggerInput Triggers="Preset.Triggers"></TriggerInput>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button class="accordion-button collapsed py-2" type="button" data-bs-toggle="collapse" data-bs-target="#steps">
|
||||
Steps
|
||||
</button>
|
||||
</h2>
|
||||
<div id="steps" class="accordion-collapse collapse" data-bs-parent="#presetAccordion">
|
||||
<div class="accordion-body">
|
||||
<StepsInput Label="Steps" @bind-Steps="Preset.Steps" CustomItems="CustomItems" ActionItems="CustomActions" SamsungPresetsEnabled="false" TPreset="SamsungPreset"></StepsInput>
|
||||
<div class="mb-2">
|
||||
<label class="form-label" for="description">Description</label>
|
||||
<textarea class="form-control" id="description" type="text" @bind="Preset.Description" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="() => IsVisible = false">Close</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="@(IsValid() ? "modal" : "")" @onclick="ApplyClick">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private SamsungPreset? Preset;
|
||||
|
||||
[Parameter]
|
||||
public SamsungPreset? PresetParam { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public List<SamsungDeviceDto>? Devices { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<SamsungPreset, Task>? AfterApply { get; set; }
|
||||
|
||||
private List<CustomStepItem>? CustomItems;
|
||||
private List<ActionStepItem>? CustomActions;
|
||||
|
||||
private string WasValidated = "";
|
||||
private bool IsVisible;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (PresetParam == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Preset = PresetParam.CloneWithId();
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
CustomItems = [new CustomStepItem("RC buttons", Enum.GetValues<ButtonType>().Select(b => b.ToString()).ToList())];
|
||||
|
||||
var actions = await _rpcClientService.CallAsync<List<InvokableActionDto<SamsungPreset>>>("SamsungService", "GetInvokableActions");
|
||||
|
||||
if (actions != null)
|
||||
{
|
||||
CustomActions = [new ActionStepItem("Actions", actions.Where(a => !a.Advanced).ToList())];
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ApplyClick(MouseEventArgs e)
|
||||
{
|
||||
if (Preset == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsValid())
|
||||
{
|
||||
WasValidated = "was-validated";
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await _rpcClientService.CallAsync<bool>("SamsungService", "UpdatePreset", Preset);
|
||||
|
||||
if (AfterApply != null)
|
||||
{
|
||||
await AfterApply.Invoke(Preset);
|
||||
}
|
||||
}
|
||||
|
||||
private void ModalOnFocus(FocusEventArgs e)
|
||||
{
|
||||
if (!IsVisible)
|
||||
{
|
||||
WasValidated = "";
|
||||
IsVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValid()
|
||||
{
|
||||
return !string.IsNullOrEmpty(Preset?.name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.Samsung
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
|
||||
@rendermode InteractiveServer
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
|
||||
<FieldsModal ModalId="serviceMenuModal" Title="@Title" ConfirmCaption="@ConfirmCaption" SecondaryCaption="@SecondaryCaption"
|
||||
Fields="Fields" OnConfirm="NextClick" OnSecondary="SecondaryClick" Dismiss="@Dismiss" OnShow="OnShow"></FieldsModal>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public List<SamsungDeviceDto>? Devices { get; set; }
|
||||
|
||||
private string? Title { get; set; }
|
||||
private string? ConfirmCaption { get; set; }
|
||||
private string? SecondaryCaption { get; set; }
|
||||
private string? Dismiss { get; set; }
|
||||
private List<FieldDefinition>? Fields { get; set; }
|
||||
private FieldDefinition? DeviceField { get; set; }
|
||||
private Func<Task>? NextAction;
|
||||
private Func<Task>? SecondaryAction;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// NextAction = FirstStepExit;
|
||||
// Title = "Access Service Menu";
|
||||
// ConfirmCaption = "Next >";
|
||||
// Fields = [AddField("Service Menu Access Type", ServiceMenuType.FactoryMenu, subLabel: "Both types will not reset your settings. 'FactoryMenu' is recommended for 2024 series but will also work for older ones. Click 'Next >' to continue or 'X' to stop")];
|
||||
}
|
||||
|
||||
protected async Task OnShow()
|
||||
{
|
||||
NextAction = FirstStepExit;
|
||||
Title = "Access Service Menu";
|
||||
ConfirmCaption = "Next >";
|
||||
SecondaryCaption = null;
|
||||
SecondaryAction = null;
|
||||
Dismiss = null;
|
||||
Fields = [AddField("Service Menu Access Type", ServiceMenuType.FactoryMenu, subLabel: "Both types will not reset your settings. 'FactoryMenu' is recommended for 2024 series but will also work for older ones. Click 'Next >' to continue or 'X' to stop")];
|
||||
|
||||
if (Devices?.Any() == true)
|
||||
{
|
||||
DeviceField = FieldDefinition.CreateDropDownField("Device", Devices.Select(d => d.ToString()).ToList());
|
||||
DeviceField.Value = Devices.FirstOrDefault(d => d.IsSelected)?.ToString();
|
||||
|
||||
Fields.Insert(0, DeviceField);
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task NextClick()
|
||||
{
|
||||
if (NextAction == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await NextAction();
|
||||
}
|
||||
|
||||
private async Task SecondaryClick()
|
||||
{
|
||||
if (SecondaryAction != null)
|
||||
{
|
||||
await SecondaryAction();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task FirstStepExit()
|
||||
{
|
||||
if (Fields == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var menuType = Fields.First(f => f.ValueType == typeof(ServiceMenuType)).ValueAsEnum<ServiceMenuType>();
|
||||
|
||||
if (menuType == ServiceMenuType.FactoryMenu)
|
||||
{
|
||||
await FactoryMenuFirstStepEnter();
|
||||
}
|
||||
else
|
||||
{
|
||||
await HospitalityMenuFirstStepEnter();
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task FactoryMenuFirstStepEnter()
|
||||
{
|
||||
await ExecutePreset(SamsungFactoryMenuPresets.Preset1);
|
||||
|
||||
NextAction = FactoryMenuSecondStepEnter;
|
||||
Title = "Access Service Menu - Step 2";
|
||||
ConfirmCaption = "Next >";
|
||||
SecondaryCaption = "Skip";
|
||||
SecondaryAction = FactoryMenuSecondStepEnterWithoutExecute;
|
||||
Fields = [AddField("Service Menu - Advanced", null, FieldType.Label, "If the service menu opened and you want to access the Advanced settings, click 'Next >' or click 'Skip' to skip")];
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
protected async Task FactoryMenuSecondStepEnterWithoutExecute()
|
||||
{
|
||||
NextAction = FactoryMenuSecondStepExit;
|
||||
Title = "Exit Service Menu";
|
||||
ConfirmCaption = "Next >";
|
||||
SecondaryCaption = null;
|
||||
SecondaryAction = null;
|
||||
Fields = [AddField("Exit Service Menu Type", ExitServiceMenuType.Exit, subLabel: "Choose how to exit the service menu. You can either reboot the TV or just exit the service menu (if, for example, no changes have been made)")];
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
protected async Task FactoryMenuSecondStepEnter()
|
||||
{
|
||||
await ExecutePreset(SamsungFactoryMenuPresets.PresetAdvanced);
|
||||
|
||||
await FactoryMenuSecondStepEnterWithoutExecute();
|
||||
}
|
||||
|
||||
protected async Task FactoryMenuSecondStepExit()
|
||||
{
|
||||
if (Fields == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var menuType = Fields.First().ValueAsEnum<ExitServiceMenuType>();
|
||||
|
||||
if (menuType == ExitServiceMenuType.Exit)
|
||||
{
|
||||
await ExitServiceMenuEnter();
|
||||
}
|
||||
else
|
||||
{
|
||||
await RebootTvEnter();
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task ExitServiceMenuEnter()
|
||||
{
|
||||
await ExecutePreset(SamsungFactoryMenuPresets.Exit);
|
||||
|
||||
NextAction = null;
|
||||
Title = "Exited Service Menu";
|
||||
Dismiss = "modal";
|
||||
ConfirmCaption = "OK";
|
||||
SecondaryCaption = "Factory";
|
||||
SecondaryAction = ExecuteFactoryKey;
|
||||
Fields = [AddField("Service Menu - Exited", null, FieldType.Label, "Exited the service menu. Click OK to close. If the TV is stuck on cycling full screen RGB images, use the 'Factory' button below.")];
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
protected async Task RebootTvEnter()
|
||||
{
|
||||
await ExecutePreset(SamsungGenericPresets.Reboot);
|
||||
|
||||
NextAction = null;
|
||||
Title = "Rebooted TV";
|
||||
Dismiss = "modal";
|
||||
ConfirmCaption = "OK";
|
||||
Fields = [AddField("Service Menu - Rebooted TV", null, FieldType.Label, "Rebooted the TV. Power on the TV manually if the TV does not come on automatically.")];
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
protected async Task HospitalityMenuFirstStepEnter()
|
||||
{
|
||||
await ExecutePreset(SamsungHospitalityMenuPresets.Preset1);
|
||||
|
||||
NextAction = HospitalityMenuSecondStepEnter;
|
||||
Title = "Access Service Menu - Step 2";
|
||||
ConfirmCaption = "Next >";
|
||||
Fields = [AddField("Service Menu opened?", null, FieldType.Label, "If the menu is opened with the 'Hospitality Mode' item highlighted and the rest disabled, click 'Next >' to continue or 'X' to stop")];
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
protected async Task HospitalityMenuSecondStepEnter()
|
||||
{
|
||||
await ExecutePreset(SamsungHospitalityMenuPresets.Preset2);
|
||||
|
||||
NextAction = HospitalityMenuThirdStepEnter;
|
||||
Title = "Access Service Menu - Step 3";
|
||||
ConfirmCaption = "Next >";
|
||||
SecondaryCaption = "Skip";
|
||||
Fields = [AddField("Service Menu - Advanced", null, FieldType.Label, "If the 'Advanced' item is highlighted and you want to access these settings, click 'Next >' or click 'Skip' to skip")];
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
protected async Task HospitalityMenuThirdStepEnter()
|
||||
{
|
||||
await ExecutePreset(SamsungHospitalityMenuPresets.Preset3);
|
||||
|
||||
await FactoryMenuSecondStepEnterWithoutExecute();
|
||||
}
|
||||
|
||||
protected async Task ExecuteFactoryKey()
|
||||
{
|
||||
await ExecutePreset(SamsungFactoryMenuPresets.KeyFactory);
|
||||
}
|
||||
|
||||
private async Task ExecutePreset(SamsungPreset preset)
|
||||
{
|
||||
if (Devices != null && DeviceField?.Value != null)
|
||||
{
|
||||
var device = Devices.FirstOrDefault(d => d.ToString() == DeviceField.Value.ToString());
|
||||
preset.DeviceMacAddress = device?.MacAddress;
|
||||
}
|
||||
|
||||
await _rpcClientService.CallAsync<bool>("SamsungService", "ApplyPreset", preset);
|
||||
//await Task.Delay(2000);
|
||||
}
|
||||
|
||||
private FieldDefinition AddField(string label, object? value, FieldType fieldType = FieldType.DropDown, string? subLabel = null)
|
||||
{
|
||||
return new FieldDefinition
|
||||
{
|
||||
FieldType = fieldType,
|
||||
Value = value,
|
||||
StringValue = value?.ToString(),
|
||||
ValueType = value?.GetType(),
|
||||
Label = label,
|
||||
SubLabel = subLabel
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.Samsung
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.Shared.Common;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
|
||||
@rendermode InteractiveServer
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
|
||||
<FieldsModal Title="Samsung settings" ConfirmCaption="Save" ModalId="settingsModal" Fields="Fields" OnConfirm="ApplyClick" OnShow="ModalOnShow"></FieldsModal>
|
||||
|
||||
@code {
|
||||
private SamsungServiceConfigDto? Config { get; set; }
|
||||
private List<FieldDefinition>? Fields { get; set; }
|
||||
|
||||
protected async Task ModalOnShow()
|
||||
{
|
||||
Config = await _rpcClientService.CallAsync<SamsungServiceConfigDto>("SamsungService", "GetConfig");
|
||||
Fields = SamsungFields.GetSettingsFields(Config);
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task ApplyClick()
|
||||
{
|
||||
if (Config == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FieldDefinition.UpdateObject(Fields, Config);
|
||||
|
||||
var result = await _rpcClientService.CallAsync<bool>("SamsungService", "UpdateConfig", Config);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
@page "/Samsung"
|
||||
|
||||
@using ColorControl.Shared.Common
|
||||
@using ColorControl.Shared.Contracts
|
||||
@using ColorControl.Shared.Contracts.NVIDIA
|
||||
@using ColorControl.Shared.Contracts.Samsung
|
||||
@using ColorControl.Shared.Services;
|
||||
@using ColorControl.UI.Components.Pages.Generic
|
||||
@using ColorControl.UI.Generics
|
||||
@using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject RpcUiClientService _rpcClientService
|
||||
@inject ProtectedLocalStorage _localStorage
|
||||
|
||||
<PageTitle>Samsung controller</PageTitle>
|
||||
|
||||
<SamsungSettings />
|
||||
<SamsungServiceMenu Devices="Devices"></SamsungServiceMenu>
|
||||
|
||||
<div class="d-flex align-items-center">
|
||||
<h1>Samsung controller</h1>
|
||||
<div class="d-flex ms-auto">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-sm btn-primary dropdown-toggle me-2" data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside">
|
||||
Buttons
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
@foreach (var button in Enum.GetValues<ButtonType>())
|
||||
{
|
||||
<li>
|
||||
<a class="dropdown-item" @onclick="() => ButtonOnClick(button)">@button.GetDescription()</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
@if (InvokableActions != null)
|
||||
{
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-sm btn-primary dropdown-toggle me-2" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Expert
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
@foreach (var action in InvokableActions)
|
||||
{
|
||||
if (action.Name == "ServiceMenu")
|
||||
{
|
||||
<li><a class="dropdown-item" data-bs-toggle="modal" data-bs-target="#serviceMenuModal">Service menu</a></li>
|
||||
}
|
||||
else
|
||||
{
|
||||
<li><a class="dropdown-item" @onclick="() => ActionOnclick(action)">@action.Name</a></li>
|
||||
}
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#settingsModal">
|
||||
Settings
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SamsungDevicePage Device="CurrentDevice" AfterApply="AfterDeviceApplied" />
|
||||
<SamsungPresetPage PresetParam="CurrentPreset" Devices="Devices" AfterApply="AfterPresetApplied" />
|
||||
<ConfirmModal Title="Delete preset" Message="Are you sure you want to delete the preset?" OnConfirm="PresetOnDeleteConfirmed" />
|
||||
|
||||
@if (Devices == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mb-2 d-flex align-items-center">
|
||||
<h2>Devices</h2>
|
||||
<div class="d-flex ms-auto">
|
||||
<button type="button" class="btn btn-sm btn-primary me-2" disabled="@IsRefreshDisabled" @onclick="RefreshDevicesOnClick">
|
||||
@if (IsRefreshDisabled)
|
||||
{
|
||||
<span class="spinner-border spinner-border-sm me-1" aria-hidden="true"></span>
|
||||
}
|
||||
<span role="status">Refresh</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#deviceModal" @onclick="() => AddDeviceOnClick()">
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
@if (!Devices.Any())
|
||||
{
|
||||
<span>No devices found</span>
|
||||
}
|
||||
@foreach (var device in Devices)
|
||||
{
|
||||
<div class="col-lg-6 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-header d-flex align-items-baseline">
|
||||
<span>
|
||||
@device.Name
|
||||
@if (device.IsConnected)
|
||||
{
|
||||
<span class="ms-2 badge text-bg-success" title="Device is connected">Connected</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="ms-2 badge text-bg-danger" title="Device is disconnected">Disconnected</span>
|
||||
}
|
||||
@if (device.IsSelected)
|
||||
{
|
||||
<span class="ms-2 badge text-bg-primary" title="Global device">Primary</span>
|
||||
}
|
||||
</span>
|
||||
<div class="d-flex ms-auto">
|
||||
@if (!device.IsSelected)
|
||||
{
|
||||
<button class="btn btn-sm btn-secondary ms-2" @onclick="() => SetAsPrimaryOnClick(device)">Set as primary</button>
|
||||
}
|
||||
<button type="button" class="btn btn-sm btn-primary ms-2" data-bs-toggle="modal" data-bs-target="#deviceModal" @onclick="() => DeviceOnClick(device)">
|
||||
Settings
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<small class="text-muted">@device.IpAddress (@(device.MacAddress ?? "No MAC-address entered"))</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Presets == null)
|
||||
{
|
||||
<div>Loading...</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mb-2 d-flex align-items-center">
|
||||
<h2>Presets</h2>
|
||||
<div class="d-flex ms-auto">
|
||||
<select class="form-select me-2" id="presetOrder" @bind="PresetOrder" @bind:after="PresetOrderChanged">
|
||||
@foreach (var order in Enum.GetValues<PresetOrder>())
|
||||
{
|
||||
<option value="@order">Order by: @order.GetDescription()</option>
|
||||
}
|
||||
</select>
|
||||
<button type="button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#presetModal" @onclick="() => AddPresetOnClick()">
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<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>
|
||||
<div class="d-flex align-items-start justify-content-between">
|
||||
<div>
|
||||
<h5 class="mb-1">@preset.name <small class="text-muted">@GetDeviceName(preset)</small></h5>
|
||||
@if (preset.Triggers.Any(t => t.Trigger != PresetTriggerType.None))
|
||||
{
|
||||
<div><small class="text-muted">Trigger: @preset.Triggers[0].ToString()</small></div>
|
||||
}
|
||||
<div><small class="text-muted">@preset.shortcut</small></div>
|
||||
</div>
|
||||
<span>
|
||||
<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>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<SamsungPreset>? Presets;
|
||||
private List<SamsungDeviceDto>? Devices;
|
||||
private List<InvokableActionDto<SamsungPreset>>? InvokableActions;
|
||||
|
||||
private SamsungServiceConfigDto? Config;
|
||||
private SamsungPreset? CurrentPreset;
|
||||
private SamsungDeviceDto? CurrentDevice;
|
||||
private bool IsRefreshDisabled;
|
||||
|
||||
private PresetOrder PresetOrder;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
Config = await _rpcClientService.CallAsync<SamsungServiceConfigDto>("SamsungService", "GetConfig");
|
||||
await RefreshPresets();
|
||||
Devices = await _rpcClientService.CallAsync<List<SamsungDeviceDto>>("SamsungService", "GetDevices");
|
||||
InvokableActions = await _rpcClientService.CallAsync<List<InvokableActionDto<SamsungPreset>>>("SamsungService", "GetInvokableActions");
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstTime)
|
||||
{
|
||||
if (firstTime)
|
||||
{
|
||||
PresetOrder = await _localStorage.TryGet<PresetOrder>("SamsungPresetOrder");
|
||||
Presets = Presets?.OrderPresetsBy(PresetOrder);
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PresetOnClick(SamsungPreset preset)
|
||||
{
|
||||
await _rpcClientService.CallAsync<bool>("SamsungService", "ApplyPresetWithId", preset.id);
|
||||
}
|
||||
|
||||
private async Task PresetOnEditClick(SamsungPreset preset)
|
||||
{
|
||||
CurrentPreset = preset;
|
||||
}
|
||||
|
||||
private async Task PresetOnCloneClick(SamsungPreset preset)
|
||||
{
|
||||
CurrentPreset = new SamsungPreset(preset);
|
||||
CurrentPreset.SetCloneValues();
|
||||
}
|
||||
|
||||
private async Task PresetOnDeleteClick(SamsungPreset preset)
|
||||
{
|
||||
CurrentPreset = preset;
|
||||
}
|
||||
|
||||
private async Task PresetOnDeleteConfirmed()
|
||||
{
|
||||
if (CurrentPreset == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _rpcClientService.CallAsync<bool>("SamsungService", "DeletePreset", CurrentPreset.id);
|
||||
await RefreshPresets();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task DeviceOnClick(SamsungDeviceDto device)
|
||||
{
|
||||
CurrentDevice = device;
|
||||
}
|
||||
|
||||
private async Task AddDeviceOnClick()
|
||||
{
|
||||
CurrentDevice = new SamsungDeviceDto { IsCustom = true };
|
||||
}
|
||||
|
||||
private async Task AddPresetOnClick()
|
||||
{
|
||||
CurrentPreset = new SamsungPreset();
|
||||
}
|
||||
|
||||
private async Task AfterPresetApplied(SamsungPreset preset)
|
||||
{
|
||||
await RefreshPresets();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task AfterDeviceApplied(SamsungDeviceDto device)
|
||||
{
|
||||
Devices = await _rpcClientService.CallAsync<List<SamsungDeviceDto>>("SamsungService", "GetDevices");
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task RefreshDevicesOnClick(MouseEventArgs e)
|
||||
{
|
||||
IsRefreshDisabled = true;
|
||||
try
|
||||
{
|
||||
await _rpcClientService.CallAsync<bool>("SamsungService", "RefreshDevices");
|
||||
Devices = await _rpcClientService.CallAsync<List<SamsungDeviceDto>>("SamsungService", "GetDevices");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsRefreshDisabled = false;
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task ActionOnclick(InvokableActionDto<SamsungPreset> action)
|
||||
{
|
||||
await _rpcClientService.CallAsync<bool>("SamsungService", "ExecuteInvokableAction", action);
|
||||
}
|
||||
|
||||
private string GetDeviceName(SamsungPreset preset)
|
||||
{
|
||||
if (string.IsNullOrEmpty(preset.DeviceMacAddress))
|
||||
{
|
||||
return "Global";
|
||||
}
|
||||
|
||||
return Devices?.FirstOrDefault(d => d.MacAddress == preset.DeviceMacAddress)?.Name ?? preset.DeviceMacAddress;
|
||||
}
|
||||
|
||||
private async Task SetAsPrimaryOnClick(SamsungDeviceDto device)
|
||||
{
|
||||
var result = await _rpcClientService.CallAsync<bool>("SamsungService", "SetSelectedDevice", device);
|
||||
|
||||
if (result)
|
||||
{
|
||||
Devices = await _rpcClientService.CallAsync<List<SamsungDeviceDto>>("SamsungService", "GetDevices");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PresetOrderChanged()
|
||||
{
|
||||
await _localStorage.SetAsync("SamsungPresetOrder", PresetOrder);
|
||||
await RefreshPresets();
|
||||
}
|
||||
|
||||
private async Task RefreshPresets()
|
||||
{
|
||||
Presets = await _rpcClientService.CallAsync<List<SamsungPreset>>("SamsungService", "GetPresets");
|
||||
|
||||
Presets = Presets?.OrderPresetsBy(PresetOrder);
|
||||
}
|
||||
|
||||
private async Task ButtonOnClick(ButtonType button)
|
||||
{
|
||||
var preset = new SamsungPreset(button.ToString(), null, [button.ToString()]);
|
||||
|
||||
await _rpcClientService.CallAsync<bool>("SamsungService", "ApplyPreset", preset);
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
@page "/weather"
|
||||
@attribute [StreamRendering]
|
||||
|
||||
<PageTitle>Weather</PageTitle>
|
||||
|
||||
<h1>Weather</h1>
|
||||
|
||||
<p>This component demonstrates showing data.</p>
|
||||
|
||||
@if (forecasts == null)
|
||||
{
|
||||
<p><em>Loading...</em></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Temp. (C)</th>
|
||||
<th>Temp. (F)</th>
|
||||
<th>Summary</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var forecast in forecasts)
|
||||
{
|
||||
<tr>
|
||||
<td>@forecast.Date.ToShortDateString()</td>
|
||||
<td>@forecast.TemperatureC</td>
|
||||
<td>@forecast.TemperatureF</td>
|
||||
<td>@forecast.Summary</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
@code {
|
||||
private WeatherForecast[]? forecasts;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Simulate asynchronous loading to demonstrate streaming rendering
|
||||
await Task.Delay(500);
|
||||
|
||||
var startDate = DateOnly.FromDateTime(DateTime.Now);
|
||||
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
|
||||
forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
|
||||
{
|
||||
Date = startDate.AddDays(index),
|
||||
TemperatureC = Random.Shared.Next(-20, 55),
|
||||
Summary = summaries[Random.Shared.Next(summaries.Length)]
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
private class WeatherForecast
|
||||
{
|
||||
public DateOnly Date { get; set; }
|
||||
public int TemperatureC { get; set; }
|
||||
public string? Summary { get; set; }
|
||||
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user