mirror of
https://github.com/claunia/marechai.git
synced 2025-12-16 19:14:25 +00:00
Show machine photo thumbnails in machine details.
This commit is contained in:
@@ -32,7 +32,7 @@ public partial class App : Application
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected Window? MainWindow { get; private set; }
|
protected Window? MainWindow { get; private set; }
|
||||||
protected IHost? Host { get; private set; }
|
public IHost? Host { get; private set; }
|
||||||
|
|
||||||
protected override async void OnLaunched(LaunchActivatedEventArgs args)
|
protected override async void OnLaunched(LaunchActivatedEventArgs args)
|
||||||
{
|
{
|
||||||
@@ -109,6 +109,7 @@ public partial class App : Application
|
|||||||
// Register application services
|
// Register application services
|
||||||
services.AddSingleton<FlagCache>();
|
services.AddSingleton<FlagCache>();
|
||||||
services.AddSingleton<CompanyLogoCache>();
|
services.AddSingleton<CompanyLogoCache>();
|
||||||
|
services.AddSingleton<MachinePhotoCache>();
|
||||||
services.AddSingleton<NewsService>();
|
services.AddSingleton<NewsService>();
|
||||||
services.AddSingleton<NewsViewModel>();
|
services.AddSingleton<NewsViewModel>();
|
||||||
services.AddSingleton<ComputersService>();
|
services.AddSingleton<ComputersService>();
|
||||||
|
|||||||
@@ -28,13 +28,18 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Windows.Storage.Streams;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using Marechai.App.Helpers;
|
using Marechai.App.Helpers;
|
||||||
using Marechai.App.Presentation.Models;
|
using Marechai.App.Presentation.Models;
|
||||||
using Marechai.App.Services;
|
using Marechai.App.Services;
|
||||||
|
using Marechai.App.Services.Caching;
|
||||||
using Marechai.Data;
|
using Marechai.Data;
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Media;
|
||||||
|
using Microsoft.UI.Xaml.Media.Imaging;
|
||||||
using Uno.Extensions.Navigation;
|
using Uno.Extensions.Navigation;
|
||||||
|
|
||||||
namespace Marechai.App.Presentation.ViewModels;
|
namespace Marechai.App.Presentation.ViewModels;
|
||||||
@@ -44,6 +49,7 @@ public partial class MachineViewViewModel : ObservableObject
|
|||||||
private readonly ComputersService _computersService;
|
private readonly ComputersService _computersService;
|
||||||
private readonly ILogger<MachineViewViewModel> _logger;
|
private readonly ILogger<MachineViewViewModel> _logger;
|
||||||
private readonly INavigator _navigator;
|
private readonly INavigator _navigator;
|
||||||
|
private readonly MachinePhotoCache _photoCache;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string _companyName = string.Empty;
|
private string _companyName = string.Empty;
|
||||||
@@ -94,6 +100,9 @@ public partial class MachineViewViewModel : ObservableObject
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private Visibility _showModel = Visibility.Collapsed;
|
private Visibility _showModel = Visibility.Collapsed;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private Visibility _showPhotos = Visibility.Collapsed;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private Visibility _showProcessors = Visibility.Collapsed;
|
private Visibility _showProcessors = Visibility.Collapsed;
|
||||||
|
|
||||||
@@ -103,12 +112,13 @@ public partial class MachineViewViewModel : ObservableObject
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private Visibility _showStorage = Visibility.Collapsed;
|
private Visibility _showStorage = Visibility.Collapsed;
|
||||||
|
|
||||||
public MachineViewViewModel(ILogger<MachineViewViewModel> logger, INavigator navigator,
|
public MachineViewViewModel(ILogger<MachineViewViewModel> logger, INavigator navigator,
|
||||||
ComputersService computersService)
|
ComputersService computersService, MachinePhotoCache photoCache)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_navigator = navigator;
|
_navigator = navigator;
|
||||||
_computersService = computersService;
|
_computersService = computersService;
|
||||||
|
_photoCache = photoCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObservableCollection<ProcessorDisplayItem> Processors { get; } = [];
|
public ObservableCollection<ProcessorDisplayItem> Processors { get; } = [];
|
||||||
@@ -116,6 +126,7 @@ public partial class MachineViewViewModel : ObservableObject
|
|||||||
public ObservableCollection<GpuDisplayItem> Gpus { get; } = [];
|
public ObservableCollection<GpuDisplayItem> Gpus { get; } = [];
|
||||||
public ObservableCollection<SoundSynthesizerDisplayItem> SoundSynthesizers { get; } = [];
|
public ObservableCollection<SoundSynthesizerDisplayItem> SoundSynthesizers { get; } = [];
|
||||||
public ObservableCollection<StorageDisplayItem> Storage { get; } = [];
|
public ObservableCollection<StorageDisplayItem> Storage { get; } = [];
|
||||||
|
public ObservableCollection<PhotoCarouselDisplayItem> Photos { get; } = [];
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
public async Task GoBack()
|
public async Task GoBack()
|
||||||
@@ -192,6 +203,7 @@ public partial class MachineViewViewModel : ObservableObject
|
|||||||
Gpus.Clear();
|
Gpus.Clear();
|
||||||
SoundSynthesizers.Clear();
|
SoundSynthesizers.Clear();
|
||||||
Storage.Clear();
|
Storage.Clear();
|
||||||
|
Photos.Clear();
|
||||||
|
|
||||||
_logger.LogInformation("Loading machine {MachineId}", machineId);
|
_logger.LogInformation("Loading machine {MachineId}", machineId);
|
||||||
|
|
||||||
@@ -324,6 +336,25 @@ public partial class MachineViewViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Populate photos
|
||||||
|
List<Guid> photoIds = await _computersService.GetMachinePhotosAsync(machineId);
|
||||||
|
|
||||||
|
if(photoIds.Count > 0)
|
||||||
|
{
|
||||||
|
foreach(Guid photoId in photoIds)
|
||||||
|
{
|
||||||
|
var photoItem = new PhotoCarouselDisplayItem
|
||||||
|
{
|
||||||
|
PhotoId = photoId
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load thumbnail image asynchronously
|
||||||
|
_ = LoadPhotoThumbnailAsync(photoItem);
|
||||||
|
|
||||||
|
Photos.Add(photoItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
UpdateVisibilities();
|
UpdateVisibilities();
|
||||||
IsDataLoaded = true;
|
IsDataLoaded = true;
|
||||||
IsLoading = false;
|
IsLoading = false;
|
||||||
@@ -354,6 +385,28 @@ public partial class MachineViewViewModel : ObservableObject
|
|||||||
ShowGpus = Gpus.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
|
ShowGpus = Gpus.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||||
ShowSoundSynthesizers = SoundSynthesizers.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
|
ShowSoundSynthesizers = SoundSynthesizers.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||||
ShowStorage = Storage.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
|
ShowStorage = Storage.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
ShowPhotos = Photos.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadPhotoThumbnailAsync(PhotoCarouselDisplayItem photoItem)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Stream stream = await _photoCache.GetThumbnailAsync(photoItem.PhotoId);
|
||||||
|
|
||||||
|
var bitmap = new BitmapImage();
|
||||||
|
|
||||||
|
using(IRandomAccessStream randomStream = stream.AsRandomAccessStream())
|
||||||
|
{
|
||||||
|
await bitmap.SetSourceAsync(randomStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
photoItem.ThumbnailImageSource = bitmap;
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error loading photo thumbnail {PhotoId}", photoItem.PhotoId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,3 +458,14 @@ public class StorageDisplayItem
|
|||||||
public string DisplayText { get; set; } = string.Empty;
|
public string DisplayText { get; set; } = string.Empty;
|
||||||
public string TypeNote { get; set; } = string.Empty;
|
public string TypeNote { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Display item for photo carousel
|
||||||
|
/// </summary>
|
||||||
|
public class PhotoCarouselDisplayItem
|
||||||
|
{
|
||||||
|
// Thumbnail constraints
|
||||||
|
public const int ThumbnailMaxSize = 256;
|
||||||
|
public Guid PhotoId { get; set; }
|
||||||
|
public ImageSource? ThumbnailImageSource { get; set; }
|
||||||
|
}
|
||||||
@@ -86,7 +86,14 @@
|
|||||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||||
<ItemsRepeater ItemsSource="{Binding LettersList}"
|
<ItemsRepeater ItemsSource="{Binding LettersList}"
|
||||||
Layout="{StaticResource LettersGridLayout}">
|
Layout="{StaticResource LettersGridLayout}">
|
||||||
<ItemsRepeater.ItemTemplate></ItemsRepeater.ItemTemplate>
|
<ItemsRepeater.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Button Content="{Binding}"
|
||||||
|
Command="{Binding DataContext.NavigateByLetterCommand, ElementName=PageRoot}"
|
||||||
|
CommandParameter="{Binding}"
|
||||||
|
Style="{StaticResource KeyboardKeyButtonStyle}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsRepeater.ItemTemplate>
|
||||||
</ItemsRepeater>
|
</ItemsRepeater>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
@@ -98,7 +105,14 @@
|
|||||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||||
<ItemsRepeater ItemsSource="{Binding YearsList}"
|
<ItemsRepeater ItemsSource="{Binding YearsList}"
|
||||||
Layout="{StaticResource YearsGridLayout}">
|
Layout="{StaticResource YearsGridLayout}">
|
||||||
<ItemsRepeater.ItemTemplate></ItemsRepeater.ItemTemplate>
|
<ItemsRepeater.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Button Content="{Binding}"
|
||||||
|
Command="{Binding DataContext.NavigateByYearCommand, ElementName=PageRoot}"
|
||||||
|
CommandParameter="{Binding}"
|
||||||
|
Style="{StaticResource KeyboardKeyButtonStyle}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsRepeater.ItemTemplate>
|
||||||
</ItemsRepeater>
|
</ItemsRepeater>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
x:Name="PageRoot"
|
x:Name="PageRoot"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:wctui="using:CommunityToolkit.WinUI.UI.Controls"
|
||||||
NavigationCacheMode="Disabled"
|
NavigationCacheMode="Disabled"
|
||||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||||
|
|
||||||
@@ -372,6 +373,37 @@
|
|||||||
</ItemsControl>
|
</ItemsControl>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Photos Carousel (Last element before spacing) -->
|
||||||
|
<StackPanel Visibility="{Binding ShowPhotos}"
|
||||||
|
Spacing="12">
|
||||||
|
<TextBlock Text="Photos"
|
||||||
|
FontSize="14"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Foreground="{ThemeResource TextControlForeground}" />
|
||||||
|
<wctui:Carousel ItemsSource="{Binding Photos}"
|
||||||
|
Height="280"
|
||||||
|
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||||
|
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="8"
|
||||||
|
HorizontalAlignment="Stretch">
|
||||||
|
<wctui:Carousel.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Grid Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
<Image Source="{Binding ThumbnailImageSource}"
|
||||||
|
Stretch="Uniform"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
MaxWidth="256"
|
||||||
|
MaxHeight="256" />
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</wctui:Carousel.ItemTemplate>
|
||||||
|
</wctui:Carousel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<!-- Bottom Spacing -->
|
<!-- Bottom Spacing -->
|
||||||
<Border Height="24" />
|
<Border Height="24" />
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public sealed class MachinePhotoCache
|
|||||||
|
|
||||||
public async Task<Stream> GetThumbnailAsync(Guid photoId)
|
public async Task<Stream> GetThumbnailAsync(Guid photoId)
|
||||||
{
|
{
|
||||||
var filename = $"{photoId}.svg";
|
var filename = $"{photoId}.webp";
|
||||||
|
|
||||||
Stream retStream;
|
Stream retStream;
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ public sealed class MachinePhotoCache
|
|||||||
|
|
||||||
public async Task<Stream> GetPhotoAsync(Guid photoId)
|
public async Task<Stream> GetPhotoAsync(Guid photoId)
|
||||||
{
|
{
|
||||||
var filename = $"{photoId}.svg";
|
var filename = $"{photoId}.webp";
|
||||||
|
|
||||||
Stream retStream;
|
Stream retStream;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Marechai.App.Helpers;
|
using Marechai.App.Helpers;
|
||||||
using Microsoft.Kiota.Abstractions.Serialization;
|
using Microsoft.Kiota.Abstractions.Serialization;
|
||||||
@@ -205,4 +206,39 @@ public class ComputersService
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fetches the list of photo GUIDs for a machine from the API
|
||||||
|
/// </summary>
|
||||||
|
public async Task<List<Guid>> GetMachinePhotosAsync(int machineId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Fetching photos for machine {MachineId} from API", machineId);
|
||||||
|
|
||||||
|
List<Guid?>? photos = await _apiClient.Machines[machineId].Photos.GetAsync();
|
||||||
|
|
||||||
|
if(photos == null || photos.Count == 0)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("No photos found for machine {MachineId}", machineId);
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out null values
|
||||||
|
var validPhotos = photos.Where(p => p.HasValue).Select(p => p!.Value).ToList();
|
||||||
|
|
||||||
|
_logger.LogInformation("Successfully fetched {Count} photos for machine {MachineId}",
|
||||||
|
validPhotos.Count,
|
||||||
|
machineId);
|
||||||
|
|
||||||
|
return validPhotos;
|
||||||
|
}
|
||||||
|
catch(Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error fetching photos for machine {MachineId} from API", machineId);
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user