feat(ui): PowerUse-Einstellungen nach Settings -> Ausfuehrung verschieben
Schalter, Zeitfenster und Handoff-Prozent lagen im Usage-Monitor und mussten in SettingsModalViewModel nur load->save durchgeschleift werden, damit ein Settings-Speichern sie nicht zurueckwirft. Jetzt gehoeren sie dem General-Tab, der Monitor ist wieder nur Anzeige + Threshold-Drag, und "Jetzt aktualisieren" sitzt als Icon in der Titelleiste (neuer ModalShell.HeaderActions-Slot).
This commit is contained in:
@@ -49,8 +49,8 @@ public sealed partial class ListSettingsModalViewModel : ViewModelBase
|
||||
// lands; a non-zero exit keeps the task out of Done instead of silently reporting merged.
|
||||
[ObservableProperty] private string _verifyCommand = "";
|
||||
// Per-list opt-in for PowerUse. A handler run on this list only goes unattended (no questions,
|
||||
// harvests new work, longer handoff chain) when this AND the global switch in the usage monitor
|
||||
// are both on. Off by default — the global switch alone must never change a list's behaviour.
|
||||
// harvests new work, longer handoff chain) when this AND the global switch in Settings →
|
||||
// Execution are both on. Off by default — the global switch alone must never change a list's behaviour.
|
||||
[ObservableProperty] private bool _powerUseAllowed;
|
||||
// Dropdown hidden entirely when no ticket-system base URL is configured (Settings → Files tab).
|
||||
// Icon key into the UI icon library (IconKeyConverter -> "Icon.<key>" StreamGeometry).
|
||||
|
||||
@@ -30,6 +30,15 @@ public sealed partial class GeneralSettingsTabViewModel : ViewModelBase
|
||||
// Cap on a list-handler run's wait<->merge handoff chain. 4 = two merge rounds (the long-standing
|
||||
// default); raise it for runs meant to keep going for hours. Bounds a runaway chain, so never 0.
|
||||
[ObservableProperty] private int _listHandlerMaxHandoffs = 4;
|
||||
// PowerUse master switch: a handler run spends the rest of the window unattended. On its own it
|
||||
// changes nothing — the run's LIST must also opt in (list settings → PowerUse).
|
||||
[ObservableProperty] private bool _powerUseEnabled;
|
||||
// Optional local-time window (HH:mm) narrowing when PowerUse applies; both blank = round the
|
||||
// clock. Unparseable input is normalized to null on save, same as the worker does.
|
||||
[ObservableProperty] private string? _powerUseWindowStart;
|
||||
[ObservableProperty] private string? _powerUseWindowEnd;
|
||||
// 5h-usage percentage at which a PowerUse handler session hands off. Clamped 50..99.
|
||||
[ObservableProperty] private int _powerUseHandoffPct = 95;
|
||||
// Newline-separated path prefixes excluded from the weekly report.
|
||||
[ObservableProperty] private string _reportExcludedPaths = @"C:\Private";
|
||||
// 0=Sunday..6=Saturday (System.DayOfWeek); default Wednesday.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Linq;
|
||||
using ClaudeDo.Data;
|
||||
using ClaudeDo.Data.Models;
|
||||
using ClaudeDo.Localization;
|
||||
using ClaudeDo.Ui.Localization;
|
||||
using ClaudeDo.Ui.Services;
|
||||
@@ -130,14 +131,6 @@ public sealed partial class SettingsModalViewModel : ViewModelBase
|
||||
// carried through load→save verbatim so saving Settings can never reset a dragged value.
|
||||
private (int FiveSoft, int FiveHard, int SevenSoft, int SevenHard) _throttleStages = (50, 65, 50, 65);
|
||||
|
||||
// Same deal: the PowerUse master switch is toggled on the usage monitor. Carried load→save so
|
||||
// opening and saving Settings can't switch it off behind the user's back.
|
||||
private bool _powerUseEnabled;
|
||||
|
||||
// Ditto for the PowerUse time window and handoff threshold — usage-monitor-owned, invisible
|
||||
// here, but they must survive a Settings save.
|
||||
private (string? Start, string? End, int HandoffPct) _powerUseWindow = (null, null, 95);
|
||||
|
||||
public async Task LoadAsync()
|
||||
{
|
||||
IsBusy = true;
|
||||
@@ -156,11 +149,10 @@ public sealed partial class SettingsModalViewModel : ViewModelBase
|
||||
General.UsageGateSevenDayPct = dto.UsageGateSevenDayPct;
|
||||
General.AutoContinueOnUsageLimit = dto.AutoContinueOnUsageLimit;
|
||||
General.ListHandlerMaxHandoffs = dto.ListHandlerMaxHandoffs;
|
||||
// Owned by the usage monitor, not editable here — but it MUST be round-tripped,
|
||||
// because SaveAsync rebuilds the whole DTO positionally and would otherwise write
|
||||
// the record default (false) back and silently switch PowerUse off.
|
||||
_powerUseEnabled = dto.PowerUseEnabled;
|
||||
_powerUseWindow = (dto.PowerUseWindowStart, dto.PowerUseWindowEnd, dto.PowerUseHandoffPct);
|
||||
General.PowerUseEnabled = dto.PowerUseEnabled;
|
||||
General.PowerUseWindowStart = dto.PowerUseWindowStart;
|
||||
General.PowerUseWindowEnd = dto.PowerUseWindowEnd;
|
||||
General.PowerUseHandoffPct = dto.PowerUseHandoffPct;
|
||||
_throttleStages = (
|
||||
dto.UsageThrottleFiveHourSoftPct, dto.UsageThrottleFiveHourHardPct,
|
||||
dto.UsageThrottleSevenDaySoftPct, dto.UsageThrottleSevenDayHardPct);
|
||||
@@ -228,11 +220,13 @@ public sealed partial class SettingsModalViewModel : ViewModelBase
|
||||
_throttleStages.SevenSoft,
|
||||
_throttleStages.SevenHard,
|
||||
General.AutoContinueOnUsageLimit,
|
||||
_powerUseEnabled,
|
||||
General.PowerUseEnabled,
|
||||
General.ListHandlerMaxHandoffs,
|
||||
_powerUseWindow.Start,
|
||||
_powerUseWindow.End,
|
||||
_powerUseWindow.HandoffPct);
|
||||
// The worker stores an unparseable time as null; normalize here too so the box
|
||||
// shows what was actually saved instead of a typo that looks like a live window.
|
||||
PowerUseWindow.Normalize(General.PowerUseWindowStart),
|
||||
PowerUseWindow.Normalize(General.PowerUseWindowEnd),
|
||||
Math.Clamp(General.PowerUseHandoffPct, 50, 99));
|
||||
await _worker.UpdateAppSettingsAsync(dto);
|
||||
await Prime.SaveAsync();
|
||||
await OnlineInbox.SaveAsync();
|
||||
|
||||
@@ -2,7 +2,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using ClaudeDo.Data.Models;
|
||||
using ClaudeDo.Ui.Localization;
|
||||
using ClaudeDo.Ui.Services;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
@@ -101,77 +100,6 @@ public sealed partial class UsageMonitorModalViewModel : ViewModelBase
|
||||
[ObservableProperty]
|
||||
private IReadOnlyList<UsageGaugeRowViewModel> _gaugeRows = Array.Empty<UsageGaugeRowViewModel>();
|
||||
|
||||
/// <summary>
|
||||
/// PowerUse master switch. It lives here rather than in Settings because turning it on is a
|
||||
/// "spend the rest of the window" decision, made while looking at how much window is left.
|
||||
/// On its own it changes nothing — a list-handler run only goes unattended when its LIST also
|
||||
/// opted in (list settings → PowerUse), so this can stay on without affecting other lists.
|
||||
/// </summary>
|
||||
[ObservableProperty] private bool _powerUseEnabled;
|
||||
|
||||
/// <summary>Optional local-time window (<c>HH:mm</c>) narrowing when PowerUse applies. Both
|
||||
/// blank = round the clock. Committed on focus loss / Enter, not per keystroke.</summary>
|
||||
[ObservableProperty] private string? _powerUseWindowStart;
|
||||
[ObservableProperty] private string? _powerUseWindowEnd;
|
||||
|
||||
/// <summary>5h-usage percentage at which a PowerUse handler session should hand off.</summary>
|
||||
[ObservableProperty] private int _powerUseHandoffPct = 95;
|
||||
|
||||
// Set while LoadAsync writes the stored value in, so seeding the property doesn't immediately
|
||||
// write it straight back to the worker.
|
||||
private bool _loadingPowerUse;
|
||||
|
||||
partial void OnPowerUseEnabledChanged(bool value)
|
||||
{
|
||||
if (_loadingPowerUse) return;
|
||||
_ = SavePowerUse();
|
||||
}
|
||||
|
||||
/// Read-modify-write, same discipline as SaveStagesAsync: never clobber a field this modal
|
||||
/// doesn't own. All four PowerUse fields go in one write, so committing one box can't drop a
|
||||
/// value the user just typed into a sibling box.
|
||||
[RelayCommand]
|
||||
private async Task SavePowerUse()
|
||||
{
|
||||
if (_loadingPowerUse) return;
|
||||
try
|
||||
{
|
||||
var settings = await _worker.GetAppSettingsAsync();
|
||||
if (settings is null)
|
||||
{
|
||||
ErrorReported?.Invoke(Loc.T("vm.usageMonitor.powerUseSaveFailed", Loc.T("vm.settingsModal.workerOffline")));
|
||||
return;
|
||||
}
|
||||
|
||||
// The worker stores an unparseable time as null; normalize here too so the box shows
|
||||
// what was actually saved instead of a typo that looks like a live window.
|
||||
var start = PowerUseWindow.Normalize(PowerUseWindowStart);
|
||||
var end = PowerUseWindow.Normalize(PowerUseWindowEnd);
|
||||
var pct = Math.Clamp(PowerUseHandoffPct, 50, 99);
|
||||
|
||||
await _worker.UpdateAppSettingsAsync(settings with
|
||||
{
|
||||
PowerUseEnabled = PowerUseEnabled,
|
||||
PowerUseWindowStart = start,
|
||||
PowerUseWindowEnd = end,
|
||||
PowerUseHandoffPct = pct,
|
||||
});
|
||||
|
||||
_loadingPowerUse = true;
|
||||
try
|
||||
{
|
||||
PowerUseWindowStart = start;
|
||||
PowerUseWindowEnd = end;
|
||||
PowerUseHandoffPct = pct;
|
||||
}
|
||||
finally { _loadingPowerUse = false; }
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ErrorReported?.Invoke(Loc.T("vm.usageMonitor.powerUseSaveFailed", ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsStale => Snapshot?.IsStale == true;
|
||||
public string? LastError => Snapshot?.LastError;
|
||||
public bool IsGateBlocked => Snapshot?.IsGateBlocked == true;
|
||||
@@ -221,20 +149,6 @@ public sealed partial class UsageMonitorModalViewModel : ViewModelBase
|
||||
_worker.UsageUpdatedEvent -= OnUsageUpdated;
|
||||
_worker.UsageUpdatedEvent += OnUsageUpdated;
|
||||
|
||||
var settings = await _worker.GetAppSettingsAsync();
|
||||
if (settings is not null)
|
||||
{
|
||||
_loadingPowerUse = true;
|
||||
try
|
||||
{
|
||||
PowerUseEnabled = settings.PowerUseEnabled;
|
||||
PowerUseWindowStart = settings.PowerUseWindowStart;
|
||||
PowerUseWindowEnd = settings.PowerUseWindowEnd;
|
||||
PowerUseHandoffPct = settings.PowerUseHandoffPct;
|
||||
}
|
||||
finally { _loadingPowerUse = false; }
|
||||
}
|
||||
|
||||
ApplyPresetRange(SelectedPresetDays);
|
||||
await LoadUsageDataAsync();
|
||||
}
|
||||
|
||||
@@ -14,19 +14,21 @@
|
||||
Background="{DynamicResource DeepBrush}"
|
||||
BorderBrush="{DynamicResource LineBrush}"
|
||||
BorderThickness="0,0,0,1">
|
||||
<Grid ColumnDefinitions="*,Auto,Auto" Margin="14,0">
|
||||
<Grid ColumnDefinitions="*,Auto,Auto,Auto" Margin="14,0">
|
||||
<TextBlock Text="{TemplateBinding Title}"
|
||||
FontFamily="{DynamicResource MonoFont}"
|
||||
FontSize="{DynamicResource FontSizeMono}"
|
||||
LetterSpacing="1.4"
|
||||
Foreground="{DynamicResource TextBrush}"
|
||||
VerticalAlignment="Center"/>
|
||||
<Button Name="PART_MaxButton" Grid.Column="1" Classes="icon-btn"
|
||||
<ContentPresenter Grid.Column="1" Content="{TemplateBinding HeaderActions}"
|
||||
VerticalAlignment="Center" Margin="0,0,8,0"/>
|
||||
<Button Name="PART_MaxButton" Grid.Column="2" Classes="icon-btn"
|
||||
IsVisible="{TemplateBinding ShowMaximizeButton}"
|
||||
VerticalAlignment="Center">
|
||||
<PathIcon Name="PART_MaxIcon" Data="{StaticResource Icon.WinMax}" Width="10" Height="10"/>
|
||||
</Button>
|
||||
<Button Grid.Column="2" Classes="icon-btn" Content="✕"
|
||||
<Button Grid.Column="3" Classes="icon-btn" Content="✕"
|
||||
FontSize="{DynamicResource FontSizeBody}"
|
||||
Command="{TemplateBinding CloseCommand}"
|
||||
VerticalAlignment="Center"/>
|
||||
|
||||
@@ -17,6 +17,11 @@ public class ModalShell : ContentControl
|
||||
public static readonly StyledProperty<object?> FooterProperty =
|
||||
AvaloniaProperty.Register<ModalShell, object?>(nameof(Footer));
|
||||
|
||||
// Window-level actions rendered in the titlebar, left of maximise/close. Keeps a single
|
||||
// always-visible action (e.g. the usage monitor's refresh) out of the body's vertical flow.
|
||||
public static readonly StyledProperty<object?> HeaderActionsProperty =
|
||||
AvaloniaProperty.Register<ModalShell, object?>(nameof(HeaderActions));
|
||||
|
||||
public static readonly StyledProperty<ICommand?> CloseCommandProperty =
|
||||
AvaloniaProperty.Register<ModalShell, ICommand?>(nameof(CloseCommand));
|
||||
|
||||
@@ -27,6 +32,7 @@ public class ModalShell : ContentControl
|
||||
|
||||
public string? Title { get => GetValue(TitleProperty); set => SetValue(TitleProperty, value); }
|
||||
public object? Footer { get => GetValue(FooterProperty); set => SetValue(FooterProperty, value); }
|
||||
public object? HeaderActions { get => GetValue(HeaderActionsProperty); set => SetValue(HeaderActionsProperty, value); }
|
||||
public ICommand? CloseCommand { get => GetValue(CloseCommandProperty); set => SetValue(CloseCommandProperty, value); }
|
||||
public bool ShowMaximizeButton { get => GetValue(ShowMaximizeButtonProperty); set => SetValue(ShowMaximizeButtonProperty, value); }
|
||||
|
||||
|
||||
@@ -252,8 +252,8 @@
|
||||
<TextBlock Text="{loc:Tr settings.general.autoContinueOnUsageLimitHint}"
|
||||
Opacity="0.6" FontSize="12" TextWrapping="Wrap"/>
|
||||
|
||||
<!-- PowerUse itself is toggled on the usage monitor; only its handoff budget is
|
||||
typed here, because it also applies to a normal (interactive) handler run. -->
|
||||
<!-- Outside the PowerUse section below on purpose: the handoff budget also bounds
|
||||
a normal (interactive) handler run. -->
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Classes="eyebrow" Text="{loc:Tr settings.general.listHandlerMaxHandoffs}"/>
|
||||
<NumericUpDown Value="{Binding General.ListHandlerMaxHandoffs, Mode=TwoWay, Converter={StaticResource KeepLastNumber}}"
|
||||
@@ -264,6 +264,34 @@
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- PowerUse: a master switch plus its time window. Never sufficient on its own —
|
||||
the run's list must also allow PowerUse in its list settings. -->
|
||||
<StackPanel Spacing="4">
|
||||
<TextBlock Classes="field-label" Text="{loc:Tr settings.general.powerUseHeading}"/>
|
||||
<CheckBox IsChecked="{Binding General.PowerUseEnabled, Mode=TwoWay}"
|
||||
Content="{loc:Tr settings.general.powerUse}"/>
|
||||
<TextBlock Text="{loc:Tr settings.general.powerUseHint}"
|
||||
Opacity="0.6" FontSize="12" TextWrapping="Wrap"/>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8" Margin="0,6,0,0">
|
||||
<TextBlock Classes="eyebrow" VerticalAlignment="Center"
|
||||
Text="{loc:Tr settings.general.powerUseWindow}"/>
|
||||
<TextBox Width="70" FontSize="12" Padding="6,2"
|
||||
Watermark="{loc:Tr settings.general.powerUseWindowPlaceholder}"
|
||||
Text="{Binding General.PowerUseWindowStart, Mode=TwoWay}"/>
|
||||
<TextBlock Classes="meta" VerticalAlignment="Center" Text="–"/>
|
||||
<TextBox Width="70" FontSize="12" Padding="6,2"
|
||||
Watermark="{loc:Tr settings.general.powerUseWindowPlaceholder}"
|
||||
Text="{Binding General.PowerUseWindowEnd, Mode=TwoWay}"/>
|
||||
<TextBlock Classes="eyebrow" VerticalAlignment="Center" Margin="12,0,0,0"
|
||||
Text="{loc:Tr settings.general.powerUseHandoffPct}"/>
|
||||
<NumericUpDown Width="80" FontSize="12" Padding="6,2" ShowButtonSpinner="False"
|
||||
Minimum="50" Maximum="99" Increment="1" FormatString="0"
|
||||
Value="{Binding General.PowerUseHandoffPct, Mode=TwoWay, Converter={StaticResource KeepLastNumber}}"/>
|
||||
</StackPanel>
|
||||
<TextBlock Text="{loc:Tr settings.general.powerUseWindowHint}"
|
||||
Opacity="0.6" FontSize="12" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Throttle stages live on the usage-monitor gauges (drag + spinner per stage);
|
||||
echoed read-only here so they aren't invisible on the tab that owns the gate. -->
|
||||
<StackPanel Spacing="4">
|
||||
|
||||
@@ -19,6 +19,18 @@
|
||||
</Window.KeyBindings>
|
||||
|
||||
<ctl:ModalShell Title="{loc:Tr modals.usageMonitor.title}" CloseCommand="{Binding CloseCommand}">
|
||||
<!-- Refresh sits in the titlebar: the worker polls on a slow cadence to avoid the endpoint's
|
||||
429s, so this is the way to get a number now — but it doesn't earn a row in the body. -->
|
||||
<ctl:ModalShell.HeaderActions>
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<Ellipse Classes="spinner" Width="12" Height="12" VerticalAlignment="Center"
|
||||
IsVisible="{Binding IsRefreshing}"/>
|
||||
<Button Classes="icon-btn" Content="⟳" FontSize="{DynamicResource FontSizeBody}"
|
||||
Command="{Binding RefreshCommand}"
|
||||
IsEnabled="{Binding !IsRefreshing}"
|
||||
ToolTip.Tip="{loc:Tr modals.usageMonitor.refreshHint}"/>
|
||||
</StackPanel>
|
||||
</ctl:ModalShell.HeaderActions>
|
||||
<DockPanel>
|
||||
|
||||
<!-- Stale / gate bands -->
|
||||
@@ -47,18 +59,6 @@
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Refresh: the worker polls on a slow cadence to avoid the endpoint's 429s -->
|
||||
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Spacing="8"
|
||||
Margin="20,12,20,0" VerticalAlignment="Center">
|
||||
<Button Classes="btn" Content="{loc:Tr modals.usageMonitor.refresh}"
|
||||
Command="{Binding RefreshCommand}"
|
||||
IsEnabled="{Binding !IsRefreshing}"/>
|
||||
<Ellipse Classes="spinner" Width="14" Height="14" VerticalAlignment="Center"
|
||||
IsVisible="{Binding IsRefreshing}"/>
|
||||
<TextBlock Classes="meta" VerticalAlignment="Center"
|
||||
Text="{loc:Tr modals.usageMonitor.refreshHint}"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Gauges -->
|
||||
<ItemsControl DockPanel.Dock="Top" Margin="20,12,20,0" ItemsSource="{Binding GaugeRows}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
@@ -134,42 +134,6 @@
|
||||
Text="{loc:Tr modals.usageMonitor.noGauges}"
|
||||
IsVisible="{Binding !GaugeRows.Count}"/>
|
||||
|
||||
<!-- PowerUse: sits under the gauges on purpose — "spend the rest of the window" is a
|
||||
decision made while looking at how much window is left. Saves immediately on toggle. -->
|
||||
<Border DockPanel.Dock="Top" Margin="20,12,20,0" Padding="12,10"
|
||||
Background="{DynamicResource DeepBrush}" BorderBrush="{DynamicResource LineBrush}"
|
||||
BorderThickness="1" CornerRadius="6">
|
||||
<StackPanel Spacing="4">
|
||||
<CheckBox IsChecked="{Binding PowerUseEnabled, Mode=TwoWay}"
|
||||
Content="{loc:Tr modals.usageMonitor.powerUse}"/>
|
||||
<TextBlock Classes="meta" TextWrapping="Wrap"
|
||||
Text="{loc:Tr modals.usageMonitor.powerUseHint}"/>
|
||||
<!-- Window + handoff threshold. Same commit-on-blur/Enter discipline as the gauge
|
||||
legend boxes: a hub write per keystroke would be one save per typed digit. -->
|
||||
<StackPanel Orientation="Horizontal" Spacing="8" Margin="0,6,0,0">
|
||||
<TextBlock Classes="meta" VerticalAlignment="Center"
|
||||
Text="{loc:Tr modals.usageMonitor.powerUseWindow}"/>
|
||||
<TextBox Width="70" FontSize="12" Padding="6,2"
|
||||
Watermark="{loc:Tr modals.usageMonitor.powerUseWindowPlaceholder}"
|
||||
Text="{Binding PowerUseWindowStart, Mode=TwoWay}"
|
||||
LostFocus="OnPowerUseBoxCommit" KeyDown="OnPowerUseBoxKeyDown"/>
|
||||
<TextBlock Classes="meta" VerticalAlignment="Center" Text="–"/>
|
||||
<TextBox Width="70" FontSize="12" Padding="6,2"
|
||||
Watermark="{loc:Tr modals.usageMonitor.powerUseWindowPlaceholder}"
|
||||
Text="{Binding PowerUseWindowEnd, Mode=TwoWay}"
|
||||
LostFocus="OnPowerUseBoxCommit" KeyDown="OnPowerUseBoxKeyDown"/>
|
||||
<TextBlock Classes="meta" VerticalAlignment="Center" Margin="12,0,0,0"
|
||||
Text="{loc:Tr modals.usageMonitor.powerUseHandoffPct}"/>
|
||||
<NumericUpDown Width="80" FontSize="12" Padding="6,2" ShowButtonSpinner="False"
|
||||
Minimum="50" Maximum="99" Increment="1" FormatString="0"
|
||||
Value="{Binding PowerUseHandoffPct, Mode=TwoWay, Converter={StaticResource KeepLastNumber}}"
|
||||
LostFocus="OnPowerUseBoxCommit" KeyDown="OnPowerUseBoxKeyDown"/>
|
||||
</StackPanel>
|
||||
<TextBlock Classes="meta" TextWrapping="Wrap" Opacity="0.75"
|
||||
Text="{loc:Tr modals.usageMonitor.powerUseWindowHint}"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Period picker -->
|
||||
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Spacing="8" Margin="20,12,20,0">
|
||||
<TextBlock Classes="meta" Text="{loc:Tr modals.usageMonitor.period}" VerticalAlignment="Center"/>
|
||||
|
||||
@@ -33,19 +33,4 @@ public partial class UsageMonitorModalView : Window
|
||||
OnStageBoxCommit(sender, e);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>Persists the PowerUse window / handoff threshold on blur — a save per keystroke
|
||||
/// would be one hub round-trip per typed digit.</summary>
|
||||
private void OnPowerUseBoxCommit(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is not UsageMonitorModalViewModel vm) return;
|
||||
if (vm.SavePowerUseCommand.CanExecute(null)) vm.SavePowerUseCommand.Execute(null);
|
||||
}
|
||||
|
||||
private void OnPowerUseBoxKeyDown(object? sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key != Key.Enter) return;
|
||||
OnPowerUseBoxCommit(sender, e);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user