Merge branch 'develop' into electron

This commit is contained in:
john liddell
2020-10-04 14:20:25 -05:00
22 changed files with 525 additions and 68 deletions

View File

@@ -36,8 +36,8 @@ namespace WowUp.Common.Models.Curse
public int? FileTypeId { get; set; }
public object ExposeAsAlternative { get; set; }
public long PackageFingerprintId { get; set; }
public DateTime GameVersionDateReleased { get; set; }
public long GameVersionMappingId { get; set; }
public DateTime? GameVersionDateReleased { get; set; }
public long? GameVersionMappingId { get; set; }
public int GameVersionId { get; set; }
public int GameId { get; set; }
public bool IsServerPack { get; set; }

View File

@@ -1,5 +1,6 @@
using Flurl;
using Flurl.Http;
using Serilog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -45,6 +46,7 @@ namespace WowUp.WPF.AddonProviders
AddonChannelType addonChannelType,
IEnumerable<AddonFolder> addonFolders)
{
Log.Debug($"{Name} Scanning {addonFolders.Count()} addons");
var addonDirectory = addonFolders.FirstOrDefault()?.Directory.Parent.FullName;
var scanResults = await GetScanResults(addonFolders);
@@ -332,24 +334,34 @@ namespace WowUp.WPF.AddonProviders
private async Task MapAddonFolders(List<CurseScanResult> scanResults, WowClientType clientType)
{
//var fingerprintStr = string.Join(",", scanResults.Select(sf => sf.FolderScanner.Fingerprint));
var fingerprintResponse = await GetAddonsByFingerprints(scanResults.Select(sf => sf.FolderScanner.Fingerprint));
var fingerprints = scanResults.Select(sf => sf.FolderScanner.Fingerprint);
foreach (var scanResult in scanResults)
try
{
// Curse can deliver the wrong result sometimes, ensure the result matches the client type
scanResult.ExactMatch = fingerprintResponse.ExactMatches
.FirstOrDefault(exactMatch =>
IsClientType(exactMatch.File.GameVersionFlavor, clientType) &&
HasMatchingFingerprint(scanResult, exactMatch));
var fingerprintResponse = await GetAddonsByFingerprints(fingerprints);
// If the addon does not have an exact match, check the partial matches.
if (scanResult.ExactMatch == null)
foreach (var scanResult in scanResults)
{
scanResult.ExactMatch = fingerprintResponse.PartialMatches
.FirstOrDefault(partialMatch => partialMatch.File.Modules.Any(module => module.Fingerprint == scanResult.FolderScanner.Fingerprint));
// Curse can deliver the wrong result sometimes, ensure the result matches the client type
scanResult.ExactMatch = fingerprintResponse.ExactMatches
.FirstOrDefault(exactMatch =>
IsClientType(exactMatch.File.GameVersionFlavor, clientType) &&
HasMatchingFingerprint(scanResult, exactMatch));
// If the addon does not have an exact match, check the partial matches.
if (scanResult.ExactMatch == null)
{
scanResult.ExactMatch = fingerprintResponse.PartialMatches
.FirstOrDefault(partialMatch => partialMatch.File.Modules.Any(module => module.Fingerprint == scanResult.FolderScanner.Fingerprint));
}
}
}
catch(Exception ex)
{
Log.Error(ex, "Failed to map addon folders");
Log.Error($"Fingerprints\n{string.Join(",", fingerprints)}");
throw;
}
}
private bool HasMatchingFingerprint(CurseScanResult scanResult, CurseMatch exactMatch)

View File

@@ -1,4 +1,5 @@
using Flurl.Http;
using Serilog;
using System;
using System.Collections.Generic;
using System.IO;
@@ -29,8 +30,9 @@ namespace WowUp.WPF.AddonProviders
public Task Scan(
WowClientType clientType,
AddonChannelType addonChannelType,
IEnumerable<AddonFolder> addonFolder)
IEnumerable<AddonFolder> addonFolders)
{
Log.Debug($"{Name} Scanning {addonFolders.Count()} addons");
return Task.CompletedTask;
}

View File

