-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMainPage.xaml.cs
More file actions
367 lines (317 loc) · 13.1 KB
/
Copy pathMainPage.xaml.cs
File metadata and controls
367 lines (317 loc) · 13.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
using System.Collections.Specialized;
using System.ComponentModel;
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using MSIXplainer.Models;
using MSIXplainer.ViewModels;
using Windows.Storage.Streams;
namespace MSIXplainer;
public sealed partial class MainPage : Page
{
public MainPageViewModel ViewModel { get; } = new();
// The top-level NavView only shows two static entry points (Apps, Compare).
// "Open package from disk…" lives inside the Apps pane as a primary action
// alongside the installed-apps list, since both are ways of picking a
// package to analyze. Settings lives in the footer rail.
private NavigationViewItem? _appsItem;
private NavigationViewItem? _compareItem;
private NavigationViewItem? _settingsItem;
public MainPage()
{
InitializeComponent();
ViewModel.InstalledPackages.CollectionChanged += InstalledPackages_CollectionChanged;
ViewModel.PropertyChanged += ViewModel_PropertyChanged;
BuildStaticNavItems();
Loaded += MainPage_Loaded;
}
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
// File activation: if the app was launched by right-clicking a .msix /
// .msixbundle in Explorer (see App.PendingFileActivationPath / issue #20),
// load it now that the page is fully wired up. Consume the path so a
// page reload doesn't reopen it.
var path = App.PendingFileActivationPath;
if (!string.IsNullOrEmpty(path))
{
App.ConsumePendingFileActivationPath();
ViewModel.LoadPackageFromPath(path);
}
}
private void BuildStaticNavItems()
{
_appsItem = new NavigationViewItem
{
Content = "Apps",
Tag = "apps",
SelectsOnInvoked = false,
Icon = new FontIcon { Glyph = "\uE71D" } // AllApps
};
AutomationProperties.SetAutomationId(_appsItem, "NavApps");
_compareItem = new NavigationViewItem
{
Content = "Compare Versions…",
Tag = "compare",
SelectsOnInvoked = false,
Icon = new FontIcon { Glyph = "\uE8AB" } // Switch
};
AutomationProperties.SetAutomationId(_compareItem, "NavCompareVersions");
NavView.MenuItems.Add(_appsItem);
NavView.MenuItems.Add(_compareItem);
_settingsItem = new NavigationViewItem
{
Content = "Settings",
Tag = "settings",
SelectsOnInvoked = false,
Icon = new FontIcon { Glyph = "\uE713" } // Gear
};
AutomationProperties.SetAutomationId(_settingsItem, "NavSettings");
NavView.FooterMenuItems.Add(_settingsItem);
}
private void InstalledPackages_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
// No-op: the ListView in the Apps pane binds directly to ViewModel.InstalledPackages.
}
private void ViewModel_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
// When a package is loaded the Sections pane appears in column 0; auto-collapse
// the primary nav rail so the user's attention shifts to the loaded package.
if (e.PropertyName == nameof(MainPageViewModel.IsPackageLoaded) && ViewModel.IsPackageLoaded)
{
NavView.IsPaneOpen = false;
}
}
private async void NavView_Expanding(NavigationView sender, NavigationViewItemExpandingEventArgs args)
{
if (args.ExpandingItemContainer is NavigationViewItem nvi && nvi.Tag is "apps")
{
nvi.IsExpanded = false;
await OpenAppsPaneAsync();
}
}
private async void NavView_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args)
{
if (args.InvokedItemContainer is not NavigationViewItem invoked) return;
switch (invoked.Tag)
{
case "apps":
ExitCompareMode();
ExitSettingsMode();
await OpenAppsPaneAsync();
break;
case "compare":
CloseAppsPane();
ExitSettingsMode();
EnterCompareMode();
break;
case "settings":
CloseAppsPane();
ExitCompareMode();
EnterSettingsMode();
break;
}
}
private async void OnOpenPackageFromDiskClick(object sender, RoutedEventArgs e)
{
CloseAppsPane();
ExitCompareMode();
ExitSettingsMode();
await ViewModel.OpenPackageCommand.ExecuteAsync(null);
}
private void NavView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
{
// The three static items are SelectsOnInvoked="False" so this should not fire
// during normal use. Safety net.
}
private async Task OpenAppsPaneAsync()
{
ViewModel.IsAppsPaneOpen = true;
if (!ViewModel.HasLoadedInstalledApps && !ViewModel.IsLoadingInstalledApps)
await ViewModel.LoadInstalledAppsCommand.ExecuteAsync(null);
}
private void CloseAppsPane()
{
ViewModel.IsAppsPaneOpen = false;
ViewModel.CancelIconResolution();
}
private void OnCloseAppsPaneClick(object sender, RoutedEventArgs e) => CloseAppsPane();
private void OnRawXmlClick(object sender, RoutedEventArgs e)
{
ViewModel.SelectSection("raw-xml");
}
private void OnInstalledAppClick(object sender, ItemClickEventArgs e)
{
if (e.ClickedItem is InstalledPackage pkg)
{
ExitCompareMode();
ExitSettingsMode();
ViewModel.OpenInstalledPackage(pkg);
// Close the Apps pane so the Sections pane (which now hosts the loaded
// package's nav + actions) takes over column 0.
CloseAppsPane();
}
}
// Opens the selected app's install folder in a terminal. Tries Windows
// Terminal first, then falls back to PowerShell / cmd (see TerminalLauncher).
private void OnOpenInstallFolderInTerminal(object sender, RoutedEventArgs e)
{
if (sender is not FrameworkElement { DataContext: InstalledPackage pkg })
return;
foreach (var option in Services.TerminalLauncher.GetLaunchOptions(pkg.InstallLocation))
{
try
{
var psi = new System.Diagnostics.ProcessStartInfo
{
FileName = option.FileName,
UseShellExecute = true,
};
if (!string.IsNullOrEmpty(option.Arguments))
psi.Arguments = option.Arguments;
if (!string.IsNullOrEmpty(option.WorkingDirectory))
psi.WorkingDirectory = option.WorkingDirectory;
System.Diagnostics.Process.Start(psi);
return;
}
catch (System.Exception ex)
{
System.Diagnostics.Debug.WriteLine(
$"[MSIXplainer] Terminal launch '{option.FileName}' failed: {ex.Message}");
}
}
}
private void EnterCompareMode()
{
ViewModel.IsCompareMode = true;
if (CompareFrame.Content is null)
CompareFrame.Navigate(typeof(Pages.ComparePage));
}
internal void ExitCompareMode()
{
if (!ViewModel.IsCompareMode) return;
ViewModel.IsCompareMode = false;
CompareFrame.Content = null;
}
private void EnterSettingsMode()
{
ViewModel.IsSettingsMode = true;
if (SettingsFrame.Content is null)
SettingsFrame.Navigate(typeof(Pages.SettingsPage));
}
internal void ExitSettingsMode()
{
if (!ViewModel.IsSettingsMode) return;
ViewModel.IsSettingsMode = false;
SettingsFrame.Content = null;
}
private void ViewFinding_Click(object sender, RoutedEventArgs e)
{
if (sender is FrameworkElement fe && fe.Tag is ManifestFinding finding)
ViewModel.SelectedFinding = finding;
}
/// <summary>
/// Closes the finding detail pane. Clears the OverviewFindingsList selection
/// explicitly first so the TwoWay binding can't immediately re-push the old
/// selection back into ViewModel.SelectedFinding once we null it. Then
/// explicitly toggles IsFindingDetailVisible so the pane collapses even if
/// the SelectedFinding PropertyChanged notification gets coalesced.
/// </summary>
private void OnCloseFindingClick(object sender, RoutedEventArgs e)
{
OverviewFindingsList.SelectedItem = null;
ViewModel.SelectedFinding = null;
ViewModel.IsFindingDetailVisible = false;
}
/// <summary>
/// Copies a manifest property value to the clipboard. The 3-line WinUI
/// pattern works as long as the process's Main is marked [STAThread] —
/// see Program.cs and the comment there about issue #21.
/// </summary>
private void CopyPropertyValue_Click(object sender, RoutedEventArgs e)
{
if (sender is not FrameworkElement fe || fe.Tag is not string value || string.IsNullOrEmpty(value))
return;
try
{
var pkg = new Windows.ApplicationModel.DataTransfer.DataPackage();
pkg.SetText(value);
Windows.ApplicationModel.DataTransfer.Clipboard.SetContent(pkg);
}
catch (System.Exception ex)
{
System.Diagnostics.Debug.WriteLine(
$"[MSIXplainer] Clipboard copy failed: {ex.GetType().Name} 0x{ex.HResult:X8} {ex.Message}");
}
}
private void OnCompareVersionsClick(object sender, RoutedEventArgs e)
{
Frame.Navigate(typeof(Pages.ComparePage));
}
// ── x:Bind helper functions ──
public static Visibility BoolToVisibility(bool value) =>
value ? Visibility.Visible : Visibility.Collapsed;
public static Visibility InvertBoolToVisibility(bool value) =>
value ? Visibility.Collapsed : Visibility.Visible;
public static Visibility NullToCollapsed(object? value) =>
value is not null ? Visibility.Visible : Visibility.Collapsed;
public static Visibility StringToVisibility(string? value) =>
string.IsNullOrWhiteSpace(value) ? Visibility.Collapsed : Visibility.Visible;
public static Visibility NullBytesToVisibility(byte[]? value) =>
value is { Length: > 0 } ? Visibility.Collapsed : Visibility.Visible;
public static Visibility NonNullBytesToVisibility(byte[]? value) =>
value is { Length: > 0 } ? Visibility.Visible : Visibility.Collapsed;
public static Visibility NullObjectToVisibility(object? value) =>
value is null ? Visibility.Visible : Visibility.Collapsed;
public static Visibility NonNullObjectToVisibility(object? value) =>
value is null ? Visibility.Collapsed : Visibility.Visible;
public static Visibility PositiveIntToVisibility(int value) =>
value > 0 ? Visibility.Visible : Visibility.Collapsed;
public static Microsoft.UI.Xaml.Media.ImageSource? ObjectToImageSource(object? value) =>
value as Microsoft.UI.Xaml.Media.ImageSource;
public static BitmapImage? BytesToBitmap(byte[]? bytes)
{
if (bytes is null || bytes.Length == 0) return null;
try
{
var bitmap = new BitmapImage();
using var stream = new InMemoryRandomAccessStream();
using (var writer = new DataWriter(stream.GetOutputStreamAt(0)))
{
writer.WriteBytes(bytes);
writer.StoreAsync().GetAwaiter().GetResult();
writer.DetachStream();
}
stream.Seek(0);
bitmap.SetSourceAsync(stream).GetAwaiter().GetResult();
return bitmap;
}
catch
{
return null;
}
}
public static SolidColorBrush SeverityToBrush(FindingSeverity severity) => severity switch
{
FindingSeverity.Critical => new SolidColorBrush(ColorHelper.FromArgb(255, 196, 43, 28)),
FindingSeverity.Warning => new SolidColorBrush(ColorHelper.FromArgb(255, 157, 93, 0)),
FindingSeverity.Review => new SolidColorBrush(ColorHelper.FromArgb(255, 0, 95, 184)),
_ => new SolidColorBrush(ColorHelper.FromArgb(255, 96, 96, 96))
};
public static SolidColorBrush SeverityToBackground(FindingSeverity severity) => severity switch
{
FindingSeverity.Critical => new SolidColorBrush(ColorHelper.FromArgb(20, 196, 43, 28)),
FindingSeverity.Warning => new SolidColorBrush(ColorHelper.FromArgb(20, 157, 93, 0)),
FindingSeverity.Review => new SolidColorBrush(ColorHelper.FromArgb(20, 0, 95, 184)),
_ => new SolidColorBrush(ColorHelper.FromArgb(20, 96, 96, 96))
};
public static InfoBarSeverity SeverityToInfoBar(FindingSeverity severity) => severity switch
{
FindingSeverity.Critical => InfoBarSeverity.Error,
FindingSeverity.Warning => InfoBarSeverity.Warning,
FindingSeverity.Review => InfoBarSeverity.Informational,
_ => InfoBarSeverity.Informational
};
}