Refactor configuration shortcut data

This commit is contained in:
Thorsten Sommer 2026-06-09 15:32:40 +02:00
parent 43840cd711
commit b4f3ddce4f
Signed by untrusted user who does not match committer: tsommer
GPG Key ID: 371BBA77A02C0108
5 changed files with 68 additions and 44 deletions

View File

@ -3,7 +3,7 @@
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudIcon Icon="@this.Icon" Color="@this.IconColor"/>
<MudText Typo="Typo.body1" Class="flex-grow-1">
@if (string.IsNullOrWhiteSpace(this.Shortcut()))
@if (string.IsNullOrWhiteSpace(this.Data.Value()))
{
@T("No shortcut configured")
}

View File

@ -1,5 +1,4 @@
using AIStudio.Dialogs;
using AIStudio.Tools.Rust;
using AIStudio.Tools.Services;
using Microsoft.AspNetCore.Components;
@ -19,40 +18,10 @@ public partial class ConfigurationShortcut : ConfigurationBaseCore
private RustService RustService { get; init; } = null!;
/// <summary>
/// The current shortcut value.
/// The shortcut binding data.
/// </summary>
[Parameter]
public Func<string> Shortcut { get; set; } = () => string.Empty;
/// <summary>
/// An action that is called when the shortcut was changed.
/// </summary>
[Parameter]
public Action<string> ShortcutUpdate { get; set; } = _ => { };
/// <summary>
/// The optional user-facing shortcut label.
/// </summary>
[Parameter]
public Func<string> ShortcutDisplayName { get; set; } = () => string.Empty;
/// <summary>
/// The canonical shortcut value the optional user-facing label belongs to.
/// </summary>
[Parameter]
public Func<string> ShortcutDisplaySource { get; set; } = () => string.Empty;
/// <summary>
/// An action that is called when the user-facing shortcut label was changed.
/// </summary>
[Parameter]
public Action<string, string> ShortcutDisplayUpdate { get; set; } = (_, _) => { };
/// <summary>
/// The name/identifier of the shortcut (used for conflict detection and registration).
/// </summary>
[Parameter]
public Shortcut ShortcutId { get; init; }
public ConfigurationShortcutData Data { get; set; } = ConfigurationShortcutData.Empty;
/// <summary>
/// The icon to display.
@ -78,12 +47,12 @@ public partial class ConfigurationShortcut : ConfigurationBaseCore
private string GetDisplayShortcut()
{
var shortcut = this.Shortcut();
var shortcut = this.Data.Value();
if (string.IsNullOrWhiteSpace(shortcut))
return string.Empty;
var shortcutDisplayName = this.ShortcutDisplayName();
var shortcutDisplaySource = this.ShortcutDisplaySource();
var shortcutDisplayName = this.Data.DisplayName();
var shortcutDisplaySource = this.Data.DisplaySource();
if (!string.IsNullOrWhiteSpace(shortcutDisplayName)
&& string.Equals(shortcutDisplaySource, shortcut, StringComparison.Ordinal))
{
@ -106,8 +75,8 @@ public partial class ConfigurationShortcut : ConfigurationBaseCore
{
var dialogParameters = new DialogParameters<ShortcutDialog>
{
{ x => x.InitialShortcut, this.Shortcut() },
{ x => x.ShortcutId, this.ShortcutId },
{ x => x.InitialShortcut, this.Data.Value() },
{ x => x.ShortcutId, this.Data.Id },
};
var dialogReference = await this.DialogService.ShowAsync<ShortcutDialog>(
@ -121,15 +90,15 @@ public partial class ConfigurationShortcut : ConfigurationBaseCore
if (dialogResult.Data is ShortcutDialogResult shortcutResult)
{
this.ShortcutUpdate(shortcutResult.Shortcut);
this.ShortcutDisplayUpdate(shortcutResult.DisplayName, shortcutResult.DisplaySource);
this.Data.ValueUpdate(shortcutResult.Shortcut);
this.Data.DisplayUpdate(shortcutResult.DisplayName, shortcutResult.DisplaySource);
await this.SettingsManager.StoreSettings();
await this.InformAboutChange();
}
else if (dialogResult.Data is string newShortcut)
{
this.ShortcutUpdate(newShortcut);
this.ShortcutDisplayUpdate(string.Empty, string.Empty);
this.Data.ValueUpdate(newShortcut);
this.Data.DisplayUpdate(string.Empty, string.Empty);
await this.SettingsManager.StoreSettings();
await this.InformAboutChange();
}

View File

@ -0,0 +1,44 @@
using AIStudio.Tools.Rust;
namespace AIStudio.Components;
/// <summary>
/// UI binding data for a configurable keyboard shortcut.
/// </summary>
public sealed class ConfigurationShortcutData
{
/// <summary>
/// Empty shortcut binding.
/// </summary>
public static ConfigurationShortcutData Empty { get; } = new();
/// <summary>
/// The name/identifier of the shortcut, used for conflict detection and registration.
/// </summary>
public Shortcut Id { get; init; } = Shortcut.NONE;
/// <summary>
/// The current shortcut value.
/// </summary>
public Func<string> Value { get; init; } = () => string.Empty;
/// <summary>
/// An action that is called when the shortcut was changed.
/// </summary>
public Action<string> ValueUpdate { get; init; } = _ => { };
/// <summary>
/// The optional user-facing shortcut label.
/// </summary>
public Func<string> DisplayName { get; init; } = () => string.Empty;
/// <summary>
/// The canonical shortcut value the optional user-facing label belongs to.
/// </summary>
public Func<string> DisplaySource { get; init; } = () => string.Empty;
/// <summary>
/// An action that is called when the user-facing shortcut label was changed.
/// </summary>
public Action<string, string> DisplayUpdate { get; init; } = (_, _) => { };
}

View File

@ -37,7 +37,7 @@
@if (PreviewFeatures.PRE_SPEECH_TO_TEXT_2026.IsEnabled(this.SettingsManager))
{
<ConfigurationSelect OptionDescription="@T("Select a transcription provider")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.UseTranscriptionProvider)" Data="@this.GetFilteredTranscriptionProviders()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.UseTranscriptionProvider = selectedValue)" OptionHelp="@T("Select a transcription provider for transcribing your voice. Without a selected provider, dictation and transcription features will be disabled.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.UseTranscriptionProvider, out var meta) && meta.IsLocked"/>
<ConfigurationShortcut ShortcutId="Shortcut.VOICE_RECORDING_TOGGLE" OptionDescription="@T("Voice recording shortcut")" Shortcut="@(() => this.SettingsManager.ConfigurationData.App.ShortcutVoiceRecording)" ShortcutUpdate="@(shortcut => this.SettingsManager.ConfigurationData.App.ShortcutVoiceRecording = shortcut)" ShortcutDisplayName="@(() => this.SettingsManager.ConfigurationData.App.ShortcutVoiceRecordingDisplayName)" ShortcutDisplaySource="@(() => this.SettingsManager.ConfigurationData.App.ShortcutVoiceRecordingDisplaySource)" ShortcutDisplayUpdate="@this.UpdateShortcutVoiceRecordingDisplay" OptionHelp="@T("The global keyboard shortcut for toggling voice recording. This shortcut works system-wide, even when the app is not focused.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.ShortcutVoiceRecording, out var meta) && meta.IsLocked"/>
<ConfigurationShortcut Data="@this.VoiceRecordingShortcut" OptionDescription="@T("Voice recording shortcut")" OptionHelp="@T("The global keyboard shortcut for toggling voice recording. This shortcut works system-wide, even when the app is not focused.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.ShortcutVoiceRecording, out var meta) && meta.IsLocked"/>
}
@if (this.SettingsManager.ConfigurationData.App.ShowAdminSettings)

View File

@ -1,11 +1,22 @@
using AIStudio.Provider;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Tools.Rust;
namespace AIStudio.Components.Settings;
public partial class SettingsPanelApp : SettingsPanelBase
{
private ConfigurationShortcutData VoiceRecordingShortcut => new()
{
Id = Shortcut.VOICE_RECORDING_TOGGLE,
Value = () => this.SettingsManager.ConfigurationData.App.ShortcutVoiceRecording,
ValueUpdate = shortcut => this.SettingsManager.ConfigurationData.App.ShortcutVoiceRecording = shortcut,
DisplayName = () => this.SettingsManager.ConfigurationData.App.ShortcutVoiceRecordingDisplayName,
DisplaySource = () => this.SettingsManager.ConfigurationData.App.ShortcutVoiceRecordingDisplaySource,
DisplayUpdate = this.UpdateShortcutVoiceRecordingDisplay,
};
private async Task GenerateEncryptionSecret()
{
var secret = EnterpriseEncryption.GenerateSecret();