@@ -42,6 +42,7 @@ namespace WowUp.WPF.AddonProviders
AddonChannelType addonChannelType,
IEnumerable<AddonFolder> addonFolders)
{
Log.Debug($"{Name} Scanning {addonFolders.Count()} addons");
var addons = await GetAllAddons(clientType);
foreach (var addonFolder in addonFolders)

View File

@@ -1,4 +1,5 @@
using Flurl.Http;
using Serilog;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -40,6 +41,7 @@ namespace WowUp.WPF.AddonProviders
AddonChannelType addonChannelType,
IEnumerable<AddonFolder> addonFolders)
{
Log.Debug($"{Name} Scanning {addonFolders.Count()} addons");
foreach (var addonFolder in addonFolders)
{
if (string.IsNullOrEmpty(addonFolder.Toc.WowInterfaceId))

View File

@@ -1,7 +1,9 @@
using Microsoft.Extensions.DependencyInjection;
using CommandLine;
using Microsoft.Extensions.DependencyInjection;
using Serilog;
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
@@ -10,6 +12,7 @@ using WowUp.Common.Services.Contracts;
using WowUp.WPF.AddonProviders;
using WowUp.WPF.AddonProviders.Contracts;
using WowUp.WPF.Enums;
using WowUp.WPF.Models.WowUp;
using WowUp.WPF.Repositories;
using WowUp.WPF.Repositories.Contracts;
using WowUp.WPF.Services;
@@ -40,6 +43,8 @@ namespace WowUp.WPF
[DllImport("user32.dll")]
public static extern int SetForegroundWindow(IntPtr hwnd);
public static StartupOptions StartupOptions { get; private set; }
public App()
{
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(ExceptionHandler);
@@ -56,13 +61,14 @@ namespace WowUp.WPF
Log.Information($"Starting {AppUtilities.CurrentVersion}");
ParseCommandLineArgs();
var serviceCollection = new ServiceCollection();
ConfigureServices(serviceCollection);
_serviceProvider = serviceCollection.BuildServiceProvider();
_analyticsService = _serviceProvider.GetRequiredService<IAnalyticsService>();
}
protected override void OnStartup(StartupEventArgs e)
{
HandleSingleInstance();
@@ -159,5 +165,15 @@ namespace WowUp.WPF
//there is already another instance running!
Current.Shutdown();
}
private void ParseCommandLineArgs()
{
var args = Environment.GetCommandLineArgs().Skip(1);
Parser.Default.ParseArguments<StartupOptions>(args)
.WithParsed(
options => StartupOptions = options)
.WithNotParsed(
errors => Log.Error(string.Join("\r\n", errors.Select(x => x.ToString()).ToArray())));
}
}
}

View File

@@ -2,7 +2,15 @@
"ChangeLogs": [
{
"Version": "1.18.0",
"Description": "Add an 'Open Folder' option to the addon context menu (By Xathz).\nFeatured addons are now sorted by download count."
"Description": "Add an 'Open Folder' option to the addon context menu (By Xathz).\nAdd command line paramater to start the app minimized '-m' (By Noxis).\nAdd command line paramater to start the app and quit after auto updates are complete '-q' (By Noxis).\nAdd selector for when the app fails to automatically find WoW install folders.\nAdd the ability to select multiple addons and update their AutoUpdate status.\nAdd the ability to select multiple addons and update their Channel.\nAdd the ability to select multiple addons and re-install them.\nAdd the ability to select multiple addons and un-install them.\nFeatured addons are now sorted by download count.\nAddon icons should now be updated when a change is detected.\nThe system tray icon will now show the window when double clicked."
},
{
"Version": "1.17.3",
"Description": "Fix another CurseForge API error.\nAdd some better error logging around CurseForge scanning."
},
{
"Version": "1.17.2",
"Description": "Fix an issue with handling CurseForge API responses."
},
{
"Version": "1.17.1",

View File

@@ -31,18 +31,9 @@ namespace WowUp.WPF.Converters
BitmapImage thumbnail = new BitmapImage();
thumbnail.BeginInit();
thumbnail.DecodePixelWidth = 80;
if (uri.IsFile)
{
imageStream = FileUtilities.GetMemoryStreamFromFile(uri.LocalPath);
thumbnail.StreamSource = imageStream;
}
else
{
thumbnail.UriSource = uri;
}
thumbnail.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
thumbnail.CacheOption = BitmapCacheOption.OnLoad;
thumbnail.UriSource = uri;
thumbnail.EndInit();
return thumbnail;
}

View File

@@ -2,6 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WowUp.WPF"
xmlns:vw = "clr-namespace:WowUp.WPF.Views"
@@ -123,6 +124,33 @@
Foreground="{StaticResource White2Brush}"
FontSize="12"
HorizontalAlignment="Center"/>
<Label Margin="0 20 0 0" HorizontalAlignment="Center">
Manually Select World of Warcraft client location to get started.
</Label>
<StackPanel Margin="0 0 0 10" HorizontalAlignment="Center" Orientation="Horizontal">
<Label>World of Warcraft Client</Label>
<ComboBox x:Name="WowClientComboBox"
SelectedItem="{Binding SelectedClientType}"
Style="{StaticResource ComboBoxFlatStyle}"
ToolTip="World of Warcraft client type"
ItemsSource="{Binding WowClientTypes}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectedWowClientChangedCommand}"
CommandParameter="{Binding ElementName=WowClientComboBox, Path=SelectedValue}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
</StackPanel>
<Button Command="{Binding SetWowLocationCommand}"
Style="{StaticResource purpleButton}"
ToolTip="Select World of Warcraft Folder">
Select Location
</Button>
<Label Foreground="{StaticResource White2Brush}"
HorizontalAlignment="Center"
Content="{Binding WowClientHint}"></Label>
</StackPanel>
<!--TABS-->
<TabControl x:Name="Tabs"
@@ -161,6 +189,7 @@
x:Name="TrayIcon"
IconSource="/Assets/wowup_logo_512np_RRT_icon.ico"
ToolTipText="WowUp"
NoLeftClickDelay="True"
LeftClickCommand="{Binding TaskbarIconClickCommand}">
<tb:TaskbarIcon.ContextMenu>
<ContextMenu Style="{StaticResource DarkMenu}">

View File

@@ -0,0 +1,18 @@
using CommandLine;
using System.Collections.Generic;
using WowUp.Common.Enums;
namespace WowUp.WPF.Models.WowUp
{
public class StartupOptions
{
[Option(shortName: 'i', longName: "install", HelpText = "Specify addon URLs to install them")]
public IEnumerable<string> InputURLs { get; set; }
[Option(shortName: 'm', longName: "minimized", HelpText = "Start the application minimized")]
public bool Minimized { get; set; }
[Option(shortName: 'q', longName: "quit", HelpText = "Exit the application after auto-updates")]
public bool Quit { get; set; }
[Option(shortName: 'c', longName: "client", HelpText = "Specify client version to use", Default = WowClientType.None)]
public WowClientType ClientType { get; set; }
}
}

View File

@@ -269,7 +269,16 @@ namespace WowUp.WPF.Services
if (result == null || latestFile == null || latestFile.Version == addon.LatestVersion)
{
await SyncThumbnail(addon);
if(addon.ThumbnailUrl != result.ThumbnailUrl)
{
addon.ThumbnailUrl = result.ThumbnailUrl;
_addonRepository.UpdateItem(addon);
await SyncThumbnail(addon, true);
}
else
{
await SyncThumbnail(addon);
}
continue;
}
@@ -293,9 +302,9 @@ namespace WowUp.WPF.Services
}
}
private async Task SyncThumbnail(Addon addon)
private async Task SyncThumbnail(Addon addon, bool force = false)
{
if (!File.Exists(addon.GetThumbnailPath()))
if (force || !File.Exists(addon.GetThumbnailPath()))
{
await CacheThumbnail(addon);
}
@@ -480,6 +489,7 @@ namespace WowUp.WPF.Services
try
{
Log.Information($"Caching thumbnail {addon.Name}: {addon.ThumbnailUrl}");
using var imageStream = await addon.ThumbnailUrl.GetStreamAsync();
using Image image = Image.Load(imageStream);
@@ -496,7 +506,7 @@ namespace WowUp.WPF.Services
}
catch(Exception ex)
{
_analyticsService.Track(ex, "Failed to download thumbnail");
_analyticsService.Track(ex, $"Failed to download thumbnail {addon.Name}");
}
}
@@ -620,6 +630,8 @@ namespace WowUp.WPF.Services
}
}
Log.Debug($"Scanned {addonFolders.Count()} folders");
var matchedAddonFolders = addonFolders.Where(af => af.MatchingAddon != null);
var matchedGroups = matchedAddonFolders.GroupBy(af => $"{af.MatchingAddon.ProviderName}{af.MatchingAddon.ExternalId}");

