From 6969a531e2a526c151d41ce320b19a3c494b313c Mon Sep 17 00:00:00 2001 From: Timothy Baldridge Date: Sat, 18 Jan 2020 15:09:32 -0700 Subject: [PATCH] Add Upload support to WJ, fixes for the new server, etc. --- Wabbajack.BuildServer/AppSettings.cs | 3 + .../Controllers/AControllerBase.cs | 11 +++- .../Controllers/UploadedFiles.cs | 7 +-- Wabbajack.BuildServer/Controllers/Users.cs | 58 +++++++++++++++++++ Wabbajack.BuildServer/JobManager.cs | 2 + .../Models/Jobs/EnqueueAllArchives.cs | 2 +- .../Models/Jobs/EnqueueAllGameFiles.cs | 2 +- .../Models/Jobs/EnqueueRecentFiles.cs | 2 +- .../Models/Jobs/GetNexusUpdatesJob.cs | 2 +- .../Models/Jobs/IBackEndJob.cs | 7 +++ .../Models/Jobs/IFrontEndJob.cs | 7 +++ Wabbajack.BuildServer/Models/Jobs/IndexJob.cs | 2 +- .../Models/Jobs/UpdateModLists.cs | 4 +- Wabbajack.BuildServer/Models/UploadedFile.cs | 7 +-- Wabbajack.BuildServer/Program.cs | 14 ++++- Wabbajack.BuildServer/Startup.cs | 4 +- .../Wabbajack.BuildServer.csproj | 2 - Wabbajack.BuildServer/appsettings.json | 4 +- Wabbajack.Common/StatusFileStream.cs | 13 ++++- Wabbajack.Lib/FileUploader/AuthorAPI.cs | 22 +++++++ Wabbajack.Lib/GraphQL/GraphQLService.cs | 36 +++++++++--- .../View Models/Settings/AuthorFilesVM.cs | 58 +++++++++++++++++++ Wabbajack/View Models/Settings/SettingsVM.cs | 4 ++ Wabbajack/Views/MainWindow.xaml | 2 +- Wabbajack/Views/Settings/AuthorFilesView.xaml | 45 ++++++++++++++ Wabbajack/Views/Settings/SettingsView.xaml | 1 + Wabbajack/Wabbajack.csproj | 7 ++- 27 files changed, 292 insertions(+), 36 deletions(-) create mode 100644 Wabbajack.BuildServer/Controllers/Users.cs create mode 100644 Wabbajack.BuildServer/Models/Jobs/IBackEndJob.cs create mode 100644 Wabbajack.BuildServer/Models/Jobs/IFrontEndJob.cs create mode 100644 Wabbajack.Lib/FileUploader/AuthorAPI.cs create mode 100644 Wabbajack/View Models/Settings/AuthorFilesVM.cs create mode 100644 Wabbajack/Views/Settings/AuthorFilesView.xaml diff --git a/Wabbajack.BuildServer/AppSettings.cs b/Wabbajack.BuildServer/AppSettings.cs index b57ea38e..5470265e 100644 --- a/Wabbajack.BuildServer/AppSettings.cs +++ b/Wabbajack.BuildServer/AppSettings.cs @@ -13,5 +13,8 @@ namespace Wabbajack.BuildServer public string ArchiveDir { get; set; } public bool MinimalMode { get; set; } + + public bool RunFrontEndJobs { get; set; } + public bool RunBackEndJobs { get; set; } } } diff --git a/Wabbajack.BuildServer/Controllers/AControllerBase.cs b/Wabbajack.BuildServer/Controllers/AControllerBase.cs index 34f95c0c..3a1b7cd0 100644 --- a/Wabbajack.BuildServer/Controllers/AControllerBase.cs +++ b/Wabbajack.BuildServer/Controllers/AControllerBase.cs @@ -1,6 +1,13 @@ -using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Alphaleonis.Win32.Filesystem; +using GraphQL; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; +using MongoDB.Driver; using Wabbajack.BuildServer.Models; +using Wabbajack.Common; namespace Wabbajack.BuildServer.Controllers { @@ -15,5 +22,7 @@ namespace Wabbajack.BuildServer.Controllers Db = db; Logger = logger; } + + } } diff --git a/Wabbajack.BuildServer/Controllers/UploadedFiles.cs b/Wabbajack.BuildServer/Controllers/UploadedFiles.cs index 31d55591..defcf415 100644 --- a/Wabbajack.BuildServer/Controllers/UploadedFiles.cs +++ b/Wabbajack.BuildServer/Controllers/UploadedFiles.cs @@ -30,10 +30,9 @@ namespace Wabbajack.BuildServer.Controllers public async Task UploadFile(IList files) { var user = User.FindFirstValue(ClaimTypes.Name); - foreach (var file in files) - await UploadedFile.Ingest(Db, file, user); - - return Ok(); + UploadedFile result = null; + result = await UploadedFile.Ingest(Db, files.First(), user); + return Ok(result.Uri.ToString()); } private static readonly Func HandleGetListTemplate = NettleEngine.GetCompiler().Compile(@" diff --git a/Wabbajack.BuildServer/Controllers/Users.cs b/Wabbajack.BuildServer/Controllers/Users.cs new file mode 100644 index 00000000..36beea94 --- /dev/null +++ b/Wabbajack.BuildServer/Controllers/Users.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Alphaleonis.Win32.Filesystem; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using MongoDB.Driver; +using Wabbajack.BuildServer.Models; +using Wabbajack.Common; + +namespace Wabbajack.BuildServer.Controllers +{ + [Authorize] + [Route("/users")] + public class Users : AControllerBase + { + public Users(ILogger logger, DBContext db) : base(logger, db) + { + } + + [HttpGet] + [Route("add/{Name}")] + public async Task AddUser(string Name) + { + var user = new ApiKey(); + var arr = new byte[128]; + new Random().NextBytes(arr); + user.Owner = Name; + user.Key = arr.ToHex(); + user.Id = Guid.NewGuid().ToString(); + user.Roles = new List(); + user.CanUploadLists = new List(); + + await Db.ApiKeys.InsertOneAsync(user); + + return user.Id; + } + + [HttpGet] + [Route("/export")] + public async Task Export() + { + if (!Directory.Exists("exported_users")) + Directory.CreateDirectory("exported_users"); + + foreach (var user in await Db.ApiKeys.AsQueryable().ToListAsync()) + { + Directory.CreateDirectory(Path.Combine("exported_users", user.Owner)); + Alphaleonis.Win32.Filesystem.File.WriteAllText(Path.Combine("exported_users", user.Owner, "author-api-key.txt"), user.Key); + } + + return "done"; + } + + } + +} diff --git a/Wabbajack.BuildServer/JobManager.cs b/Wabbajack.BuildServer/JobManager.cs index a2e64f8d..95d60e50 100644 --- a/Wabbajack.BuildServer/JobManager.cs +++ b/Wabbajack.BuildServer/JobManager.cs @@ -106,6 +106,8 @@ namespace Wabbajack.BuildServer private async Task ScheduledJob(TimeSpan span, Job.JobPriority priority) where T : AJobPayload, new() { + if (!Settings.RunBackEndJobs && typeof(T).IsSubclassOf(typeof(IBackEndJob))) return; + if (!Settings.RunFrontEndJobs && typeof(T).IsSubclassOf(typeof(IFrontEndJob))) return; try { var jobs = await Db.Jobs.AsQueryable() diff --git a/Wabbajack.BuildServer/Models/Jobs/EnqueueAllArchives.cs b/Wabbajack.BuildServer/Models/Jobs/EnqueueAllArchives.cs index 109d3452..b0413386 100644 --- a/Wabbajack.BuildServer/Models/Jobs/EnqueueAllArchives.cs +++ b/Wabbajack.BuildServer/Models/Jobs/EnqueueAllArchives.cs @@ -12,7 +12,7 @@ using Wabbajack.Lib.ModListRegistry; namespace Wabbajack.BuildServer.Models.Jobs { - public class EnqueueAllArchives : AJobPayload + public class EnqueueAllArchives : AJobPayload, IBackEndJob { public override string Description => "Add missing modlist archives to indexer"; public override async Task Execute(DBContext db, AppSettings settings) diff --git a/Wabbajack.BuildServer/Models/Jobs/EnqueueAllGameFiles.cs b/Wabbajack.BuildServer/Models/Jobs/EnqueueAllGameFiles.cs index 8fde6e50..2a67d83c 100644 --- a/Wabbajack.BuildServer/Models/Jobs/EnqueueAllGameFiles.cs +++ b/Wabbajack.BuildServer/Models/Jobs/EnqueueAllGameFiles.cs @@ -12,7 +12,7 @@ using Path = Alphaleonis.Win32.Filesystem.Path; namespace Wabbajack.BuildServer.Models.Jobs { - public class EnqueueAllGameFiles : AJobPayload + public class EnqueueAllGameFiles : AJobPayload, IBackEndJob { public override string Description { get => $"Enqueue all game files for indexing"; } public override async Task Execute(DBContext db, AppSettings settings) diff --git a/Wabbajack.BuildServer/Models/Jobs/EnqueueRecentFiles.cs b/Wabbajack.BuildServer/Models/Jobs/EnqueueRecentFiles.cs index eaabf2e9..49f3ec96 100644 --- a/Wabbajack.BuildServer/Models/Jobs/EnqueueRecentFiles.cs +++ b/Wabbajack.BuildServer/Models/Jobs/EnqueueRecentFiles.cs @@ -13,7 +13,7 @@ using Wabbajack.Lib.NexusApi; namespace Wabbajack.BuildServer.Models.Jobs { - public class EnqueueRecentFiles : AJobPayload + public class EnqueueRecentFiles : AJobPayload, IFrontEndJob { public override string Description => "Enqueue the past days worth of mods for indexing"; diff --git a/Wabbajack.BuildServer/Models/Jobs/GetNexusUpdatesJob.cs b/Wabbajack.BuildServer/Models/Jobs/GetNexusUpdatesJob.cs index 4677dc2c..ac9134e5 100644 --- a/Wabbajack.BuildServer/Models/Jobs/GetNexusUpdatesJob.cs +++ b/Wabbajack.BuildServer/Models/Jobs/GetNexusUpdatesJob.cs @@ -10,7 +10,7 @@ using Newtonsoft.Json; namespace Wabbajack.BuildServer.Models.Jobs { - public class GetNexusUpdatesJob : AJobPayload + public class GetNexusUpdatesJob : AJobPayload, IFrontEndJob { public override string Description => "Poll the Nexus for updated mods, and clean any references to those mods"; diff --git a/Wabbajack.BuildServer/Models/Jobs/IBackEndJob.cs b/Wabbajack.BuildServer/Models/Jobs/IBackEndJob.cs new file mode 100644 index 00000000..7dfa3059 --- /dev/null +++ b/Wabbajack.BuildServer/Models/Jobs/IBackEndJob.cs @@ -0,0 +1,7 @@ +namespace Wabbajack.BuildServer.Models.Jobs +{ + public interface IBackEndJob + { + + } +} diff --git a/Wabbajack.BuildServer/Models/Jobs/IFrontEndJob.cs b/Wabbajack.BuildServer/Models/Jobs/IFrontEndJob.cs new file mode 100644 index 00000000..e8bf13a8 --- /dev/null +++ b/Wabbajack.BuildServer/Models/Jobs/IFrontEndJob.cs @@ -0,0 +1,7 @@ +namespace Wabbajack.BuildServer.Models.Jobs +{ + public interface IFrontEndJob + { + + } +} diff --git a/Wabbajack.BuildServer/Models/Jobs/IndexJob.cs b/Wabbajack.BuildServer/Models/Jobs/IndexJob.cs index 003a8eeb..91982179 100644 --- a/Wabbajack.BuildServer/Models/Jobs/IndexJob.cs +++ b/Wabbajack.BuildServer/Models/Jobs/IndexJob.cs @@ -16,7 +16,7 @@ using Wabbajack.VirtualFileSystem; namespace Wabbajack.BuildServer.Models.Jobs { - public class IndexJob : AJobPayload + public class IndexJob : AJobPayload, IBackEndJob { public Archive Archive { get; set; } public override string Description => $"Index ${Archive.State.PrimaryKeyString} and save the download/file state"; diff --git a/Wabbajack.BuildServer/Models/Jobs/UpdateModLists.cs b/Wabbajack.BuildServer/Models/Jobs/UpdateModLists.cs index badba9b3..809f7f5d 100644 --- a/Wabbajack.BuildServer/Models/Jobs/UpdateModLists.cs +++ b/Wabbajack.BuildServer/Models/Jobs/UpdateModLists.cs @@ -12,7 +12,7 @@ using File = Alphaleonis.Win32.Filesystem.File; namespace Wabbajack.BuildServer.Models.Jobs { - public class UpdateModLists : AJobPayload + public class UpdateModLists : AJobPayload, IFrontEndJob { public override string Description => "Validate curated modlists"; public override async Task Execute(DBContext db, AppSettings settings) @@ -26,7 +26,7 @@ namespace Wabbajack.BuildServer.Models.Jobs var whitelists = new ValidateModlist(queue); await whitelists.LoadListsFromGithub(); - foreach (var list in modlists.Skip(8).Take(1)) + foreach (var list in modlists) { try { diff --git a/Wabbajack.BuildServer/Models/UploadedFile.cs b/Wabbajack.BuildServer/Models/UploadedFile.cs index 9edd0426..7c2e95a5 100644 --- a/Wabbajack.BuildServer/Models/UploadedFile.cs +++ b/Wabbajack.BuildServer/Models/UploadedFile.cs @@ -18,15 +18,14 @@ namespace Wabbajack.BuildServer.Models public DateTime UploadDate { get; set; } = DateTime.UtcNow; [BsonIgnore] - public string MungedName => $"{Path.GetFileNameWithoutExtension(Name)}-{Id}-{Path.GetExtension(Name)}"; + public string MungedName => $"{Path.GetFileNameWithoutExtension(Name)}-{Id}{Path.GetExtension(Name)}"; - [BsonIgnore] public object Uri => $"https://static.wabbajack.org/files/{MungedName}"; + [BsonIgnore] public object Uri => $"https://build.wabbajack.org/files/{MungedName}"; public static async Task Ingest(DBContext db, IFormFile src, string uploader) { var record = new UploadedFile {Uploader = uploader, Name = src.FileName, Id = Guid.NewGuid().ToString()}; - var dest_path = - $@"public\\files\\{Path.GetFileNameWithoutExtension(src.FileName)}-{record.Id}{Path.GetExtension(src.FileName)}"; + var dest_path = $@"public\\files\\{record.MungedName}"; using (var stream = File.OpenWrite(dest_path)) await src.CopyToAsync(stream); diff --git a/Wabbajack.BuildServer/Program.cs b/Wabbajack.BuildServer/Program.cs index b0cb70a4..ccff441b 100644 --- a/Wabbajack.BuildServer/Program.cs +++ b/Wabbajack.BuildServer/Program.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; +using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; @@ -20,10 +22,20 @@ namespace Wabbajack.BuildServer Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { - webBuilder.UseUrls("http://*:5000"); webBuilder.UseStartup() .UseKestrel(options => { + options.Listen(IPAddress.Any, 80); + options.Listen(IPAddress.Any, 443, listenOptions => + { + using (var store = new X509Store(StoreName.My)) + { + store.Open(OpenFlags.ReadOnly); + var cert = store.Certificates.Find(X509FindType.FindBySubjectName, "build.wabbajack.org", true)[0]; + listenOptions.UseHttps(cert); + + } + }); options.Limits.MaxRequestBodySize = null; }); }); diff --git a/Wabbajack.BuildServer/Startup.cs b/Wabbajack.BuildServer/Startup.cs index f3a821a1..a4c91af0 100644 --- a/Wabbajack.BuildServer/Startup.cs +++ b/Wabbajack.BuildServer/Startup.cs @@ -11,9 +11,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.HttpsPolicy; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.AzureAD.UI; using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; @@ -88,7 +86,7 @@ namespace Wabbajack.BuildServer app.UseDeveloperExceptionPage(); } - + app.UseHttpsRedirection(); app.UseGraphiQl(); app.UseDeveloperExceptionPage(); app.UseStaticFiles(); diff --git a/Wabbajack.BuildServer/Wabbajack.BuildServer.csproj b/Wabbajack.BuildServer/Wabbajack.BuildServer.csproj index 035e2f4c..d70fac15 100644 --- a/Wabbajack.BuildServer/Wabbajack.BuildServer.csproj +++ b/Wabbajack.BuildServer/Wabbajack.BuildServer.csproj @@ -12,10 +12,8 @@ - - diff --git a/Wabbajack.BuildServer/appsettings.json b/Wabbajack.BuildServer/appsettings.json index f7b96535..96c6a575 100644 --- a/Wabbajack.BuildServer/appsettings.json +++ b/Wabbajack.BuildServer/appsettings.json @@ -32,7 +32,9 @@ "WabbajackSettings": { "DownloadDir": "c:\\tmp\\downloads", "ArchiveDir": "c:\\archives", - "MinimalMode": true + "MinimalMode": true, + "RunFrontEndJobs": true, + "RunBackEndJobs": true }, "AllowedHosts": "*" } diff --git a/Wabbajack.Common/StatusFileStream.cs b/Wabbajack.Common/StatusFileStream.cs index 69a61909..0d566c4e 100644 --- a/Wabbajack.Common/StatusFileStream.cs +++ b/Wabbajack.Common/StatusFileStream.cs @@ -6,9 +6,11 @@ namespace Wabbajack.Common { private string _message; private Stream _inner; + private WorkQueue _queue; - public StatusFileStream(Stream fs, string message) + public StatusFileStream(Stream fs, string message, WorkQueue queue = null) { + _queue = queue; _inner = fs; _message = message; } @@ -36,7 +38,14 @@ namespace Wabbajack.Common private void UpdateStatus() { - if (_inner.Length != 0) + if (_inner.Length == 0) + { + return; + } + + if (_queue != null) + _queue.Report(_message, (int) (_inner.Position * 100 / _inner.Length)); + else Utils.Status(_message, (int) (_inner.Position * 100 / _inner.Length)); } diff --git a/Wabbajack.Lib/FileUploader/AuthorAPI.cs b/Wabbajack.Lib/FileUploader/AuthorAPI.cs new file mode 100644 index 00000000..21e4cdff --- /dev/null +++ b/Wabbajack.Lib/FileUploader/AuthorAPI.cs @@ -0,0 +1,22 @@ +using System; +using System.Reactive.Linq; +using Alphaleonis.Win32.Filesystem; +using Wabbajack.Common; + +namespace Wabbajack.Lib.FileUploader +{ + public class AuthorAPI + { + public static IObservable HaveAuthorAPIKey => Utils.HaveEncryptedJsonObservable("author-api-key"); + + public static IObservable AuthorAPIKey => HaveAuthorAPIKey.Where(h => h) + .Select(_ => File.ReadAllText(Path.Combine(Consts.LocalAppDataPath, "author-api-key"))); + + + public static string GetAPIKey() + { + return File.ReadAllText(Path.Combine(Consts.LocalAppDataPath, "author-api-key.txt")).Trim(); + } + public static bool HasAPIKey => File.Exists(Path.Combine(Consts.LocalAppDataPath, "author-api-key.txt")); + } +} diff --git a/Wabbajack.Lib/GraphQL/GraphQLService.cs b/Wabbajack.Lib/GraphQL/GraphQLService.cs index 40d7e2d5..6a3c29e9 100644 --- a/Wabbajack.Lib/GraphQL/GraphQLService.cs +++ b/Wabbajack.Lib/GraphQL/GraphQLService.cs @@ -1,12 +1,15 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net.Http; +using System.Net.Http.Headers; using System.Threading.Tasks; using GraphQL.Client; using GraphQL.Client.Http; using GraphQL.Common.Request; using Wabbajack.Common; +using Wabbajack.Lib.FileUploader; using Wabbajack.Lib.GraphQL.DTOs; using Path = Alphaleonis.Win32.Filesystem.Path; @@ -17,7 +20,7 @@ namespace Wabbajack.Lib.GraphQL public static readonly Uri BaseURL = new Uri("https://build.wabbajack.org/graphql"); public static readonly Uri UploadURL = new Uri("https://build.wabbajack.org/upload_file"); - public async Task> GetUploadedFiles() + public static async Task> GetUploadedFiles() { var client = new GraphQLHttpClient(BaseURL); var query = new GraphQLRequest @@ -38,16 +41,31 @@ namespace Wabbajack.Lib.GraphQL return result.GetDataFieldAs>("uploadedFiles"); } - public async Task UploadFile(string filename) + public static Task UploadFile(WorkQueue queue, string filename) { - using (var stream = new StatusFileStream(File.OpenRead(filename), $"Uploading {Path.GetFileName(filename)}")) + var tcs = new TaskCompletionSource(); + queue.QueueTask(async () => { - var client = new HttpClient(); - client.DefaultRequestHeaders.Add("X-API-KEY", "TODO"); - var form = new MultipartFormDataContent {{new StreamContent(stream), "file"}}; - var response = await client.PostAsync(UploadURL, form); - return response.IsSuccessStatusCode; - } + using (var stream = + new StatusFileStream(File.OpenRead(filename), $"Uploading {Path.GetFileName(filename)}", queue)) + { + var client = new HttpClient(); + client.DefaultRequestHeaders.Add("X-API-KEY", AuthorAPI.GetAPIKey()); + var content = new StreamContent(stream); + var form = new MultipartFormDataContent + { + {content, "files", Path.GetFileName(filename)} + }; + content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); + + var response = await client.PostAsync(UploadURL, form); + if (response.IsSuccessStatusCode) + tcs.SetResult(await response.Content.ReadAsStringAsync()); + else + tcs.SetResult("FAILED"); + } + }); + return tcs.Task; } } } diff --git a/Wabbajack/View Models/Settings/AuthorFilesVM.cs b/Wabbajack/View Models/Settings/AuthorFilesVM.cs new file mode 100644 index 00000000..bddbfa39 --- /dev/null +++ b/Wabbajack/View Models/Settings/AuthorFilesVM.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Reactive.Disposables; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Threading.Tasks; + +using System.Windows; +using Alphaleonis.Win32.Filesystem; +using Microsoft.WindowsAPICodePack.Shell.PropertySystem; +using ReactiveUI; +using ReactiveUI.Fody.Helpers; +using Wabbajack.Common; +using Wabbajack.Lib.FileUploader; +using Wabbajack.Lib.GraphQL; +using Wabbajack.Lib.GraphQL.DTOs; +using File = System.IO.File; + +namespace Wabbajack +{ + public class AuthorFilesVM : BackNavigatingVM + { + public Visibility IsVisible { get; } + + [Reactive] + public string SelectedFile { get; set; } + + public IReactiveCommand SelectFile { get; } + public IReactiveCommand Upload { get; } + + [Reactive] + public double UploadProgress { get; set; } + + private WorkQueue Queue = new WorkQueue(1); + + public AuthorFilesVM(SettingsVM vm) : base(vm.MWVM) + { + var sub = new Subject(); + Queue.Status.Select(s => (double)s.ProgressPercent).Subscribe(v => + { + UploadProgress = v; + }); + IsVisible = AuthorAPI.HasAPIKey ? Visibility.Visible : Visibility.Collapsed; + + SelectFile = ReactiveCommand.Create(() => + { + var fod = UIUtils.OpenFileDialog("*|*"); + if (fod != null) + SelectedFile = fod; + }); + + Upload = ReactiveCommand.Create(async () => + { + SelectedFile = await GraphQLService.UploadFile(Queue, SelectedFile); + }); + } + } +} diff --git a/Wabbajack/View Models/Settings/SettingsVM.cs b/Wabbajack/View Models/Settings/SettingsVM.cs index 67ef7e9e..4175f7f2 100644 --- a/Wabbajack/View Models/Settings/SettingsVM.cs +++ b/Wabbajack/View Models/Settings/SettingsVM.cs @@ -14,12 +14,16 @@ namespace Wabbajack public LoginManagerVM Login { get; } public PerformanceSettings Performance { get; } + public AuthorFilesVM AuthorFile { get; } + public SettingsVM(MainWindowVM mainWindowVM) : base(mainWindowVM) { MWVM = mainWindowVM; Login = new LoginManagerVM(this); Performance = mainWindowVM.Settings.Performance; + AuthorFile = new AuthorFilesVM(this); } + } } diff --git a/Wabbajack/Views/MainWindow.xaml b/Wabbajack/Views/MainWindow.xaml index ed801fcb..711764b1 100644 --- a/Wabbajack/Views/MainWindow.xaml +++ b/Wabbajack/Views/MainWindow.xaml @@ -16,7 +16,7 @@ RenderOptions.BitmapScalingMode="HighQuality" ResizeMode="CanResize" Style="{StaticResource {x:Type Window}}" - TitleBarHeight="25" + TitlebarHeight="25" UseLayoutRounding="True" WindowStyle="ToolWindow" mc:Ignorable="d"> diff --git a/Wabbajack/Views/Settings/AuthorFilesView.xaml b/Wabbajack/Views/Settings/AuthorFilesView.xaml new file mode 100644 index 00000000..0228eee6 --- /dev/null +++ b/Wabbajack/Views/Settings/AuthorFilesView.xaml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Wabbajack/Views/Settings/SettingsView.xaml b/Wabbajack/Views/Settings/SettingsView.xaml index e738772f..76b43e9f 100644 --- a/Wabbajack/Views/Settings/SettingsView.xaml +++ b/Wabbajack/Views/Settings/SettingsView.xaml @@ -47,6 +47,7 @@ + diff --git a/Wabbajack/Wabbajack.csproj b/Wabbajack/Wabbajack.csproj index 84e6c6a9..bec54fb2 100644 --- a/Wabbajack/Wabbajack.csproj +++ b/Wabbajack/Wabbajack.csproj @@ -173,6 +173,7 @@ Designer + ModListTileView.xaml @@ -184,6 +185,9 @@ + + AuthorFilesView.xaml + LoginItemView.xaml @@ -308,6 +312,7 @@ Designer MSBuild:Compile + Designer MSBuild:Compile @@ -543,7 +548,7 @@ 1.1.11 - 2.0.0-alpha0660 + 1.6.5 3.1.0