mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-02-04 02:43:32 +00:00
Show multiple recent releases per entry if available
This commit is contained in:
parent
562049681f
commit
559434630d
@ -175,7 +175,7 @@ public class WindowsUpdateNotificationProvider : IUpdateNotificationProvider
|
||||
else if (action == "disable-workshop-notifications")
|
||||
_workshopUpdateService.DisableNotifications();
|
||||
else if (action == "view-library")
|
||||
NavigateToRoute("workshop/library");
|
||||
NavigateToRoute("workshop/library/recently-updated");
|
||||
}
|
||||
|
||||
private void NavigateToRoute(string route)
|
||||
|
||||
@ -39,7 +39,7 @@ public partial class EntryInfoViewModel : ActivatableViewModelBase
|
||||
.DisposeWith(d);
|
||||
});
|
||||
|
||||
IsAdministrator = authenticationService.GetRoles().Contains("Administrator");
|
||||
IsAdministrator = authenticationService.Roles.Contains("Administrator");
|
||||
}
|
||||
|
||||
public bool IsAdministrator { get; }
|
||||
|
||||
@ -69,7 +69,7 @@ public partial class EntrySpecificationsViewModel : ValidatableViewModelBase
|
||||
_categoriesValid = categoriesRule.ValidationChanged.Select(c => c.IsValid).ToProperty(this, vm => vm.CategoriesValid);
|
||||
_descriptionValid = descriptionRule.ValidationChanged.Select(c => c.IsValid).ToProperty(this, vm => vm.DescriptionValid);
|
||||
|
||||
IsAdministrator = authenticationService.GetRoles().Contains("Administrator");
|
||||
IsAdministrator = authenticationService.Roles.Contains("Administrator");
|
||||
this.WhenActivatedAsync(async _ => await PopulateCategories());
|
||||
this.WhenAnyValue(vm => vm.Fit).Subscribe(_ => UpdateIcon());
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
xmlns:converters="clr-namespace:Artemis.UI.Converters"
|
||||
xmlns:mdxaml="https://github.com/whistyun/Markdown.Avalonia.Tight"
|
||||
xmlns:ui="clr-namespace:Artemis.UI"
|
||||
xmlns:workshop="clr-namespace:Artemis.WebClient.Workshop;assembly=Artemis.WebClient.Workshop"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.UI.Screens.Workshop.Library.Tabs.RecentlyUpdatedItemView"
|
||||
x:DataType="tabs:RecentlyUpdatedItemViewModel">
|
||||
@ -16,27 +17,43 @@
|
||||
<converters:DateTimeConverter x:Key="DateTimeConverter" />
|
||||
<ui:ArtemisLinkCommand x:Key="ArtemisLinkCommand" />
|
||||
</UserControl.Resources>
|
||||
<Border MinHeight="110"
|
||||
MaxHeight="140"
|
||||
Padding="12"
|
||||
<UserControl.Styles>
|
||||
<Style Selector="Border.badge">
|
||||
<Setter Property="Background" Value="{DynamicResource ControlSolidFillColorDefaultBrush}" />
|
||||
<Setter Property="CornerRadius" Value="12" />
|
||||
<Setter Property="Padding" Value="6 1"></Setter>
|
||||
<Setter Property="TextBlock.FontSize" Value="12" />
|
||||
</Style>
|
||||
<Style Selector="StackPanel.entry-clickable:pointerover TextBlock">
|
||||
<Setter Property="TextDecorations" Value="Underline" />
|
||||
</Style>
|
||||
<Style Selector="TextBlock.version-clickable:pointerover">
|
||||
<Setter Property="TextDecorations" Value="Underline" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
<Border Padding="12"
|
||||
HorizontalAlignment="Stretch"
|
||||
Classes="card">
|
||||
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto, Auto">
|
||||
|
||||
<StackPanel Grid.Column="0" Grid.Row="0" Orientation="Horizontal">
|
||||
<StackPanel Grid.Column="0"
|
||||
Grid.Row="0"
|
||||
Classes="entry-clickable"
|
||||
Orientation="Horizontal"
|
||||
Cursor="Hand"
|
||||
Background="Transparent"
|
||||
PointerPressed="Entry_OnPointerPressed">
|
||||
<!-- Icon -->
|
||||
<Border CornerRadius="6"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0 0 2 0"
|
||||
Width="20"
|
||||
Height="20"
|
||||
Width="18"
|
||||
Height="18"
|
||||
ClipToBounds="True">
|
||||
<Image Stretch="UniformToFill" il:ImageLoader.Source="{CompiledBinding Entry.Id, Converter={StaticResource EntryIconUriConverter}, Mode=OneWay}" />
|
||||
</Border>
|
||||
|
||||
<!-- Title -->
|
||||
<TextBlock Margin="0 0 0 5" TextTrimming="CharacterEllipsis">
|
||||
<Run Classes="h5" Text="{CompiledBinding Entry.Name, FallbackValue=Title}" />
|
||||
<TextBlock Margin="2 0" TextTrimming="CharacterEllipsis">
|
||||
<Run Text="{CompiledBinding Entry.Name, FallbackValue=Title}" />
|
||||
<Run Classes="subtitle">by</Run>
|
||||
<Run Classes="subtitle" Text="{CompiledBinding Entry.Author, FallbackValue=Author}" />
|
||||
</TextBlock>
|
||||
@ -44,46 +61,54 @@
|
||||
IsVisible="{CompiledBinding Entry.IsOfficial}"
|
||||
Kind="ShieldStar"
|
||||
Foreground="{DynamicResource SystemAccentColorLight1}"
|
||||
Margin="2 -2 0 0"
|
||||
Margin="0 -2 0 0"
|
||||
Width="18"
|
||||
Height="18"
|
||||
HorizontalAlignment="Left"
|
||||
ToolTip.Tip="Official entry by the Artemis team" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="1" Grid.Row="0">
|
||||
<!-- Info -->
|
||||
<StackPanel Margin="0 0 4 0" HorizontalAlignment="Right">
|
||||
<TextBlock TextAlignment="Right">
|
||||
<avalonia:MaterialIcon Kind="Harddisk" />
|
||||
<Run Text="{CompiledBinding Release.Version}" />
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Install state -->
|
||||
<StackPanel Margin="0 0 4 0" HorizontalAlignment="Right" VerticalAlignment="Bottom">
|
||||
<TextBlock TextAlignment="Right" IsVisible="{CompiledBinding NotYetInstalled}">
|
||||
<avalonia:MaterialIcon Kind="Update" Foreground="{DynamicResource SystemAccentColorLight1}" Width="20" Height="20" />
|
||||
<Run>not yet installed</Run>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1" Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="10">
|
||||
<Border Classes="badge" VerticalAlignment="Top">
|
||||
<TextBlock Text="{CompiledBinding Entry.EntryType}"></TextBlock>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<Panel Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2">
|
||||
<TextBlock Classes="subtitle" IsVisible="{CompiledBinding Release.Changelog, Converter={x:Static StringConverters.IsNullOrEmpty}}">
|
||||
There are no release notes for this release.
|
||||
</TextBlock>
|
||||
<mdxaml:MarkdownScrollViewer Markdown="{CompiledBinding Release.Changelog}"
|
||||
MarkdownStyleName="FluentAvalonia"
|
||||
IsVisible="{CompiledBinding Release.Changelog, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
||||
<mdxaml:MarkdownScrollViewer.Engine>
|
||||
<mdxaml:Markdown HyperlinkCommand="{StaticResource ArtemisLinkCommand}" />
|
||||
</mdxaml:MarkdownScrollViewer.Engine>
|
||||
<mdxaml:MarkdownScrollViewer.Styles>
|
||||
<StyleInclude Source="/Styles/Markdown.axaml" />
|
||||
</mdxaml:MarkdownScrollViewer.Styles>
|
||||
</mdxaml:MarkdownScrollViewer>
|
||||
</Panel>
|
||||
|
||||
<ItemsControl Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2" ItemsSource="{CompiledBinding Releases}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate DataType="workshop:IGetRecentUpdates_Entries_Releases_Release">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<Border Classes="card-separator" />
|
||||
<Grid ColumnDefinitions="*,Auto" HorizontalAlignment="Stretch">
|
||||
<TextBlock Grid.Column="0"
|
||||
Text="{CompiledBinding Version}"
|
||||
Classes="version-clickable"
|
||||
Cursor="Hand"
|
||||
Background="Transparent"
|
||||
PointerPressed="Release_OnPointerPressed" />
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="{CompiledBinding CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter='humanize'}"
|
||||
Classes="subtitle"
|
||||
HorizontalAlignment="Right" />
|
||||
</Grid>
|
||||
<TextBlock Classes="subtitle" IsVisible="{CompiledBinding Changelog, Converter={x:Static StringConverters.IsNullOrEmpty}}">
|
||||
There are no release notes for this release.
|
||||
</TextBlock>
|
||||
<mdxaml:MarkdownScrollViewer Markdown="{CompiledBinding Changelog}"
|
||||
MarkdownStyleName="FluentAvalonia"
|
||||
IsVisible="{CompiledBinding Changelog, Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
|
||||
<mdxaml:MarkdownScrollViewer.Engine>
|
||||
<mdxaml:Markdown HyperlinkCommand="{StaticResource ArtemisLinkCommand}" />
|
||||
</mdxaml:MarkdownScrollViewer.Engine>
|
||||
<mdxaml:MarkdownScrollViewer.Styles>
|
||||
<StyleInclude Source="/Styles/Markdown.axaml" />
|
||||
</mdxaml:MarkdownScrollViewer.Styles>
|
||||
</mdxaml:MarkdownScrollViewer>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
@ -1,4 +1,8 @@
|
||||
using ReactiveUI.Avalonia;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.WebClient.Workshop;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using ReactiveUI.Avalonia;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Library.Tabs;
|
||||
|
||||
@ -8,4 +12,15 @@ public partial class RecentlyUpdatedItemView : ReactiveUserControl<RecentlyUpdat
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void Entry_OnPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
ViewModel?.NavigateToEntry();
|
||||
}
|
||||
|
||||
private void Release_OnPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
object? dataContext = (sender as TextBlock)?.DataContext;
|
||||
ViewModel?.NavigateToRelease(dataContext as IGetRecentUpdates_Entries_Releases);
|
||||
}
|
||||
}
|
||||
@ -1,23 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Artemis.UI.Shared;
|
||||
using Artemis.UI.Shared.Routing;
|
||||
using Artemis.WebClient.Workshop;
|
||||
using Artemis.WebClient.Workshop.Models;
|
||||
using Artemis.WebClient.Workshop.Services;
|
||||
using PropertyChanged.SourceGenerator;
|
||||
|
||||
namespace Artemis.UI.Screens.Workshop.Library.Tabs;
|
||||
|
||||
public partial class RecentlyUpdatedItemViewModel : ActivatableViewModelBase
|
||||
{
|
||||
public IGetRecentUpdates_Entries Entry { get; }
|
||||
public IGetRecentUpdates_Entries_LatestRelease Release { get; }
|
||||
public InstalledEntry InstalledEntry { get; }
|
||||
private readonly IWorkshopService _workshopService;
|
||||
private readonly IRouter _router;
|
||||
[Notify] private bool _notYetInstalled;
|
||||
|
||||
public RecentlyUpdatedItemViewModel(IGetRecentUpdates_Entries entry, IWorkshopService workshopService)
|
||||
public RecentlyUpdatedItemViewModel(IGetRecentUpdates_Entries entry, IWorkshopService workshopService, IRouter router)
|
||||
{
|
||||
_workshopService = workshopService;
|
||||
_router = router;
|
||||
Releases = entry.Releases;
|
||||
Entry = entry;
|
||||
Release = entry.LatestRelease ?? throw new InvalidOperationException("Entry does not have a latest release");
|
||||
InstalledEntry = workshopService.GetInstalledEntry(entry.Id) ?? throw new InvalidOperationException("Entry is not installed");
|
||||
LatestRelease = Releases.First(r => r.Id == entry.LatestReleaseId);
|
||||
NotYetInstalled = InstalledEntry.ReleaseId != Releases.Max(r => r.Id);
|
||||
}
|
||||
|
||||
public bool NotYetInstalled => InstalledEntry.ReleaseId < Release.Id;
|
||||
|
||||
public InstalledEntry InstalledEntry { get; }
|
||||
public IGetRecentUpdates_Entries Entry { get; }
|
||||
public IReadOnlyList<IGetRecentUpdates_Entries_Releases> Releases { get; set; }
|
||||
public IGetRecentUpdates_Entries_Releases LatestRelease { get; }
|
||||
|
||||
public async Task NavigateToEntry()
|
||||
{
|
||||
await _workshopService.NavigateToEntry(Entry.Id, Entry.EntryType);
|
||||
}
|
||||
|
||||
public async Task NavigateToRelease(IGetRecentUpdates_Entries_Releases? release)
|
||||
{
|
||||
if (release == null)
|
||||
return;
|
||||
|
||||
await _router.Navigate($"workshop/entries/{Entry.EntryType.ToString()}s/details/{Entry.Id}/releases/{release.Id}");
|
||||
}
|
||||
}
|
||||
@ -15,8 +15,8 @@
|
||||
</Styles>
|
||||
</UserControl.Styles>
|
||||
|
||||
<Grid RowDefinitions="Auto,*">
|
||||
<Grid Grid.Row="0" Grid.Column="0" MaxWidth="1000" Margin="0 22 0 10">
|
||||
<Grid RowDefinitions="Auto,*" MaxWidth="1000">
|
||||
<Grid Grid.Row="0" Grid.Column="0" Margin="0 22 0 10">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition MinWidth="165" MaxWidth="400" />
|
||||
<ColumnDefinition Width="*" />
|
||||
@ -34,7 +34,7 @@
|
||||
</StackPanel>
|
||||
|
||||
<ScrollViewer Grid.Row="1" Grid.Column="0" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto" VerticalAlignment="Top">
|
||||
<ItemsControl ItemsSource="{CompiledBinding Entries}" Margin="0 0 20 0" MaxWidth="1000">
|
||||
<ItemsControl ItemsSource="{CompiledBinding Entries}" Margin="0 0 20 0">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<VirtualizingStackPanel />
|
||||
|
||||
@ -51,7 +51,7 @@ public partial class RecentlyUpdatedViewModel : RoutableScreen
|
||||
.Transform(getRecentlyUpdatedItemViewModel)
|
||||
.SortAndBind(
|
||||
out ReadOnlyObservableCollection<RecentlyUpdatedItemViewModel> entries,
|
||||
SortExpressionComparer<RecentlyUpdatedItemViewModel>.Descending(p => p.Release.CreatedAt)
|
||||
SortExpressionComparer<RecentlyUpdatedItemViewModel>.Descending(p => p.LatestRelease.CreatedAt)
|
||||
)
|
||||
.Subscribe();
|
||||
|
||||
|
||||
@ -44,28 +44,4 @@ query GetDefaultPlugins {
|
||||
pluginGuid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query GetRecentUpdates($entryIds: [Long!]!, $cutoff: DateTime!) {
|
||||
entries(
|
||||
includeDefaults: true
|
||||
where: {
|
||||
and: [
|
||||
{ id: { in: $entryIds } }
|
||||
{ latestRelease: { createdAt: { gte: $cutoff } } }
|
||||
]
|
||||
}
|
||||
) {
|
||||
id
|
||||
author
|
||||
isOfficial
|
||||
name
|
||||
summary
|
||||
entryType
|
||||
createdAt
|
||||
latestRelease {
|
||||
...release
|
||||
changelog
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
query GetRecentUpdates($entryIds: [Long!]!, $cutoff: DateTime!) {
|
||||
entries(
|
||||
includeDefaults: true
|
||||
order: [{ latestRelease: { createdAt: DESC } }]
|
||||
where: {
|
||||
and: [
|
||||
{ id: { in: $entryIds } }
|
||||
{ releases: { some: { createdAt: { gte: $cutoff } } } }
|
||||
]
|
||||
}
|
||||
) {
|
||||
id
|
||||
author
|
||||
isOfficial
|
||||
name
|
||||
summary
|
||||
entryType
|
||||
latestReleaseId
|
||||
releases(
|
||||
order: [{ createdAt: DESC }]
|
||||
where: { createdAt: { gte: $cutoff } }
|
||||
) {
|
||||
...release
|
||||
changelog
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -23,7 +23,8 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
|
||||
private readonly IAuthenticationRepository _authenticationRepository;
|
||||
private readonly SemaphoreSlim _authLock = new(1, 1);
|
||||
private readonly SourceList<Claim> _claims;
|
||||
|
||||
private readonly SourceList<string> _roles;
|
||||
|
||||
private readonly IDiscoveryCache _discoveryCache;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
@ -41,7 +42,10 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
|
||||
|
||||
_claims = new SourceList<Claim>();
|
||||
_claims.Connect().Bind(out ReadOnlyObservableCollection<Claim> claims).Subscribe();
|
||||
_roles = new SourceList<string>();
|
||||
_roles.Connect().Bind(out ReadOnlyObservableCollection<string> roles).Subscribe();
|
||||
Claims = claims;
|
||||
Roles = roles;
|
||||
}
|
||||
|
||||
private async Task<DiscoveryDocumentResponse> GetDiscovery()
|
||||
@ -68,6 +72,11 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
|
||||
c.Clear();
|
||||
c.AddRange(token.Claims);
|
||||
});
|
||||
_roles.Edit(r =>
|
||||
{
|
||||
r.Clear();
|
||||
r.AddRange(_claims.Items.Where(c => c.Type == JwtClaimTypes.Role).Select(c => c.Value));
|
||||
});
|
||||
|
||||
_isLoggedInSubject.OnNext(true);
|
||||
}
|
||||
@ -118,7 +127,10 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
|
||||
|
||||
/// <inheritdoc />
|
||||
public ReadOnlyObservableCollection<Claim> Claims { get; }
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public ReadOnlyObservableCollection<string> Roles { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IObservable<Claim?> GetClaim(string type)
|
||||
{
|
||||
@ -278,6 +290,7 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
|
||||
|
||||
_token = null;
|
||||
_claims.Clear();
|
||||
_roles.Clear();
|
||||
SetStoredRefreshToken(null);
|
||||
_isLoggedInSubject.OnNext(false);
|
||||
}
|
||||
@ -289,12 +302,6 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
|
||||
return emailVerified?.Value.ToLower() == "true";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<string> GetRoles()
|
||||
{
|
||||
return Claims.Where(c => c.Type == JwtClaimTypes.Role).Select(c => c.Value).ToList();
|
||||
}
|
||||
|
||||
private async Task<bool> InternalAutoLogin(bool force = false)
|
||||
{
|
||||
if (!force && _isLoggedInSubject.Value)
|
||||
|
||||
@ -8,6 +8,7 @@ public interface IAuthenticationService : IProtectedArtemisService
|
||||
{
|
||||
IObservable<bool> IsLoggedIn { get; }
|
||||
ReadOnlyObservableCollection<Claim> Claims { get; }
|
||||
ReadOnlyObservableCollection<string> Roles { get; }
|
||||
|
||||
IObservable<Claim?> GetClaim(string type);
|
||||
Task<string?> GetBearer();
|
||||
@ -15,5 +16,4 @@ public interface IAuthenticationService : IProtectedArtemisService
|
||||
Task Login(CancellationToken cancellationToken);
|
||||
Task Logout();
|
||||
bool GetIsEmailVerified();
|
||||
List<string> GetRoles();
|
||||
}
|
||||
@ -65,7 +65,10 @@ type Entry {
|
||||
categories: [Category!]!
|
||||
tags: [Tag!]!
|
||||
images: [Image!]!
|
||||
releases: [Release!]!
|
||||
releases(
|
||||
order: [ReleaseSortInput!] @cost(weight: "10")
|
||||
where: ReleaseFilterInput @cost(weight: "10")
|
||||
): [Release!]!
|
||||
dependantReleases: [Release!]!
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user