View File

@@ -25,5 +25,7 @@ namespace WowUp.WPF.Services.Contracts
string GetAddonFolderPath(WowClientType clientType);
Task<IEnumerable<AddonFolder>> ListAddons(WowClientType clientType);
string GetClientFolderName(WowClientType clientType);
}
}

View File

@@ -4,9 +4,12 @@ using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using WowUp.Common.Enums;
using WowUp.Common.Models;
using WowUp.Common.Models.Events;
using WowUp.WPF.Extensions;
using WowUp.WPF.Services.Contracts;
namespace WowUp.WPF.Services
@@ -54,7 +57,6 @@ namespace WowUp.WPF.Services
StatusText = string.Empty,
UpdaterReady = false
};
}
private async void UpdateCheckTimerElapsed()
@@ -112,9 +114,16 @@ namespace WowUp.WPF.Services
SessionChanged?.Invoke(this, new SessionEventArgs(_sessionState));
}
public void AppLoaded()
public async void AppLoaded()
{
if(_updateCheckTimer == null)
if (App.StartupOptions != null && App.StartupOptions.ClientType != WowClientType.None)
{
SelectedClientType = App.StartupOptions.ClientType;
}
await ProcessInputUrls();
if (_updateCheckTimer == null)
{
_updateCheckTimer = new Timer(_ => UpdateCheckTimerElapsed(), null, TimeSpan.FromSeconds(0), TimeSpan.FromMinutes(60));
}
@@ -136,6 +145,41 @@ namespace WowUp.WPF.Services
SetContextText(text);
}
private async Task ProcessInputUrls()
{
if (!App.StartupOptions?.InputURLs.Any() ?? false)
{
return;
}
await App.StartupOptions.InputURLs.ForEachAsync(2, async x =>
{
PotentialAddon potentialAddon = null;
try
{
potentialAddon = await _addonService.GetAddonByUri(new Uri(x), SelectedClientType);
}
catch
{
MessageBox.Show($"Failed to import addon by URI: {x}");
return;
}
if (potentialAddon != null)
{
try
{
await _addonService.InstallAddon(potentialAddon, SelectedClientType);
}
catch
{
MessageBox.Show($"Failed to install addon {potentialAddon.Name}");
}
}
});
}
private async void ProcessAutoUpdates()
{
var updateCount = await _addonService.ProcessAutoUpdates();
@@ -144,6 +188,13 @@ namespace WowUp.WPF.Services
{
TaskbarIcon.ShowBalloonTip("WowUp", $"Automatically updated {updateCount} addons.", TaskbarIcon.Icon, true);
}
if (App.StartupOptions?.Quit == true)
{
// Artificial delay to allow notification to fire.
await Task.Delay(3000);
await Application.Current.Dispatcher.BeginInvoke(() => { Application.Current.Shutdown(); }, DispatcherPriority.SystemIdle);
}
}
private void SetContextText(string text)

