diff --git a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs
index 4d2907af4..1744e5335 100644
--- a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs
+++ b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs
@@ -76,8 +76,9 @@ public interface IProfileService : IArtemisService
/// Creates a new profile category and saves it to persistent storage.
///
/// The name of the new profile category, must be unique.
+ /// A boolean indicating whether or not to add the category to the top.
/// The newly created profile category.
- ProfileCategory CreateProfileCategory(string name);
+ ProfileCategory CreateProfileCategory(string name, bool addToTop = false);
///
/// Permanently deletes the provided profile category.
diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs
index 159e2ebf5..3a09a8c06 100644
--- a/src/Artemis.Core/Services/Storage/ProfileService.cs
+++ b/src/Artemis.Core/Services/Storage/ProfileService.cs
@@ -286,12 +286,26 @@ internal class ProfileService : IProfileService
}
///
- public ProfileCategory CreateProfileCategory(string name)
+ public ProfileCategory CreateProfileCategory(string name, bool addToTop = false)
{
ProfileCategory profileCategory;
lock (_profileRepository)
{
- profileCategory = new ProfileCategory(name, _profileCategories.Count + 1);
+ if (addToTop)
+ {
+ profileCategory = new ProfileCategory(name, 1);
+ foreach (ProfileCategory category in _profileCategories)
+ {
+ category.Order++;
+ category.Save();
+ _profileCategoryRepository.Save(category.Entity);
+ }
+ }
+ else
+ {
+ profileCategory = new ProfileCategory(name, _profileCategories.Count + 1);
+ }
+
_profileCategories.Add(profileCategory);
SaveProfileCategory(profileCategory);
}
@@ -370,7 +384,7 @@ internal class ProfileService : IProfileService
profile.ProfileEntity.IsFreshImport = false;
_profileRepository.Save(profile.ProfileEntity);
-
+
// If the provided profile is external (cloned or from the workshop?) but it is loaded locally too, reload the local instance
// A bit dodge but it ensures local instances always represent the latest stored version
ProfileConfiguration? localInstance = ProfileConfigurations.FirstOrDefault(p => p.Profile != null && p.Profile != profile && p.ProfileId == profile.ProfileEntity.Id);
@@ -450,7 +464,7 @@ internal class ProfileService : IProfileService
List modules = _pluginManagementService.GetFeaturesOfType();
profileConfiguration.LoadModules(modules);
SaveProfileCategory(category);
-
+
return profileConfiguration;
}
diff --git a/src/Artemis.UI/Extensions/HttpClientExtensions.cs b/src/Artemis.UI.Shared/Extensions/HttpClientExtensions.cs
similarity index 78%
rename from src/Artemis.UI/Extensions/HttpClientExtensions.cs
rename to src/Artemis.UI.Shared/Extensions/HttpClientExtensions.cs
index 50af33443..d9adda42f 100644
--- a/src/Artemis.UI/Extensions/HttpClientExtensions.cs
+++ b/src/Artemis.UI.Shared/Extensions/HttpClientExtensions.cs
@@ -3,12 +3,13 @@ using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
+using Artemis.UI.Shared.Utilities;
-namespace Artemis.UI.Extensions
+namespace Artemis.UI.Shared.Extensions
{
public static class HttpClientProgressExtensions
{
- public static async Task DownloadDataAsync(this HttpClient client, string requestUrl, Stream destination, IProgress? progress, CancellationToken cancellationToken)
+ public static async Task DownloadDataAsync(this HttpClient client, string requestUrl, Stream destination, IProgress? progress, CancellationToken cancellationToken)
{
using HttpResponseMessage response = await client.GetAsync(requestUrl, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
response.EnsureSuccessStatusCode();
@@ -23,13 +24,10 @@ namespace Artemis.UI.Extensions
}
// Such progress and contentLength much reporting Wow!
- Progress progressWrapper = new(totalBytes => progress.Report(GetProgressPercentage(totalBytes, contentLength.Value)));
- await download.CopyToAsync(destination, 81920, progressWrapper, cancellationToken);
-
- float GetProgressPercentage(float totalBytes, float currentBytes) => (totalBytes / currentBytes) * 100f;
+ await download.CopyToAsync(destination, 81920, progress, contentLength, cancellationToken);
}
- static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress progress, CancellationToken cancellationToken)
+ static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress progress, long? contentLength, CancellationToken cancellationToken)
{
if (bufferSize < 0)
throw new ArgumentOutOfRangeException(nameof(bufferSize));
@@ -49,7 +47,7 @@ namespace Artemis.UI.Extensions
{
await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
totalBytesRead += bytesRead;
- progress?.Report(totalBytesRead);
+ progress?.Report(new StreamProgress(totalBytesRead, contentLength ?? totalBytesRead));
}
}
}
diff --git a/src/Artemis.UI/Extensions/ZipArchiveExtensions.cs b/src/Artemis.UI/Extensions/ZipArchiveExtensions.cs
index 0d1fc507d..ad1900caa 100644
--- a/src/Artemis.UI/Extensions/ZipArchiveExtensions.cs
+++ b/src/Artemis.UI/Extensions/ZipArchiveExtensions.cs
@@ -2,6 +2,7 @@ using System;
using System.IO;
using System.IO.Compression;
using System.Threading;
+using Artemis.UI.Shared.Utilities;
namespace Artemis.UI.Extensions;
@@ -16,7 +17,7 @@ public static class ZipArchiveExtensions
/// A boolean indicating whether to override existing files
/// The progress to report to.
/// A cancellation token
- public static void ExtractToDirectory(this ZipArchive source, string destinationDirectoryName, bool overwriteFiles, IProgress progress, CancellationToken cancellationToken)
+ public static void ExtractToDirectory(this ZipArchive source, string destinationDirectoryName, bool overwriteFiles, IProgress progress, CancellationToken cancellationToken)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
@@ -28,7 +29,7 @@ public static class ZipArchiveExtensions
{
ZipArchiveEntry entry = source.Entries[index];
entry.ExtractRelativeToDirectory(destinationDirectoryName, overwriteFiles);
- progress.Report((index + 1f) / source.Entries.Count * 100f);
+ progress.Report(new StreamProgress(index + 1, source.Entries.Count));
cancellationToken.ThrowIfCancellationRequested();
}
}
diff --git a/src/Artemis.UI/Routing/Routes.cs b/src/Artemis.UI/Routing/Routes.cs
index d2709888a..dbdf17053 100644
--- a/src/Artemis.UI/Routing/Routes.cs
+++ b/src/Artemis.UI/Routing/Routes.cs
@@ -5,6 +5,7 @@ using Artemis.UI.Screens.Settings;
using Artemis.UI.Screens.Settings.Updating;
using Artemis.UI.Screens.SurfaceEditor;
using Artemis.UI.Screens.Workshop;
+using Artemis.UI.Screens.Workshop.Home;
using Artemis.UI.Screens.Workshop.Layout;
using Artemis.UI.Screens.Workshop.Profile;
using Artemis.UI.Shared.Routing;
@@ -21,6 +22,7 @@ public static class Routes
{
Children = new List()
{
+ new RouteRegistration("offline/{message:string}"),
new RouteRegistration("profiles/{page:int}"),
new RouteRegistration("profiles/{entryId:guid}"),
new RouteRegistration("layouts/{page:int}"),
diff --git a/src/Artemis.UI/Screens/Sidebar/SidebarCategoryViewModel.cs b/src/Artemis.UI/Screens/Sidebar/SidebarCategoryViewModel.cs
index 5fd46c94c..8911989d7 100644
--- a/src/Artemis.UI/Screens/Sidebar/SidebarCategoryViewModel.cs
+++ b/src/Artemis.UI/Screens/Sidebar/SidebarCategoryViewModel.cs
@@ -77,16 +77,16 @@ public class SidebarCategoryViewModel : ActivatableViewModelBase
Observable.FromEventPattern(x => profileCategory.ProfileConfigurationRemoved += x, x => profileCategory.ProfileConfigurationRemoved -= x)
.Subscribe(e => profileConfigurations.RemoveMany(profileConfigurations.Items.Where(c => c == e.EventArgs.ProfileConfiguration)))
.DisposeWith(d);
+
+ profileConfigurations.Edit(updater =>
+ {
+ updater.Clear();
+ updater.AddRange(profileCategory.ProfileConfigurations);
+ });
_isCollapsed = ProfileCategory.WhenAnyValue(vm => vm.IsCollapsed).ToProperty(this, vm => vm.IsCollapsed).DisposeWith(d);
_isSuspended = ProfileCategory.WhenAnyValue(vm => vm.IsSuspended).ToProperty(this, vm => vm.IsSuspended).DisposeWith(d);
});
-
- profileConfigurations.Edit(updater =>
- {
- foreach (ProfileConfiguration profileConfiguration in profileCategory.ProfileConfigurations)
- updater.Add(profileConfiguration);
- });
}
public ReactiveCommand ImportProfile { get; }
diff --git a/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeView.axaml b/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeView.axaml
index d0717860f..0695a7554 100644
--- a/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeView.axaml
+++ b/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeView.axaml
@@ -9,6 +9,8 @@
x:DataType="home:WorkshopHomeViewModel">
+
+
-
+
-
+
-
+
-
+
-
+
Featured submissions
Not yet implemented, here we'll show submissions we think are worth some extra attention.
-
+
Recently updated
Not yet implemented, here we'll a few of the most recent uploads/updates to the workshop.
-
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeViewModel.cs b/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeViewModel.cs
index dfaf0a8f2..45663f0d7 100644
--- a/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeViewModel.cs
+++ b/src/Artemis.UI/Screens/Workshop/Home/WorkshopHomeViewModel.cs
@@ -1,4 +1,5 @@
using System.Reactive;
+using System.Reactive.Disposables;
using System.Threading;
using System.Threading.Tasks;
using Artemis.UI.Screens.Workshop.SubmissionWizard;
@@ -6,6 +7,8 @@ using Artemis.UI.Shared;
using Artemis.UI.Shared.Routing;
using Artemis.UI.Shared.Services;
using Artemis.WebClient.Workshop;
+using Artemis.WebClient.Workshop.Services;
+using Avalonia.Threading;
using ReactiveUI;
namespace Artemis.UI.Screens.Workshop.Home;
@@ -13,21 +16,38 @@ namespace Artemis.UI.Screens.Workshop.Home;
public class WorkshopHomeViewModel : ActivatableViewModelBase, IWorkshopViewModel
{
private readonly IWindowService _windowService;
+ private readonly IWorkshopService _workshopService;
+ private bool _workshopReachable;
- public WorkshopHomeViewModel(IRouter router, IWindowService windowService)
+ public WorkshopHomeViewModel(IRouter router, IWindowService windowService, IWorkshopService workshopService)
{
_windowService = windowService;
- AddSubmission = ReactiveCommand.CreateFromTask(ExecuteAddSubmission);
- Navigate = ReactiveCommand.CreateFromTask(async r => await router.Navigate(r));
+ _workshopService = workshopService;
+
+ AddSubmission = ReactiveCommand.CreateFromTask(ExecuteAddSubmission, this.WhenAnyValue(vm => vm.WorkshopReachable));
+ Navigate = ReactiveCommand.CreateFromTask(async r => await router.Navigate(r), this.WhenAnyValue(vm => vm.WorkshopReachable));
+
+ this.WhenActivated((CompositeDisposable _) => Dispatcher.UIThread.InvokeAsync(ValidateWorkshopStatus));
}
public ReactiveCommand AddSubmission { get; }
public ReactiveCommand Navigate { get; }
+ public bool WorkshopReachable
+ {
+ get => _workshopReachable;
+ private set => RaiseAndSetIfChanged(ref _workshopReachable, value);
+ }
+
private async Task ExecuteAddSubmission(CancellationToken arg)
{
await _windowService.ShowDialogAsync();
}
+ private async Task ValidateWorkshopStatus()
+ {
+ WorkshopReachable = await _workshopService.ValidateWorkshopStatus();
+ }
+
public EntryType? EntryType => null;
}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Workshop/Home/WorkshopOfflineView.axaml b/src/Artemis.UI/Screens/Workshop/Home/WorkshopOfflineView.axaml
new file mode 100644
index 000000000..b700c5375
--- /dev/null
+++ b/src/Artemis.UI/Screens/Workshop/Home/WorkshopOfflineView.axaml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Could not reach the workshop
+
+
+
+ Please ensure you are connected to the internet.
+ If this keeps occuring, hit us up on Discord
+
+
+
+
+
+
+
diff --git a/src/Artemis.UI/Screens/Workshop/Home/WorkshopOfflineView.axaml.cs b/src/Artemis.UI/Screens/Workshop/Home/WorkshopOfflineView.axaml.cs
new file mode 100644
index 000000000..a7d5eebdb
--- /dev/null
+++ b/src/Artemis.UI/Screens/Workshop/Home/WorkshopOfflineView.axaml.cs
@@ -0,0 +1,14 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.ReactiveUI;
+
+namespace Artemis.UI.Screens.Workshop.Home;
+
+public partial class WorkshopOfflineView : ReactiveUserControl
+{
+ public WorkshopOfflineView()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Workshop/Home/WorkshopOfflineViewModel.cs b/src/Artemis.UI/Screens/Workshop/Home/WorkshopOfflineViewModel.cs
new file mode 100644
index 000000000..5ee84b8d4
--- /dev/null
+++ b/src/Artemis.UI/Screens/Workshop/Home/WorkshopOfflineViewModel.cs
@@ -0,0 +1,57 @@
+using System.Net;
+using System.Reactive;
+using System.Threading;
+using System.Threading.Tasks;
+using Artemis.UI.Shared;
+using Artemis.UI.Shared.Routing;
+using Artemis.WebClient.Workshop;
+using Artemis.WebClient.Workshop.Services;
+using ReactiveUI;
+
+namespace Artemis.UI.Screens.Workshop.Home;
+
+public class WorkshopOfflineViewModel : RoutableScreen, IWorkshopViewModel
+{
+ private readonly IRouter _router;
+ private readonly IWorkshopService _workshopService;
+ private string _message;
+
+ ///
+ public WorkshopOfflineViewModel(IWorkshopService workshopService, IRouter router)
+ {
+ _workshopService = workshopService;
+ _router = router;
+
+ Retry = ReactiveCommand.CreateFromTask(ExecuteRetry);
+ }
+
+ public ReactiveCommand Retry { get; }
+
+ public string Message
+ {
+ get => _message;
+ set => RaiseAndSetIfChanged(ref _message, value);
+ }
+
+ public override Task OnNavigating(WorkshopOfflineParameters parameters, NavigationArguments args, CancellationToken cancellationToken)
+ {
+ Message = parameters.Message;
+ return base.OnNavigating(parameters, args, cancellationToken);
+ }
+
+ private async Task ExecuteRetry(CancellationToken cancellationToken)
+ {
+ IWorkshopService.WorkshopStatus status = await _workshopService.GetWorkshopStatus();
+ if (status.IsReachable)
+ await _router.Navigate("workshop");
+
+ Message = status.Message;
+ }
+
+ public EntryType? EntryType => null;
+}
+
+public class WorkshopOfflineParameters
+{
+ public string Message { get; set; }
+}
\ No newline at end of file
diff --git a/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsView.axaml b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsView.axaml
index 2adad018e..00ffc76c9 100644
--- a/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsView.axaml
+++ b/src/Artemis.UI/Screens/Workshop/Profile/ProfileDetailsView.axaml
@@ -86,29 +86,36 @@
Latest release
-