2026-05-31 11:18:27 -04:00
|
|
|
|
using DragonISO.App.Services;
|
refactor(viewmodels): split MainViewModel into themed partial classes
MainViewModel.cs was 1017 lines and 45KB — most of it was bulk-operation
loops, Teams UIA plumbing, and the auto-apply-last-preset state machine
sitting on top of the actual MainViewModel surface (constructor, props,
OnStatsTick). Splits the class via partial-class into themed siblings:
* MainViewModel.cs (was 1017L → now 699L) — fields, properties,
constructor that wires every Command, OnStatsTick + Dispose. This
remains the thin aggregator.
* MainViewModel.TeamsCommands.cs (130L, new) — MakeTeamsCommand helper,
JoinPastedMeeting (body of JoinMeetingCommand), ExtractMeetingTitle
(already-tested static), PollTeamsMeetingState (the 1Hz UIA probe
formerly inlined in OnStatsTick).
* MainViewModel.PresetCommands.cs (108L, new) —
RequestApplyPresetOnStartup (CLI hook), LoadPendingPresetFromPreferences
(called by InitializeAsync), TryAutoApplyPendingPreset (the reconcile
step), and the _pendingPreset* private-field set that backs the path.
* MainViewModel.BulkCommands.cs (149L, new) — EnableAllOnlineAsync,
StopAllIsosAsync (with the default-No confirmation dialog),
SnapshotAll. RecordingCommands.cs from the original punch list is
intentionally absent — the recording surface was axed at 1d1ce6a;
what remains here is bulk-state ops across the participants
collection (note in the file header).
Why partial-class instead of helper-services or composed objects: every
extracted method touches the same private dispatcher / controller /
participants / toast state. Composing would require either passing
those references in (verbose call sites) or extracting them to a
shared private context object (boilerplate). Partial gives us
file-level separation without spreading the state contract.
ExtractMeetingTitle stays internal-static so the existing
MeetingTitleExtractionTests (10 cases) keep finding it. Build clean;
56 App + 104 Engine tests still pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:31:49 -04:00
|
|
|
|
|
2026-05-31 11:18:27 -04:00
|
|
|
|
namespace DragonISO.App.ViewModels;
|
refactor(viewmodels): split MainViewModel into themed partial classes
MainViewModel.cs was 1017 lines and 45KB — most of it was bulk-operation
loops, Teams UIA plumbing, and the auto-apply-last-preset state machine
sitting on top of the actual MainViewModel surface (constructor, props,
OnStatsTick). Splits the class via partial-class into themed siblings:
* MainViewModel.cs (was 1017L → now 699L) — fields, properties,
constructor that wires every Command, OnStatsTick + Dispose. This
remains the thin aggregator.
* MainViewModel.TeamsCommands.cs (130L, new) — MakeTeamsCommand helper,
JoinPastedMeeting (body of JoinMeetingCommand), ExtractMeetingTitle
(already-tested static), PollTeamsMeetingState (the 1Hz UIA probe
formerly inlined in OnStatsTick).
* MainViewModel.PresetCommands.cs (108L, new) —
RequestApplyPresetOnStartup (CLI hook), LoadPendingPresetFromPreferences
(called by InitializeAsync), TryAutoApplyPendingPreset (the reconcile
step), and the _pendingPreset* private-field set that backs the path.
* MainViewModel.BulkCommands.cs (149L, new) — EnableAllOnlineAsync,
StopAllIsosAsync (with the default-No confirmation dialog),
SnapshotAll. RecordingCommands.cs from the original punch list is
intentionally absent — the recording surface was axed at 1d1ce6a;
what remains here is bulk-state ops across the participants
collection (note in the file header).
Why partial-class instead of helper-services or composed objects: every
extracted method touches the same private dispatcher / controller /
participants / toast state. Composing would require either passing
those references in (verbose call sites) or extracting them to a
shared private context object (boilerplate). Partial gives us
file-level separation without spreading the state contract.
ExtractMeetingTitle stays internal-static so the existing
MeetingTitleExtractionTests (10 cases) keep finding it. Build clean;
56 App + 104 Engine tests still pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:31:49 -04:00
|
|
|
|
|
2026-05-31 11:18:27 -04:00
|
|
|
|
// Bulk operations that touch every (or every-enabled) participant —
|
refactor(viewmodels): split MainViewModel into themed partial classes
MainViewModel.cs was 1017 lines and 45KB — most of it was bulk-operation
loops, Teams UIA plumbing, and the auto-apply-last-preset state machine
sitting on top of the actual MainViewModel surface (constructor, props,
OnStatsTick). Splits the class via partial-class into themed siblings:
* MainViewModel.cs (was 1017L → now 699L) — fields, properties,
constructor that wires every Command, OnStatsTick + Dispose. This
remains the thin aggregator.
* MainViewModel.TeamsCommands.cs (130L, new) — MakeTeamsCommand helper,
JoinPastedMeeting (body of JoinMeetingCommand), ExtractMeetingTitle
(already-tested static), PollTeamsMeetingState (the 1Hz UIA probe
formerly inlined in OnStatsTick).
* MainViewModel.PresetCommands.cs (108L, new) —
RequestApplyPresetOnStartup (CLI hook), LoadPendingPresetFromPreferences
(called by InitializeAsync), TryAutoApplyPendingPreset (the reconcile
step), and the _pendingPreset* private-field set that backs the path.
* MainViewModel.BulkCommands.cs (149L, new) — EnableAllOnlineAsync,
StopAllIsosAsync (with the default-No confirmation dialog),
SnapshotAll. RecordingCommands.cs from the original punch list is
intentionally absent — the recording surface was axed at 1d1ce6a;
what remains here is bulk-state ops across the participants
collection (note in the file header).
Why partial-class instead of helper-services or composed objects: every
extracted method touches the same private dispatcher / controller /
participants / toast state. Composing would require either passing
those references in (verbose call sites) or extracting them to a
shared private context object (boilerplate). Partial gives us
file-level separation without spreading the state contract.
ExtractMeetingTitle stays internal-static so the existing
MeetingTitleExtractionTests (10 cases) keep finding it. Build clean;
56 App + 104 Engine tests still pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:31:49 -04:00
|
|
|
|
// Stop all ISOs, Enable all online, Snapshot all enabled frames.
|
|
|
|
|
|
// Split out of MainViewModel.cs so the main file isn't dominated by
|
|
|
|
|
|
// long async iteration loops.
|
|
|
|
|
|
//
|
|
|
|
|
|
// The RecordingCommands partial originally planned at this slot is
|
|
|
|
|
|
// intentionally absent: the recording surface was axed earlier in the
|
|
|
|
|
|
// May 2026 batch (see commit 1d1ce6a). What remains is bulk-state
|
|
|
|
|
|
// manipulation across the participants collection.
|
|
|
|
|
|
public sealed partial class MainViewModel
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Enable ISOs for every online + non-enabled participant in
|
|
|
|
|
|
/// parallel-ish (sequential await, but each individual EnableIsoAsync
|
|
|
|
|
|
/// is fast). Tolerates per-participant failures so one bad source
|
|
|
|
|
|
/// doesn't abort the rest.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private async Task EnableAllOnlineAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
var candidates = Participants.Where(p => p.IsOnline && !p.IsEnabled).ToArray();
|
|
|
|
|
|
var enabled = 0;
|
|
|
|
|
|
foreach (var p in candidates)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var resolvedName = string.IsNullOrWhiteSpace(p.CustomName)
|
|
|
|
|
|
? OutputNameTemplate.Render(
|
|
|
|
|
|
OutputNameTemplate.Get(),
|
|
|
|
|
|
p.Id,
|
|
|
|
|
|
p.DisplayName)
|
|
|
|
|
|
: p.CustomName;
|
2026-05-31 11:18:27 -04:00
|
|
|
|
// 3-arg overload (no recordOverride) — recording surface axed,
|
refactor(viewmodels): split MainViewModel into themed partial classes
MainViewModel.cs was 1017 lines and 45KB — most of it was bulk-operation
loops, Teams UIA plumbing, and the auto-apply-last-preset state machine
sitting on top of the actual MainViewModel surface (constructor, props,
OnStatsTick). Splits the class via partial-class into themed siblings:
* MainViewModel.cs (was 1017L → now 699L) — fields, properties,
constructor that wires every Command, OnStatsTick + Dispose. This
remains the thin aggregator.
* MainViewModel.TeamsCommands.cs (130L, new) — MakeTeamsCommand helper,
JoinPastedMeeting (body of JoinMeetingCommand), ExtractMeetingTitle
(already-tested static), PollTeamsMeetingState (the 1Hz UIA probe
formerly inlined in OnStatsTick).
* MainViewModel.PresetCommands.cs (108L, new) —
RequestApplyPresetOnStartup (CLI hook), LoadPendingPresetFromPreferences
(called by InitializeAsync), TryAutoApplyPendingPreset (the reconcile
step), and the _pendingPreset* private-field set that backs the path.
* MainViewModel.BulkCommands.cs (149L, new) — EnableAllOnlineAsync,
StopAllIsosAsync (with the default-No confirmation dialog),
SnapshotAll. RecordingCommands.cs from the original punch list is
intentionally absent — the recording surface was axed at 1d1ce6a;
what remains here is bulk-state ops across the participants
collection (note in the file header).
Why partial-class instead of helper-services or composed objects: every
extracted method touches the same private dispatcher / controller /
participants / toast state. Composing would require either passing
those references in (verbose call sites) or extracting them to a
shared private context object (boilerplate). Partial gives us
file-level separation without spreading the state contract.
ExtractMeetingTitle stays internal-static so the existing
MeetingTitleExtractionTests (10 cases) keep finding it. Build clean;
56 App + 104 Engine tests still pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:31:49 -04:00
|
|
|
|
// so the engine's per-pipeline recorder sink stays unattached.
|
|
|
|
|
|
await _controller.EnableIsoAsync(p.Id, resolvedName, CancellationToken.None);
|
|
|
|
|
|
p.IsEnabled = true;
|
|
|
|
|
|
enabled++;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
2026-05-31 11:18:27 -04:00
|
|
|
|
// Per-participant best-effort — one bad source shouldn't
|
refactor(viewmodels): split MainViewModel into themed partial classes
MainViewModel.cs was 1017 lines and 45KB — most of it was bulk-operation
loops, Teams UIA plumbing, and the auto-apply-last-preset state machine
sitting on top of the actual MainViewModel surface (constructor, props,
OnStatsTick). Splits the class via partial-class into themed siblings:
* MainViewModel.cs (was 1017L → now 699L) — fields, properties,
constructor that wires every Command, OnStatsTick + Dispose. This
remains the thin aggregator.
* MainViewModel.TeamsCommands.cs (130L, new) — MakeTeamsCommand helper,
JoinPastedMeeting (body of JoinMeetingCommand), ExtractMeetingTitle
(already-tested static), PollTeamsMeetingState (the 1Hz UIA probe
formerly inlined in OnStatsTick).
* MainViewModel.PresetCommands.cs (108L, new) —
RequestApplyPresetOnStartup (CLI hook), LoadPendingPresetFromPreferences
(called by InitializeAsync), TryAutoApplyPendingPreset (the reconcile
step), and the _pendingPreset* private-field set that backs the path.
* MainViewModel.BulkCommands.cs (149L, new) — EnableAllOnlineAsync,
StopAllIsosAsync (with the default-No confirmation dialog),
SnapshotAll. RecordingCommands.cs from the original punch list is
intentionally absent — the recording surface was axed at 1d1ce6a;
what remains here is bulk-state ops across the participants
collection (note in the file header).
Why partial-class instead of helper-services or composed objects: every
extracted method touches the same private dispatcher / controller /
participants / toast state. Composing would require either passing
those references in (verbose call sites) or extracting them to a
shared private context object (boilerplate). Partial gives us
file-level separation without spreading the state contract.
ExtractMeetingTitle stays internal-static so the existing
MeetingTitleExtractionTests (10 cases) keep finding it. Build clean;
56 App + 104 Engine tests still pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:31:49 -04:00
|
|
|
|
// abort the bulk operation.
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
Toast.Show(enabled == 0
|
|
|
|
|
|
? "No participants to enable"
|
|
|
|
|
|
: $"Enabled {enabled} ISO(s)");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Emergency-stop: disable every running ISO. Confirmation dialog with
|
|
|
|
|
|
/// default-No guards mid-show misclicks; the regret cost of yanking 5
|
|
|
|
|
|
/// ISOs is far higher than the Enter-press cost of the prompt.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private async Task StopAllIsosAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
// Snapshot first so the collection doesn't mutate while we iterate.
|
|
|
|
|
|
var enabled = Participants.Where(p => p.IsEnabled).ToArray();
|
|
|
|
|
|
if (enabled.Length == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
Toast.Show("No ISOs to stop");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
var confirm = System.Windows.MessageBox.Show(
|
|
|
|
|
|
$"Stop {enabled.Length} running ISO(s)?\n\nThis tears down every active pipeline immediately.",
|
2026-05-31 11:18:27 -04:00
|
|
|
|
"Dragon-ISO — Stop all ISOs",
|
refactor(viewmodels): split MainViewModel into themed partial classes
MainViewModel.cs was 1017 lines and 45KB — most of it was bulk-operation
loops, Teams UIA plumbing, and the auto-apply-last-preset state machine
sitting on top of the actual MainViewModel surface (constructor, props,
OnStatsTick). Splits the class via partial-class into themed siblings:
* MainViewModel.cs (was 1017L → now 699L) — fields, properties,
constructor that wires every Command, OnStatsTick + Dispose. This
remains the thin aggregator.
* MainViewModel.TeamsCommands.cs (130L, new) — MakeTeamsCommand helper,
JoinPastedMeeting (body of JoinMeetingCommand), ExtractMeetingTitle
(already-tested static), PollTeamsMeetingState (the 1Hz UIA probe
formerly inlined in OnStatsTick).
* MainViewModel.PresetCommands.cs (108L, new) —
RequestApplyPresetOnStartup (CLI hook), LoadPendingPresetFromPreferences
(called by InitializeAsync), TryAutoApplyPendingPreset (the reconcile
step), and the _pendingPreset* private-field set that backs the path.
* MainViewModel.BulkCommands.cs (149L, new) — EnableAllOnlineAsync,
StopAllIsosAsync (with the default-No confirmation dialog),
SnapshotAll. RecordingCommands.cs from the original punch list is
intentionally absent — the recording surface was axed at 1d1ce6a;
what remains here is bulk-state ops across the participants
collection (note in the file header).
Why partial-class instead of helper-services or composed objects: every
extracted method touches the same private dispatcher / controller /
participants / toast state. Composing would require either passing
those references in (verbose call sites) or extracting them to a
shared private context object (boilerplate). Partial gives us
file-level separation without spreading the state contract.
ExtractMeetingTitle stays internal-static so the existing
MeetingTitleExtractionTests (10 cases) keep finding it. Build clean;
56 App + 104 Engine tests still pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:31:49 -04:00
|
|
|
|
System.Windows.MessageBoxButton.YesNo,
|
|
|
|
|
|
System.Windows.MessageBoxImage.Warning,
|
|
|
|
|
|
System.Windows.MessageBoxResult.No);
|
|
|
|
|
|
if (confirm != System.Windows.MessageBoxResult.Yes) return;
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var p in enabled)
|
|
|
|
|
|
{
|
|
|
|
|
|
try { await _controller.DisableIsoAsync(p.Id, CancellationToken.None); }
|
|
|
|
|
|
catch { /* defensive */ }
|
|
|
|
|
|
p.IsEnabled = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
Toast.Show($"Stopped {enabled.Length} ISO(s)");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Save a PNG of every currently-enabled participant's latest
|
|
|
|
|
|
/// processed frame to a timestamped subdirectory under
|
2026-05-31 11:18:27 -04:00
|
|
|
|
/// %USERPROFILE%\Pictures\Dragon-ISO\. One folder per Snapshot All click
|
refactor(viewmodels): split MainViewModel into themed partial classes
MainViewModel.cs was 1017 lines and 45KB — most of it was bulk-operation
loops, Teams UIA plumbing, and the auto-apply-last-preset state machine
sitting on top of the actual MainViewModel surface (constructor, props,
OnStatsTick). Splits the class via partial-class into themed siblings:
* MainViewModel.cs (was 1017L → now 699L) — fields, properties,
constructor that wires every Command, OnStatsTick + Dispose. This
remains the thin aggregator.
* MainViewModel.TeamsCommands.cs (130L, new) — MakeTeamsCommand helper,
JoinPastedMeeting (body of JoinMeetingCommand), ExtractMeetingTitle
(already-tested static), PollTeamsMeetingState (the 1Hz UIA probe
formerly inlined in OnStatsTick).
* MainViewModel.PresetCommands.cs (108L, new) —
RequestApplyPresetOnStartup (CLI hook), LoadPendingPresetFromPreferences
(called by InitializeAsync), TryAutoApplyPendingPreset (the reconcile
step), and the _pendingPreset* private-field set that backs the path.
* MainViewModel.BulkCommands.cs (149L, new) — EnableAllOnlineAsync,
StopAllIsosAsync (with the default-No confirmation dialog),
SnapshotAll. RecordingCommands.cs from the original punch list is
intentionally absent — the recording surface was axed at 1d1ce6a;
what remains here is bulk-state ops across the participants
collection (note in the file header).
Why partial-class instead of helper-services or composed objects: every
extracted method touches the same private dispatcher / controller /
participants / toast state. Composing would require either passing
those references in (verbose call sites) or extracting them to a
shared private context object (boilerplate). Partial gives us
file-level separation without spreading the state contract.
ExtractMeetingTitle stays internal-static so the existing
MeetingTitleExtractionTests (10 cases) keep finding it. Build clean;
56 App + 104 Engine tests still pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:31:49 -04:00
|
|
|
|
/// so back-to-back clicks don't comingle. Useful for end-of-meeting
|
|
|
|
|
|
/// archives, recapping who showed up, etc.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void SnapshotAll()
|
|
|
|
|
|
{
|
|
|
|
|
|
var enabled = Participants.Where(p => p.IsEnabled).ToArray();
|
|
|
|
|
|
if (enabled.Length == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
Toast.Warn("No enabled participants to snapshot");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var rootDir = System.IO.Path.Combine(
|
|
|
|
|
|
Environment.GetFolderPath(Environment.SpecialFolder.MyPictures),
|
2026-05-31 11:18:27 -04:00
|
|
|
|
"Dragon-ISO",
|
refactor(viewmodels): split MainViewModel into themed partial classes
MainViewModel.cs was 1017 lines and 45KB — most of it was bulk-operation
loops, Teams UIA plumbing, and the auto-apply-last-preset state machine
sitting on top of the actual MainViewModel surface (constructor, props,
OnStatsTick). Splits the class via partial-class into themed siblings:
* MainViewModel.cs (was 1017L → now 699L) — fields, properties,
constructor that wires every Command, OnStatsTick + Dispose. This
remains the thin aggregator.
* MainViewModel.TeamsCommands.cs (130L, new) — MakeTeamsCommand helper,
JoinPastedMeeting (body of JoinMeetingCommand), ExtractMeetingTitle
(already-tested static), PollTeamsMeetingState (the 1Hz UIA probe
formerly inlined in OnStatsTick).
* MainViewModel.PresetCommands.cs (108L, new) —
RequestApplyPresetOnStartup (CLI hook), LoadPendingPresetFromPreferences
(called by InitializeAsync), TryAutoApplyPendingPreset (the reconcile
step), and the _pendingPreset* private-field set that backs the path.
* MainViewModel.BulkCommands.cs (149L, new) — EnableAllOnlineAsync,
StopAllIsosAsync (with the default-No confirmation dialog),
SnapshotAll. RecordingCommands.cs from the original punch list is
intentionally absent — the recording surface was axed at 1d1ce6a;
what remains here is bulk-state ops across the participants
collection (note in the file header).
Why partial-class instead of helper-services or composed objects: every
extracted method touches the same private dispatcher / controller /
participants / toast state. Composing would require either passing
those references in (verbose call sites) or extracting them to a
shared private context object (boilerplate). Partial gives us
file-level separation without spreading the state contract.
ExtractMeetingTitle stays internal-static so the existing
MeetingTitleExtractionTests (10 cases) keep finding it. Build clean;
56 App + 104 Engine tests still pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:31:49 -04:00
|
|
|
|
$"snapshots-{DateTimeOffset.Now:yyyyMMdd_HHmmss}");
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
System.IO.Directory.CreateDirectory(rootDir);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
Toast.Warn($"Couldn't create snapshot dir: {ex.Message}");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var saved = 0;
|
|
|
|
|
|
var failed = 0;
|
|
|
|
|
|
foreach (var p in enabled)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var frame = _controller.GetLatestProcessedFrame(p.Id);
|
|
|
|
|
|
if (frame is null || frame.Pixels.IsEmpty) { failed++; continue; }
|
|
|
|
|
|
|
|
|
|
|
|
var safeName = string.Join("_", p.DisplayName.Split(System.IO.Path.GetInvalidFileNameChars()));
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(safeName)) safeName = "participant";
|
|
|
|
|
|
var path = System.IO.Path.Combine(rootDir, $"{safeName}.png");
|
|
|
|
|
|
|
|
|
|
|
|
var stride = frame.Width * 4;
|
|
|
|
|
|
var bmp = new System.Windows.Media.Imaging.WriteableBitmap(
|
|
|
|
|
|
frame.Width, frame.Height, 96, 96,
|
|
|
|
|
|
System.Windows.Media.PixelFormats.Bgra32, null);
|
|
|
|
|
|
bmp.WritePixels(
|
|
|
|
|
|
new System.Windows.Int32Rect(0, 0, frame.Width, frame.Height),
|
|
|
|
|
|
frame.Pixels.ToArray(), stride, 0);
|
|
|
|
|
|
|
|
|
|
|
|
using var fs = new System.IO.FileStream(path, System.IO.FileMode.Create);
|
|
|
|
|
|
var encoder = new System.Windows.Media.Imaging.PngBitmapEncoder();
|
|
|
|
|
|
encoder.Frames.Add(System.Windows.Media.Imaging.BitmapFrame.Create(bmp));
|
|
|
|
|
|
encoder.Save(fs);
|
|
|
|
|
|
saved++;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch { failed++; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Toast.Show(failed > 0
|
2026-05-31 11:18:27 -04:00
|
|
|
|
? $"Saved {saved} snapshot(s) ({failed} failed) to Pictures\\Dragon-ISO\\{System.IO.Path.GetFileName(rootDir)}"
|
|
|
|
|
|
: $"Saved {saved} snapshot(s) to Pictures\\Dragon-ISO\\{System.IO.Path.GetFileName(rootDir)}");
|
refactor(viewmodels): split MainViewModel into themed partial classes
MainViewModel.cs was 1017 lines and 45KB — most of it was bulk-operation
loops, Teams UIA plumbing, and the auto-apply-last-preset state machine
sitting on top of the actual MainViewModel surface (constructor, props,
OnStatsTick). Splits the class via partial-class into themed siblings:
* MainViewModel.cs (was 1017L → now 699L) — fields, properties,
constructor that wires every Command, OnStatsTick + Dispose. This
remains the thin aggregator.
* MainViewModel.TeamsCommands.cs (130L, new) — MakeTeamsCommand helper,
JoinPastedMeeting (body of JoinMeetingCommand), ExtractMeetingTitle
(already-tested static), PollTeamsMeetingState (the 1Hz UIA probe
formerly inlined in OnStatsTick).
* MainViewModel.PresetCommands.cs (108L, new) —
RequestApplyPresetOnStartup (CLI hook), LoadPendingPresetFromPreferences
(called by InitializeAsync), TryAutoApplyPendingPreset (the reconcile
step), and the _pendingPreset* private-field set that backs the path.
* MainViewModel.BulkCommands.cs (149L, new) — EnableAllOnlineAsync,
StopAllIsosAsync (with the default-No confirmation dialog),
SnapshotAll. RecordingCommands.cs from the original punch list is
intentionally absent — the recording surface was axed at 1d1ce6a;
what remains here is bulk-state ops across the participants
collection (note in the file header).
Why partial-class instead of helper-services or composed objects: every
extracted method touches the same private dispatcher / controller /
participants / toast state. Composing would require either passing
those references in (verbose call sites) or extracting them to a
shared private context object (boilerplate). Partial gives us
file-level separation without spreading the state contract.
ExtractMeetingTitle stays internal-static so the existing
MeetingTitleExtractionTests (10 cases) keep finding it. Build clean;
56 App + 104 Engine tests still pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:31:49 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|