View File

@@ -246,6 +246,7 @@ namespace WowUp.WPF.Services
foreach (var directory in addonDirectories)
{
Log.Debug($"Getting addon folder {directory.Name}");
var addonFolder = await GetAddonFolder(directory);
addons.Add(addonFolder);
}
@@ -253,6 +254,19 @@ namespace WowUp.WPF.Services
return addons;
}
public string GetClientFolderName(WowClientType clientType)
{
return clientType switch
{
WowClientType.Retail => RetailFolderName,
WowClientType.Classic => ClassicFolderName,
WowClientType.RetailPtr => RetailPtrFolderName,
WowClientType.ClassicPtr => ClassicPtrFolderName,
WowClientType.Beta => BetaFolderName,
_ => string.Empty,
};
}
private async Task<AddonFolder> GetAddonFolder(DirectoryInfo directory)
{
var toc = await ParseToc(directory);
@@ -295,19 +309,6 @@ namespace WowUp.WPF.Services
return new TocParser(fileText).GetMetaData();
}
private string GetClientFolderName(WowClientType clientType)
{
return clientType switch
{
WowClientType.Retail => RetailFolderName,
WowClientType.Classic => ClassicFolderName,
WowClientType.RetailPtr => RetailPtrFolderName,
WowClientType.ClassicPtr => ClassicPtrFolderName,
WowClientType.Beta => BetaFolderName,
_ => string.Empty,
};
}
private string GetClientLocationPreferenceKey(WowClientType clientType)
{
return clientType switch

View File

@@ -281,7 +281,7 @@ namespace WowUp.WPF.ViewModels
_epicBrush = Application.Current.Resources["EpicBrush"] as SolidColorBrush;
}
private void SetupDisplayState()
public void SetupDisplayState()
{
Name = _addon.Name;
CurrentVersion = string.IsNullOrEmpty(_addon.InstalledVersion)

View File

@@ -10,7 +10,6 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using WowUp.Common.Enums;
using WowUp.Common.Services.Contracts;
using WowUp.WPF.Entities;
using WowUp.WPF.Extensions;
using WowUp.WPF.Services.Contracts;
@@ -23,12 +22,16 @@ namespace WowUp.WPF.ViewModels
private static readonly object ClientNamesLock = new object();
private static readonly object DisplayAddonsLock = new object();
private readonly IAnalyticsService _analyticsService;
private readonly IServiceProvider _serviceProvider;
private readonly IWarcraftService _warcraftService;
private readonly IAddonService _addonService;
private readonly ISessionService _sessionService;
private List<Addon> _addons;
private bool _disableUpdateLoad;
private IEnumerable<AddonListItemViewModel> _selectedRows;
private IEnumerable<Addon> _selectedAddons;
private string _busyText;
public string BusyText
@@ -170,6 +173,48 @@ namespace WowUp.WPF.ViewModels
set { SetProperty(ref _selectedClientType, value); }
}
private ContextMenu _activeContextMenu;
public ContextMenu ActiveContextMenu
{
get => _activeContextMenu;
set { SetProperty(ref _activeContextMenu, value); }
}
private string _multiRowMenuTitle;
public string MultiRowMenuTitle
{
get => _multiRowMenuTitle;
set { SetProperty(ref _multiRowMenuTitle, value); }
}
private bool _multiRowMenuAutoUpdateCheck;
public bool MultiRowMenuAutoUpdateCheck
{
get => _multiRowMenuAutoUpdateCheck;
set { SetProperty(ref _multiRowMenuAutoUpdateCheck, value); }
}
private bool _multiRowMenuStableChannelCheck;
public bool MultiRowMenuStableChannelCheck
{
get => _multiRowMenuStableChannelCheck;
set { SetProperty(ref _multiRowMenuStableChannelCheck, value); }
}
private bool _multiRowMenuBetaChannelCheck;
public bool MultiRowMenuBetaChannelCheck
{
get => _multiRowMenuBetaChannelCheck;
set { SetProperty(ref _multiRowMenuBetaChannelCheck, value); }
}
private bool _multiRowMenuAlphaChannelCheck;
public bool MultiRowMenuAlphaChannelCheck
{
get => _multiRowMenuAlphaChannelCheck;
set { SetProperty(ref _multiRowMenuAlphaChannelCheck, value); }
}
public SearchInputViewModel SearchInputViewModel { get; set; }
public Command LoadItemsCommand { get; set; }
@@ -181,17 +226,28 @@ namespace WowUp.WPF.ViewModels
public Command SelectedWowClientCommand { get; set; }
public Command GridSortingCommand { get; set; }
public Command ViewInitializedCommand { get; set; }
public Command AutoUpdateCheckedCommand { get; set; }
public Command StableChannelCheckedCommand { get; set; }
public Command BetaChannelCheckedCommand { get; set; }
public Command AlphaChannelCheckedCommand { get; set; }
public Command ReInstallAllCommand { get; set; }
public Command UninstallAllCommand { get; set; }
public ContextMenu MultiRowMenu { get; set; }
public ContextMenu RowMenu { get; set; }
public ObservableCollection<AddonListItemViewModel> DisplayAddons { get; set; }
public ObservableCollection<WowClientType> ClientTypeNames { get; set; }
public AddonsViewViewModel(
IAnalyticsService analyticsService,
IServiceProvider serviceProvider,
IAddonService addonService,
IWarcraftService warcraftService,
ISessionService sessionService)
{
_addonService = addonService;
_analyticsService = analyticsService;
_warcraftService = warcraftService;
_serviceProvider = serviceProvider;
_sessionService = sessionService;
@@ -244,6 +300,12 @@ namespace WowUp.WPF.ViewModels
SelectedWowClientCommand = new Command(async () => await OnSelectedWowClientChanged(SelectedClientType));
GridSortingCommand = new Command((args) => OnGridSorting(args as DataGridSortingEventArgs));
ViewInitializedCommand = new Command(() => OnViewInitialized());
AutoUpdateCheckedCommand = new Command(() => OnAutoUpdateCheckedCommand());
StableChannelCheckedCommand = new Command(() => OnChangeAllChannelCommand(AddonChannelType.Stable));
BetaChannelCheckedCommand = new Command(() => OnChangeAllChannelCommand(AddonChannelType.Beta));
AlphaChannelCheckedCommand = new Command(() => OnChangeAllChannelCommand(AddonChannelType.Alpha));
ReInstallAllCommand = new Command(async () => await ReInstallAll());
UninstallAllCommand = new Command(async () => await UninstallAll());
SearchInputViewModel = serviceProvider.GetService<SearchInputViewModel>();
SearchInputViewModel.TextChanged += SearchInputViewModel_TextChanged;
@@ -261,6 +323,138 @@ namespace WowUp.WPF.ViewModels
Initialize();
}
private void OnAutoUpdateCheckedCommand()
{
_disableUpdateLoad = true;
foreach (var addon in _selectedAddons)
{
addon.AutoUpdateEnabled = MultiRowMenuAutoUpdateCheck;
_addonService.UpdateAddon(addon);
var listItem = DisplayAddons.FirstOrDefault(item => item.Addon.Id == addon.Id);
listItem.IsAutoUpdated = addon.AutoUpdateEnabled;
}
_disableUpdateLoad = false;
}
private void OnChangeAllChannelCommand(AddonChannelType addonChannel)
{
_disableUpdateLoad = true;
foreach (var addon in _selectedAddons)
{
addon.ChannelType = addonChannel;
_addonService.UpdateAddon(addon);
var listItem = DisplayAddons.FirstOrDefault(item => item.Addon.Id == addon.Id);
listItem.Addon.ChannelType = addonChannel;
listItem.SetupDisplayState();
}
SetSelectionChannelState();
_disableUpdateLoad = false;
}
private async Task ReInstallAll()
{
IsBusy = true;
EnableUpdateAll = false;
EnableRefresh = false;
EnableRescan = false;
await _selectedAddons.ForEachAsync(2, async (addon) =>
{
try
{
await _addonService.InstallAddon(addon.Id);
}
catch (Exception ex)
{
_analyticsService.Track(ex, "Failed during bulk install");
}
});
IsBusy = false;
EnableUpdateAll = CanUpdateAll;
EnableRefresh = true;
EnableRescan = true;
await _analyticsService.TrackUserAction(
"Addons",
"ReInstallBulk",
_selectedAddons.Count().ToString());
}
public async Task UninstallAll()
{
var messageBoxResult = MessageBox.Show(
$"Are you sure you want to remove {_selectedAddons.Count()} addons? This will remove all related folders from your World of Warcraft folder.",
"Uninstall Addon?",
MessageBoxButton.YesNo);
if (messageBoxResult != MessageBoxResult.Yes)
{
return;
}
IsBusy = true;
EnableUpdateAll = false;
EnableRefresh = false;
EnableRescan = false;
foreach (var addon in _selectedAddons.ToList())
{
try
{
await _addonService.UninstallAddon(addon);
}
catch (Exception ex)
{
Log.Error(ex, $"Failed to uninstall addon {addon.Name}");
}
}
IsBusy = false;
EnableUpdateAll = CanUpdateAll;
EnableRefresh = true;
EnableRescan = true;
await _analyticsService.TrackUserAction(
"Addons",
"UninstallBulk",
_selectedAddons.Count().ToString());
}
public void OnDataGridSelectionChange(
IEnumerable<AddonListItemViewModel> selectedItems)
{
_selectedRows = selectedItems;
_selectedAddons = selectedItems.Select(item => item.Addon);
MultiRowMenuTitle = selectedItems.Count() > 1
? $"{selectedItems.Count()} addons selected"
: string.Empty;
ActiveContextMenu = selectedItems.Count() > 1
? MultiRowMenu
: RowMenu;
MultiRowMenuAutoUpdateCheck = selectedItems.All(item => item.IsAutoUpdated);
SetSelectionChannelState();
}
private void SetSelectionChannelState()
{
MultiRowMenuStableChannelCheck = _selectedRows
.All(item => item.Addon.ChannelType == AddonChannelType.Stable);
MultiRowMenuBetaChannelCheck = _selectedRows
.All(item => item.Addon.ChannelType == AddonChannelType.Beta);
MultiRowMenuAlphaChannelCheck = _selectedRows
.All(item => item.Addon.ChannelType == AddonChannelType.Alpha);
}
private void SessionService_TabChanged(object sender, Type tabType)
{
SetAddonCountContextText(DisplayAddons.Count);
@@ -377,13 +571,15 @@ namespace WowUp.WPF.ViewModels
}
finally
{
EnableUpdateAll = DisplayAddons.Any(addon => addon.CanUpdate || addon.CanInstall);
EnableUpdateAll = CanUpdateAll;
EnableRefresh = true;
EnableRescan = true;
IsBusy = false;
}
}
public bool CanUpdateAll => DisplayAddons.Any(addon => addon.CanUpdate || addon.CanInstall);
public async Task UpdateAllRetailClassic()
{
await UpdateAllWithSpinner(WowClientType.Retail, WowClientType.Classic);
@@ -558,8 +754,10 @@ namespace WowUp.WPF.ViewModels
}
// If this addon is already in the list, ignore it
if (DisplayAddons.Any(da => da.Addon.Id == addon.Id))
var displayItem = DisplayAddons.FirstOrDefault(listItem => listItem.Addon.Id == addon.Id);
if (displayItem != null)
{
displayItem.ThumbnailUrl = addon.ThumbnailUrl;
return;
}
@@ -579,7 +777,7 @@ namespace WowUp.WPF.ViewModels
private void AddonUpdated(Addon addon)
{
if (IsBusy)
if (IsBusy || _disableUpdateLoad)
{
return;
}

View File

@@ -1,11 +1,11 @@
using Hardcodet.Wpf.TaskbarNotification;
using Hardcodet.Wpf.TaskbarNotification;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using WowUp.Common.Services.Contracts;
using WowUp.Common.Enums;
using WowUp.WPF.Entities;
using WowUp.WPF.Extensions;
using WowUp.WPF.Repositories.Contracts;
@@ -27,10 +27,15 @@ namespace WowUp.WPF.ViewModels
private readonly IAnalyticsService _analyticsService;
private readonly ISessionService _sessionService;
public ObservableCollection<WowClientType> WowClientTypes { get; set; }
public ObservableCollection<TabItem> TabItems { get; set; }
public Command SelectWowCommand { get; set; }
public Command CloseWindowCommand { get; set; }
public Command TaskbarIconCloseCommand { get; set; }
public Command TaskbarIconClickCommand { get; set; }
public Command SetWowLocationCommand { get; set; }
public Command SelectedWowClientChangedCommand { get; set; }
private TaskbarIcon _taskbarIcon;
public TaskbarIcon TaskbarIcon
@@ -116,7 +121,19 @@ namespace WowUp.WPF.ViewModels
set { SetProperty(ref _contextText, value); }
}
public ObservableCollection<TabItem> TabItems { get; set; }
private WowClientType _selectedClientType;
public WowClientType SelectedClientType
{
get => _selectedClientType;
set { SetProperty(ref _selectedClientType, value); }
}
private string _wowClientHint;
public string WowClientHint
{
get => _wowClientHint;
set { SetProperty(ref _wowClientHint, value); }
}
public ApplicationUpdateControlViewModel ApplicationUpdateControlViewModel { get; set; }
@@ -140,17 +157,59 @@ namespace WowUp.WPF.ViewModels
CloseWindowCommand = new Command(() => OnCloseWindow());
TaskbarIconCloseCommand = new Command(() => OnTaskbarIconClose());
TaskbarIconClickCommand = new Command(() => OnTaskbarIconClick());
SetWowLocationCommand = new Command(() => OnSetWowLocation());
SelectedWowClientChangedCommand = new Command(() => OnSelectedWowClientChanged());
ApplicationUpdateControlViewModel = serviceProvider.GetService<ApplicationUpdateControlViewModel>();
TabItems = new ObservableCollection<TabItem>();
WowClientTypes = new ObservableCollection<WowClientType>(
Enum.GetValues(typeof(WowClientType))
.Cast<WowClientType>()
.Where(type => type != WowClientType.None));
migrationService.MigrateDatabase();
InitializeView();
_sessionService.SessionChanged += SessionService_SessionChanged;
_sessionService.ContextTextChanged += SessionService_ContextTextChanged;
SetClientHint();
}
/// <summary>
/// Handle when the user wants to change the install folder for a particular client
/// </summary>
/// <param name="clientType"></param>
private void OnSetWowLocation()
{
var selectedPath = DialogUtilities.SelectFolder();
if (string.IsNullOrEmpty(selectedPath))
{
return;
}
if (!_warcraftService.SetWowFolderPath(SelectedClientType, selectedPath))
{
MessageBox.Show($"Unable to set \"{selectedPath}\" as your {SelectedClientType} folder");
return;
}
_sessionService.SelectedClientType = SelectedClientType;
InitializeView();
}
private void OnSelectedWowClientChanged()
{
SetClientHint();
}
private void SetClientHint()
{
var clientFolderName = _warcraftService.GetClientFolderName(SelectedClientType);
WowClientHint = $"Select the folder that contains the {SelectedClientType} client folder _{clientFolderName}";
}
private void OnTaskbarIconClick()
@@ -160,6 +219,7 @@ namespace WowUp.WPF.ViewModels
return;
}
Application.Current.MainWindow.ShowInTaskbar = true;
Application.Current.MainWindow.Show();
Application.Current.MainWindow.WindowState = WindowState.Normal;
Application.Current.MainWindow.Activate();
@@ -196,6 +256,13 @@ namespace WowUp.WPF.ViewModels
public void OnSourceInitialized(Window window)
{
if (App.StartupOptions?.Minimized == true)
{
window.Hide();
window.ShowInTaskbar = false;
window.WindowState = WindowState.Minimized;
}
var windowPref = _preferenceRepository.FindByKey(WindowPlacementKey);
var windowStatePref = _preferenceRepository.FindByKey(WindowStateKey);
if (windowPref == null)

View File

@@ -192,17 +192,17 @@ namespace WowUp.WPF.ViewModels
WowUpReleaseChannelChangedCommand = new Command(() => OnWowUpReleaseChannelChange(SelectedWowUpReleaseChannelType));
DumpDebugDataCommand = new Command(() => DumpDebugData());
RetailAddonChannelChangeCommand = new Command(() => OnAddonChannelChange(WowClientType.Retail, SelectedRetailAddonChannelType));;
RetailAddonChannelChangeCommand = new Command(() => OnAddonChannelChange(WowClientType.Retail, SelectedRetailAddonChannelType));
RetailPtrAddonChannelChangeCommand = new Command(() => OnAddonChannelChange(WowClientType.RetailPtr, SelectedRetailPtrAddonChannelType));
ClassicAddonChannelChangeCommand = new Command(() => OnAddonChannelChange(WowClientType.Classic, SelectedClassicAddonChannelType));
ClassicPtrAddonChannelChangeCommand = new Command(() => OnAddonChannelChange(WowClientType.ClassicPtr, SelectedClassicPtrAddonChannelType));
BetaAddonChannelChangeCommand = new Command(() => OnAddonChannelChange(WowClientType.Beta, SelectedBetaAddonChannelType));
RetailAutoUpdateChangeCommand = new Command(() => OnAddonAutoUpdateChange(WowClientType.Retail, RetailAutoUpdateAddons)); ;
RetailPtrAutoUpdateChangeCommand = new Command(() => OnAddonAutoUpdateChange(WowClientType.RetailPtr, RetailPtrAutoUpdateAddons)); ;
ClassicAutoUpdateChangeCommand = new Command(() => OnAddonAutoUpdateChange(WowClientType.Classic, ClassicAutoUpdateAddons)); ;
ClassicPtrAutoUpdateChangeCommand = new Command(() => OnAddonAutoUpdateChange(WowClientType.ClassicPtr, ClassicPtrAutoUpdateAddons)); ;
BetaAutoUpdateChangeCommand = new Command(() => OnAddonAutoUpdateChange(WowClientType.Beta, BetaAutoUpdateAddons)); ;
RetailAutoUpdateChangeCommand = new Command(() => OnAddonAutoUpdateChange(WowClientType.Retail, RetailAutoUpdateAddons));
RetailPtrAutoUpdateChangeCommand = new Command(() => OnAddonAutoUpdateChange(WowClientType.RetailPtr, RetailPtrAutoUpdateAddons));
ClassicAutoUpdateChangeCommand = new Command(() => OnAddonAutoUpdateChange(WowClientType.Classic, ClassicAutoUpdateAddons));
ClassicPtrAutoUpdateChangeCommand = new Command(() => OnAddonAutoUpdateChange(WowClientType.ClassicPtr, ClassicPtrAutoUpdateAddons));
BetaAutoUpdateChangeCommand = new Command(() => OnAddonAutoUpdateChange(WowClientType.Beta, BetaAutoUpdateAddons));
AddonChannelNames = new ObservableCollection<AddonChannelType>
{

View File

@@ -9,6 +9,7 @@
xmlns:vw = "clr-namespace:WowUp.WPF.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
x:Name="AddonsViewControl"
Initialized="UserControl_Initialized">
<UserControl.Resources>
@@ -247,10 +248,40 @@
HorizontalGridLinesBrush="{StaticResource Dark2}"
RowHeaderWidth="0"
BorderThickness="0"
SelectionMode="Extended"
VirtualizingPanel.ScrollUnit="Pixel"
SelectionChanged="AddonGrid_SelectionChanged"
BorderBrush="Transparent"
Sorting="AddonGrid_Sorting">
<DataGrid.Resources>
<ContextMenu x:Key="MultiRowMenu"
DataContext="{Binding DataContext, Source={x:Reference AddonsViewControl}}"
Style="{StaticResource DarkMenu}">
<MenuItem Header="{Binding MultiRowMenuTitle}" IsEnabled="False" />
<MenuItem Header="Auto Update"
IsCheckable="True"
IsChecked="{Binding MultiRowMenuAutoUpdateCheck}"
Command="{Binding AutoUpdateCheckedCommand}" />
<MenuItem Header="Channel">
<MenuItem Header="Stable"
IsCheckable="True"
IsChecked="{Binding MultiRowMenuStableChannelCheck}"
Command="{Binding StableChannelCheckedCommand}"></MenuItem>
<MenuItem Header="Beta"
IsCheckable="True"
IsChecked="{Binding MultiRowMenuBetaChannelCheck}"
Command="{Binding BetaChannelCheckedCommand}"></MenuItem>
<MenuItem Header="Alpha"
IsCheckable="True"
IsChecked="{Binding MultiRowMenuAlphaChannelCheck}"
Command="{Binding AlphaChannelCheckedCommand}"></MenuItem>
</MenuItem>
<MenuItem Header="Re-Install"
Command="{Binding ReInstallAllCommand}"/>
<Separator />
<MenuItem Header="Remove"
Command="{Binding UninstallAllCommand}"/>
</ContextMenu>
<ContextMenu x:Key="RowMenu"
Style="{StaticResource DarkMenu}"
DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
@@ -326,12 +357,15 @@
</DataGrid.Resources>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow" >
<Setter Property="ContextMenu" Value="{StaticResource RowMenu}" />
<Setter Property="ContextMenu" Value="{Binding DataContext.ActiveContextMenu, RelativeSource={RelativeSource AncestorType=UserControl}}" />
<Setter Property="Background" Value="{StaticResource Dark3}"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{StaticResource Dark1}" />
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="{StaticResource Purple4}" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
@@ -388,8 +422,7 @@
<DataTemplate>
<Border Padding="5">
<StackPanel Orientation="Horizontal" >
<TextBlock
Text="{Binding DataContext.LatestVersionHeaderText, RelativeSource={RelativeSource AncestorType=DataGrid}}"
<TextBlock Text="{Binding DataContext.LatestVersionHeaderText, RelativeSource={RelativeSource AncestorType=DataGrid}}"
TextWrapping="Wrap"
Style="{StaticResource labelTableHeader}" />
<Image Width="20"

View File

@@ -1,4 +1,7 @@
using System.Windows.Controls;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;
using WowUp.WPF.Extensions;
using WowUp.WPF.ViewModels;
@@ -46,7 +49,15 @@ namespace WowUp.WPF.Views
private void UserControl_Initialized(object sender, System.EventArgs e)
{
_viewModel.MultiRowMenu = (ContextMenu)AddonGrid.Resources["MultiRowMenu"];
_viewModel.RowMenu = (ContextMenu)AddonGrid.Resources["RowMenu"];
_viewModel.ViewInitializedCommand.Execute(e);
}
private void AddonGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selectedItems = ((DataGrid)sender).SelectedItems.Cast<AddonListItemViewModel>();
_viewModel.OnDataGridSelectionChange(selectedItems);
}
}
}

View File

@@ -9,6 +9,7 @@
<Color x:Key="Purple1Color">#6B69D6</Color>
<Color x:Key="Purple2Color">#504FA1</Color>
<Color x:Key="Purple3Color">#383773</Color>
<Color x:Key="Purple4Color">#282836</Color>
<Color x:Key="White1Color">#FFFFFF</Color>
<Color x:Key="White2Color">#DDDDDD</Color>
<Color x:Key="White3Color">#CCCCCC</Color>
@@ -45,6 +46,7 @@
<SolidColorBrush x:Key="Purple1">#6B69D6</SolidColorBrush>
<SolidColorBrush x:Key="Purple2">#504FA1</SolidColorBrush>
<SolidColorBrush x:Key="Purple3">#383773</SolidColorBrush>
<SolidColorBrush x:Key="Purple4">#282836</SolidColorBrush>
<SolidColorBrush x:Key="Blue1Brush">#315891</SolidColorBrush>
<SolidColorBrush x:Key="Blue2Brush">#24416c</SolidColorBrush>
<SolidColorBrush x:Key="Highlight1Brush">#01BAEF</SolidColorBrush>

View File

@@ -10,7 +10,7 @@
<PackageId>WowUp</PackageId>
<Authors>Jliddev</Authors>
<Product>WowUp</Product>
<Version>1.18.0-beta.3</Version>
<Version>1.18.0-beta.12</Version>
<ApplicationIcon>wowup_logo_512np_RRT_icon.ico</ApplicationIcon>
<Copyright>jliddev</Copyright>
<PackageProjectUrl>https://wowup.io</PackageProjectUrl>
@@ -54,6 +54,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="Flurl.Http" Version="2.4.2" />
<PackageReference Include="Hardcodet.NotifyIcon.Wpf" Version="1.0.8" />
<PackageReference Include="Microsoft.AppCenter.Analytics" Version="3.4.1" />