13 Commits

91 changed files with 8136 additions and 687 deletions

View File

@@ -5,6 +5,7 @@
</PropertyGroup>
<ItemGroup>
<!-- Duplicated packages (also in Directory.Build.props) -->
<PackageVersion Include="Humanizer" Version="2.14.1"/>
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.11"/>
<PackageVersion Include="Microsoft.EntityFrameworkCore.Proxies" Version="9.0.11"/>
<!-- Unique to Marechai.csproj -->

View File

@@ -1,19 +1,23 @@
<Application x:Class="Marechai.App.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Marechai.App.Presentation.Converters">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- Load WinUI resources -->
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<!-- Load Uno.UI.Toolkit resources -->
<ToolkitResources xmlns="using:Uno.Toolkit.UI" />
</ResourceDictionary.MergedDictionaries>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- Load WinUI resources -->
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<!-- Load Uno.UI.Toolkit resources -->
<ToolkitResources xmlns="using:Uno.Toolkit.UI" />
</ResourceDictionary.MergedDictionaries>
<!-- Add resources here -->
<!-- Add resources here -->
<local:ObjectToVisibilityConverter x:Key="ObjectToVisibilityConverter" />
<local:StringToVisibilityConverter x:Key="StringToVisibilityConverter" />
<local:ZeroToVisibilityConverter x:Key="ZeroToVisibilityConverter" />
</ResourceDictionary>
</Application.Resources>
</ResourceDictionary>
</Application.Resources>
</Application>
</Application>

View File

@@ -1,4 +1,6 @@
using System.Net.Http;
using Marechai.App.Presentation.ViewModels;
using Marechai.App.Presentation.Views;
using Marechai.App.Services;
using Microsoft.UI.Xaml;
using Uno.Extensions;
@@ -8,6 +10,12 @@ using Uno.Extensions.Http;
using Uno.Extensions.Localization;
using Uno.Extensions.Navigation;
using Uno.UI;
using CompanyDetailViewModel = Marechai.App.Presentation.ViewModels.CompanyDetailViewModel;
using ComputersListViewModel = Marechai.App.Presentation.ViewModels.ComputersListViewModel;
using ComputersViewModel = Marechai.App.Presentation.ViewModels.ComputersViewModel;
using MachineViewViewModel = Marechai.App.Presentation.ViewModels.MachineViewViewModel;
using MainViewModel = Marechai.App.Presentation.ViewModels.MainViewModel;
using NewsViewModel = Marechai.App.Presentation.ViewModels.NewsViewModel;
namespace Marechai.App;
@@ -100,6 +108,26 @@ public partial class App : Application
// Register application services
services.AddSingleton<NewsService>();
services.AddSingleton<NewsViewModel>();
services.AddSingleton<ComputersService>();
services.AddSingleton<ComputersViewModel>();
services.AddSingleton<ConsolesService>();
services.AddSingleton<ConsolesViewModel>();
services.AddSingleton<CompaniesService>();
services.AddSingleton<CompaniesViewModel>();
services.AddSingleton<CompanyDetailService>();
services.AddSingleton<CompanyDetailViewModel>();
services.AddSingleton<MachineViewViewModel>();
services
.AddSingleton<IComputersListFilterContext,
ComputersListFilterContext>();
services
.AddSingleton<IConsolesListFilterContext,
ConsolesListFilterContext>();
services.AddTransient<ComputersListViewModel>();
services.AddTransient<ConsolesListViewModel>();
})
.UseNavigation(RegisterRoutes));
@@ -117,14 +145,58 @@ public partial class App : Application
{
views.Register(new ViewMap(ViewModel: typeof(ShellViewModel)),
new ViewMap<MainPage, MainViewModel>(),
new ViewMap<NewsPage, NewsViewModel>(),
new ViewMap<ComputersPage, ComputersViewModel>(),
new ViewMap<ComputersListPage, ComputersListViewModel>(),
new ViewMap<ConsolesPage, ConsolesViewModel>(),
new ViewMap<ConsolesListPage, ConsolesListViewModel>(),
new ViewMap<CompaniesPage, CompaniesViewModel>(),
new ViewMap<CompanyDetailPage, CompanyDetailViewModel>(),
new ViewMap<MachineViewPage, MachineViewViewModel>(),
new DataViewMap<SecondPage, SecondViewModel, Entity>());
routes.Register(new RouteMap("",
views.FindByViewModel<ShellViewModel>(),
Nested:
[
new RouteMap("Main", views.FindByViewModel<MainViewModel>(), true),
new RouteMap("Second", views.FindByViewModel<SecondViewModel>())
new RouteMap("Main",
views.FindByViewModel<MainViewModel>(),
true,
Nested:
[
new RouteMap("News",
views.FindByViewModel<NewsViewModel>(),
true),
new RouteMap("computers",
views.FindByViewModel<ComputersViewModel>(),
Nested:
[
new RouteMap("list-computers",
views.FindByViewModel<
ComputersListViewModel>()),
new RouteMap("view",
views.FindByViewModel<
MachineViewViewModel>())
]),
new RouteMap("consoles",
views.FindByViewModel<ConsolesViewModel>(),
Nested:
[
new RouteMap("list-consoles",
views.FindByViewModel<
ConsolesListViewModel>())
]),
new RouteMap("companies",
views.FindByViewModel<CompaniesViewModel>(),
Nested:
[
new RouteMap("detail",
views.FindByViewModel<
CompanyDetailViewModel>())
]),
new RouteMap("Second",
views.FindByViewModel<SecondViewModel>())
])
]));
}
}

View File

@@ -0,0 +1,61 @@
using Microsoft.Kiota.Abstractions.Serialization;
namespace Marechai.App.Helpers;
/// <summary>
/// Helper class for extracting values from Kiota UntypedNode objects.
/// </summary>
public static class UntypedNodeExtractor
{
/// <summary>
/// Extracts an integer value from an UntypedNode.
/// </summary>
/// <param name="node">The UntypedNode to extract from. Can be null.</param>
/// <returns>The extracted integer value, or 0 if extraction fails.</returns>
public static int ExtractInt(UntypedNode? node)
{
if(node == null) return 0;
try
{
// Cast to UntypedInteger to access the Value property
if(node is UntypedInteger intNode) return intNode.GetValue();
// Fallback: try to parse ToString() result
var stringValue = node.ToString();
if(!string.IsNullOrWhiteSpace(stringValue) && int.TryParse(stringValue, out int result)) return result;
return 0;
}
catch
{
return 0;
}
}
/// <summary>
/// Extracts a long value from an UntypedNode.
/// </summary>
/// <param name="node">The UntypedNode to extract from. Can be null.</param>
/// <returns>The extracted long value, or 0 if extraction fails.</returns>
public static long ExtractLong(UntypedNode? node)
{
if(node == null) return 0;
try
{
if(node is UntypedInteger intNode) return intNode.GetValue();
var stringValue = node.ToString();
if(!string.IsNullOrWhiteSpace(stringValue) && long.TryParse(stringValue, out long result)) return result;
return 0;
}
catch
{
return 0;
}
}
}

View File

@@ -41,5 +41,42 @@
<ItemGroup>
<ProjectReference Include="..\Marechai.Data\Marechai.Data.csproj"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Humanizer"/>
</ItemGroup>
<ItemGroup>
<Compile Update="Presentation\Views\Shell.xaml.cs">
<DependentUpon>Shell.xaml</DependentUpon>
<IsDefaultItem>true</IsDefaultItem>
</Compile>
<Compile Update="Presentation\Views\SecondPage.xaml.cs">
<DependentUpon>SecondPage.xaml</DependentUpon>
<IsDefaultItem>true</IsDefaultItem>
</Compile>
<Compile Update="Presentation\Views\ComputersPage.xaml.cs">
<DependentUpon>ComputersPage.xaml</DependentUpon>
<IsDefaultItem>true</IsDefaultItem>
</Compile>
<Compile Update="Presentation\Views\MainPage.xaml.cs">
<DependentUpon>MainPage.xaml</DependentUpon>
<IsDefaultItem>true</IsDefaultItem>
</Compile>
<Compile Update="Presentation\Views\ComputersListPage.xaml.cs">
<DependentUpon>ComputersListPage.xaml</DependentUpon>
<IsDefaultItem>true</IsDefaultItem>
</Compile>
<Compile Update="Presentation\Views\NewsPage.xaml.cs">
<DependentUpon>NewsPage.xaml</DependentUpon>
<IsDefaultItem>true</IsDefaultItem>
</Compile>
<Compile Update="Presentation\Views\MachineViewPage.xaml.cs">
<DependentUpon>MachineViewPage.xaml</DependentUpon>
<IsDefaultItem>true</IsDefaultItem>
</Compile>
<Compile Update="Presentation\Components\Sidebar.xaml.cs">
<DependentUpon>Sidebar.xaml</DependentUpon>
<IsDefaultItem>true</IsDefaultItem>
</Compile>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,260 @@
<?xml version="1.0"
encoding="utf-8"?>
<UserControl x:Class="Marechai.App.Presentation.Components.Sidebar"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="using:Marechai.App.Presentation.Converters"
mc:Ignorable="d"
d:DesignHeight="600"
d:DesignWidth="280"
Background="{ThemeResource NavigationViewDefaultPaneBackground}">
<UserControl.Resources>
<local:CollapseExpandIconConverter x:Key="CollapseExpandIconConverter" />
<local:CollapseExpandTooltipConverter x:Key="CollapseExpandTooltipConverter" />
</UserControl.Resources>
<!-- Grid container - naturally responds to parent column width -->
<Grid RowSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- Sidebar Header with Collapse/Expand Button -->
<Grid Grid.Row="0"
Padding="8,8,8,8"
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
BorderThickness="0,0,0,1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- Sidebar Title - Hidden when collapsed -->
<TextBlock Grid.Column="0"
Text="Navigation"
FontSize="16"
FontWeight="SemiBold"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
VerticalAlignment="Center"
Padding="4,0,0,0"
Visibility="{Binding SidebarContentVisible}" />
<!-- Collapse/Expand Button - Always visible -->
<Button Grid.Column="1"
Content="{Binding IsSidebarOpen, Converter={StaticResource CollapseExpandIconConverter}}"
Command="{Binding ToggleSidebarCommand}"
Background="Transparent"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Padding="8,8,8,8"
CornerRadius="4"
ToolTipService.ToolTip="{Binding IsSidebarOpen, Converter={StaticResource CollapseExpandTooltipConverter}}"
FontSize="14"
MinWidth="40"
MinHeight="40"
HorizontalAlignment="Center" />
</Grid>
<!-- Scrollable Navigation Items - Hidden when collapsed -->
<ScrollViewer Grid.Row="1"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled"
Padding="0"
Visibility="{Binding SidebarContentVisible}">
<StackPanel Orientation="Vertical"
Spacing="0"
Padding="0">
<!-- News -->
<Button Content="{Binding LocalizedStrings[News]}"
Command="{Binding NavigateToNewsCommand}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Padding="16,10,16,10"
FontSize="13"
Background="Transparent"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
BorderThickness="0"
CornerRadius="0" />
<!-- Books -->
<Button Content="{Binding LocalizedStrings[Books]}"
Command="{Binding NavigateToBooksCommand}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Padding="16,10,16,10"
FontSize="13"
Background="Transparent"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
BorderThickness="0"
CornerRadius="0" />
<!-- Companies -->
<Button Content="{Binding LocalizedStrings[Companies]}"
Command="{Binding NavigateToCompaniesCommand}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Padding="16,10,16,10"
FontSize="13"
Background="Transparent"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
BorderThickness="0"
CornerRadius="0" />
<!-- Computers -->
<Button Content="{Binding LocalizedStrings[Computers]}"
Command="{Binding NavigateToComputersCommand}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Padding="16,10,16,10"
FontSize="13"
Background="Transparent"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
BorderThickness="0"
CornerRadius="0" />
<!-- Consoles -->
<Button Content="{Binding LocalizedStrings[Consoles]}"
Command="{Binding NavigateToConsolesCommand}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Padding="16,10,16,10"
FontSize="13"
Background="Transparent"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
BorderThickness="0"
CornerRadius="0" />
<!-- Documents -->
<Button Content="{Binding LocalizedStrings[Documents]}"
Command="{Binding NavigateToDocumentsCommand}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Padding="16,10,16,10"
FontSize="13"
Background="Transparent"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
BorderThickness="0"
CornerRadius="0" />
<!-- Dumps -->
<Button Content="{Binding LocalizedStrings[Dumps]}"
Command="{Binding NavigateToDumpsCommand}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Padding="16,10,16,10"
FontSize="13"
Background="Transparent"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
BorderThickness="0"
CornerRadius="0" />
<!-- Graphical Processing Units -->
<Button Content="{Binding LocalizedStrings[GraphicalProcessingUnits]}"
Command="{Binding NavigateToGraphicalProcessingUnitsCommand}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Padding="16,10,16,10"
FontSize="13"
Background="Transparent"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
BorderThickness="0"
CornerRadius="0" />
<!-- Magazines -->
<Button Content="{Binding LocalizedStrings[Magazines]}"
Command="{Binding NavigateToMagazinesCommand}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Padding="16,10,16,10"
FontSize="13"
Background="Transparent"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
BorderThickness="0"
CornerRadius="0" />
<!-- People -->
<Button Content="{Binding LocalizedStrings[People]}"
Command="{Binding NavigateToPeopleCommand}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Padding="16,10,16,10"
FontSize="13"
Background="Transparent"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
BorderThickness="0"
CornerRadius="0" />
<!-- Processors -->
<Button Content="{Binding LocalizedStrings[Processors]}"
Command="{Binding NavigateToProcessorsCommand}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Padding="16,10,16,10"
FontSize="13"
Background="Transparent"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
BorderThickness="0"
CornerRadius="0" />
<!-- Software -->
<Button Content="{Binding LocalizedStrings[Software]}"
Command="{Binding NavigateToSoftwareCommand}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Padding="16,10,16,10"
FontSize="13"
Background="Transparent"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
BorderThickness="0"
CornerRadius="0" />
<!-- Sound Synthesizers -->
<Button Content="{Binding LocalizedStrings[SoundSynthesizers]}"
Command="{Binding NavigateToSoundSynthesizersCommand}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Padding="16,10,16,10"
FontSize="13"
Background="Transparent"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
BorderThickness="0"
CornerRadius="0" />
</StackPanel>
</ScrollViewer>
<!-- Bottom Fixed Items - Hidden when collapsed -->
<Grid Grid.Row="2"
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
BorderThickness="0,1,0,0"
Padding="0"
Visibility="{Binding IsSidebarOpen}">
<StackPanel Orientation="Vertical"
Spacing="0">
<!-- Login/Logout -->
<Button Content="{Binding LoginLogoutButtonText}"
Command="{Binding LoginLogoutCommand}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Padding="16,10,16,10"
FontSize="13"
Background="Transparent"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
BorderThickness="0"
CornerRadius="0" />
<!-- Settings -->
<Button Content="{Binding LocalizedStrings[Settings]}"
Command="{Binding NavigateToSettingsCommand}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
Padding="16,10,16,10"
FontSize="13"
Background="Transparent"
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
BorderThickness="0"
CornerRadius="0" />
</StackPanel>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,11 @@
using Microsoft.UI.Xaml.Controls;
namespace Marechai.App.Presentation.Components;
public sealed partial class Sidebar : UserControl
{
public Sidebar()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,51 @@
using System;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
namespace Marechai.App.Presentation.Converters;
/// <summary>
/// Converts null object to Collapsed visibility
/// </summary>
public class ObjectToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language) =>
value != null ? Visibility.Visible : Visibility.Collapsed;
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
throw new NotImplementedException();
}
/// <summary>
/// Converts empty/null string to Collapsed visibility
/// </summary>
public class StringToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if(value is string str && !string.IsNullOrWhiteSpace(str)) return Visibility.Visible;
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
throw new NotImplementedException();
}
/// <summary>
/// Converts zero count to Collapsed visibility, otherwise Visible
/// </summary>
public class ZeroToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if(value is int count && count > 0) return Visibility.Visible;
if(value is long longCount && longCount > 0) return Visibility.Visible;
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
throw new NotImplementedException();
}

View File

@@ -0,0 +1,20 @@
using System;
using Microsoft.UI.Xaml.Data;
namespace Marechai.App.Presentation.Converters;
/// <summary>
/// Converts DateTime to formatted foundation date string, returns empty if null
/// </summary>
public class FoundationDateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if(value is DateTime dateTime) return dateTime.ToString("MMMM d, yyyy");
return string.Empty;
}
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
throw new NotImplementedException();
}

View File

@@ -0,0 +1,59 @@
using System;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Data;
namespace Marechai.App.Presentation.Converters;
/// <summary>
/// Converts boolean value to collapse/expand arrow icon
/// </summary>
public class CollapseExpandIconConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if(value is bool isOpen) return isOpen ? "◄" : "►";
return "►";
}
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
throw new NotImplementedException();
}
/// <summary>
/// Converts boolean value to collapse/expand tooltip text
/// </summary>
public class CollapseExpandTooltipConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if(value is bool isOpen) return isOpen ? "Collapse Sidebar" : "Expand Sidebar";
return "Expand Sidebar";
}
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
throw new NotImplementedException();
}
/// <summary>
/// Converts boolean value to GridLength for sidebar column width
/// </summary>
public class SidebarWidthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if(value is bool isOpen)
{
// 280 when open, 60 when collapsed (to keep toggle button visible)
double width = isOpen ? 280 : 60;
return new GridLength(width, GridUnitType.Pixel);
}
return new GridLength(280, GridUnitType.Pixel);
}
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
throw new NotImplementedException();
}

View File

@@ -1,60 +0,0 @@
using System;
using Windows.Foundation;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation;
namespace Marechai.App.Presentation;
public sealed partial class MainPage : Page
{
private bool _initialNewsLoaded;
public MainPage()
{
InitializeComponent();
DataContextChanged += MainPage_DataContextChanged;
}
private void MainPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
{
if(_initialNewsLoaded) return;
if(args.NewValue is MainViewModel viewModel && viewModel.NewsViewModel is not null)
{
_initialNewsLoaded = true;
_ = viewModel.NewsViewModel.LoadNews.ExecuteAsync(null);
DataContextChanged -= MainPage_DataContextChanged;
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if(_initialNewsLoaded) return;
if(DataContext is MainViewModel viewModel && viewModel.NewsViewModel is not null)
{
_initialNewsLoaded = true;
_ = viewModel.NewsViewModel.LoadNews.ExecuteAsync(null);
DataContextChanged -= MainPage_DataContextChanged;
}
}
private async void RefreshContainer_RefreshRequested(RefreshContainer sender, RefreshRequestedEventArgs args)
{
// Handle pull-to-refresh
using Deferral deferral = args.GetDeferral();
try
{
if(DataContext is MainViewModel viewModel && viewModel.NewsViewModel is not null)
await viewModel.NewsViewModel.LoadNews.ExecuteAsync(null);
}
catch(Exception)
{
// Swallow to avoid process crash; NewsViewModel already logs errors.
}
}
}

View File

@@ -1,35 +0,0 @@
using System.Threading.Tasks;
using System.Windows.Input;
using Uno.Extensions.Navigation;
namespace Marechai.App.Presentation;
public partial class MainViewModel : ObservableObject
{
private readonly INavigator _navigator;
[ObservableProperty]
private string? name;
[ObservableProperty]
private NewsViewModel? newsViewModel;
public MainViewModel(IStringLocalizer localizer, IOptions<AppConfig> appInfo, INavigator navigator,
NewsViewModel newsViewModel)
{
_navigator = navigator;
NewsViewModel = newsViewModel;
Title = "Marechai";
Title += $" - {localizer["ApplicationName"]}";
Title += $" - {appInfo?.Value?.Environment}";
GoToSecond = new AsyncRelayCommand(GoToSecondView);
}
public string? Title { get; }
public ICommand GoToSecond { get; }
private async Task GoToSecondView()
{
await _navigator.NavigateViewModelAsync<SecondViewModel>(this, data: new Entity(Name!));
}
}

View File

@@ -0,0 +1,35 @@
/******************************************************************************
// MARECHAI: Master repository of computing history artifacts information
// ----------------------------------------------------------------------------
//
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// --[ License ] --------------------------------------------------------------
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2003-2026 Natalia Portillo
*******************************************************************************/
namespace Marechai.App.Presentation.Models;
/// <summary>
/// Navigation parameter for the CompanyDetailPage containing both the company ID and the navigation source.
/// </summary>
public class CompanyDetailNavigationParameter
{
public required int CompanyId { get; init; }
public object? NavigationSource { get; init; }
}

View File

@@ -0,0 +1,35 @@
/******************************************************************************
// MARECHAI: Master repository of computing history artifacts information
// ----------------------------------------------------------------------------
//
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// --[ License ] --------------------------------------------------------------
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2003-2026 Natalia Portillo
*******************************************************************************/
namespace Marechai.App.Presentation.Models;
/// <summary>
/// Navigation parameter for the MachineViewPage containing both the machine ID and the navigation source.
/// </summary>
public class MachineViewNavigationParameter
{
public required int MachineId { get; init; }
public object? NavigationSource { get; init; }
}

View File

@@ -1,108 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Marechai.App.Services;
using Marechai.Data;
namespace Marechai.App.Presentation;
/// <summary>
/// Wrapper for NewsDto with generated display text
/// </summary>
public class NewsItemViewModel
{
public required NewsDto News { get; init; }
public required string DisplayText { get; init; }
}
public partial class NewsViewModel : ObservableObject
{
private readonly IStringLocalizer _localizer;
private readonly ILogger<NewsViewModel> _logger;
private readonly NewsService _newsService;
[ObservableProperty]
private string errorMessage = string.Empty;
[ObservableProperty]
private bool hasError;
[ObservableProperty]
private bool isLoading;
[ObservableProperty]
private ObservableCollection<NewsItemViewModel> newsList = new();
public NewsViewModel(NewsService newsService, IStringLocalizer localizer, ILogger<NewsViewModel> logger)
{
_newsService = newsService;
_localizer = localizer;
_logger = logger;
LoadNews = new AsyncRelayCommand(LoadNewsAsync);
}
public IAsyncRelayCommand LoadNews { get; }
/// <summary>
/// Generates localized text based on NewsType
/// </summary>
private string GetLocalizedTextForNewsType(NewsType type)
{
return type switch
{
NewsType.NewComputerInDb => _localizer["New computer in database"].Value,
NewsType.NewConsoleInDb => _localizer["New console in database"].Value,
NewsType.NewComputerInCollection => _localizer["New computer in collection"].Value,
NewsType.NewConsoleInCollection => _localizer["New console in collection"].Value,
NewsType.UpdatedComputerInDb => _localizer["Updated computer in database"].Value,
NewsType.UpdatedConsoleInDb => _localizer["Updated console in database"].Value,
NewsType.UpdatedComputerInCollection => _localizer["Updated computer in collection"].Value,
NewsType.UpdatedConsoleInCollection => _localizer["Updated console in collection"].Value,
_ => string.Empty
};
}
/// <summary>
/// Loads the latest news from the API
/// </summary>
private async Task LoadNewsAsync()
{
try
{
IsLoading = true;
ErrorMessage = string.Empty;
HasError = false;
NewsList.Clear();
List<NewsDto> news = await _newsService.GetLatestNewsAsync();
if(news.Count == 0)
{
ErrorMessage = _localizer["No news available"].Value;
HasError = true;
}
else
{
foreach(NewsDto item in news)
{
NewsList.Add(new NewsItemViewModel
{
News = item,
DisplayText = GetLocalizedTextForNewsType((NewsType)(item.Type ?? 0))
});
}
}
}
catch(Exception ex)
{
_logger.LogError("Error loading news: {Exception}", ex.Message);
ErrorMessage = _localizer["Failed to load news. Please try again later."].Value;
HasError = true;
}
finally
{
IsLoading = false;
}
}
}

View File

@@ -1,27 +0,0 @@
<Page x:Class="Marechai.App.Presentation.SecondPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Marechai.App.Presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uen="using:Uno.Extensions.Navigation.UI"
xmlns:utu="using:Uno.Toolkit.UI"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid utu:SafeArea.Insets="VisibleBounds">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<utu:NavigationBar Content="Second Page" />
<StackPanel Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock Text="{Binding Entity.Name}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="8" />
</StackPanel>
</Grid>
</Page>

View File

@@ -1,5 +0,0 @@
namespace Marechai.App.Presentation;
public partial record SecondViewModel(Entity Entity)
{
}

View File

@@ -1,36 +0,0 @@
<UserControl x:Class="Marechai.App.Presentation.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Marechai.App.Presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:utu="using:Uno.Toolkit.UI"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Border Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<utu:ExtendedSplashScreen x:Name="Splash"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch">
<utu:ExtendedSplashScreen.LoadingContentTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition />
</Grid.RowDefinitions>
<ProgressRing IsActive="True"
Grid.Row="1"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Height="100"
Width="100" />
</Grid>
</DataTemplate>
</utu:ExtendedSplashScreen.LoadingContentTemplate>
</utu:ExtendedSplashScreen>
</Border>
</UserControl>

View File

@@ -1,15 +0,0 @@
using Uno.Extensions.Navigation;
namespace Marechai.App.Presentation;
public class ShellViewModel
{
private readonly INavigator _navigator;
public ShellViewModel(
INavigator navigator)
{
_navigator = navigator;
// Add code here to initialize or attach event handlers to singleton services
}
}

View File

@@ -0,0 +1,195 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Marechai.App.Helpers;
using Marechai.App.Presentation.Models;
using Marechai.App.Services;
using Uno.Extensions.Navigation;
namespace Marechai.App.Presentation.ViewModels;
public partial class CompaniesViewModel : ObservableObject
{
private readonly List<CompanyListItem> _allCompanies = [];
private readonly CompaniesService _companiesService;
private readonly IStringLocalizer _localizer;
private readonly ILogger<CompaniesViewModel> _logger;
private readonly INavigator _navigator;
[ObservableProperty]
private ObservableCollection<CompanyListItem> _companiesList = [];
[ObservableProperty]
private int _companyCount;
[ObservableProperty]
private string _companyCountText = string.Empty;
[ObservableProperty]
private string _errorMessage = string.Empty;
[ObservableProperty]
private bool _hasError;
[ObservableProperty]
private bool _isDataLoaded;
[ObservableProperty]
private bool _isLoading;
[ObservableProperty]
private string _searchQuery = string.Empty;
public CompaniesViewModel(CompaniesService companiesService, IStringLocalizer localizer,
ILogger<CompaniesViewModel> logger, INavigator navigator)
{
_companiesService = companiesService;
_localizer = localizer;
_logger = logger;
_navigator = navigator;
LoadData = new AsyncRelayCommand(LoadDataAsync);
GoBackCommand = new AsyncRelayCommand(GoBackAsync);
NavigateToCompanyCommand = new AsyncRelayCommand<CompanyListItem>(NavigateToCompanyAsync);
}
public IAsyncRelayCommand LoadData { get; }
public ICommand GoBackCommand { get; }
public IAsyncRelayCommand<CompanyListItem> NavigateToCompanyCommand { get; }
public string Title { get; } = "Companies";
partial void OnSearchQueryChanged(string value)
{
// Automatically filter when SearchQuery changes
UpdateFilter(value);
}
/// <summary>
/// Loads companies count and list from the API
/// </summary>
private async Task LoadDataAsync()
{
try
{
IsLoading = true;
ErrorMessage = string.Empty;
HasError = false;
IsDataLoaded = false;
CompaniesList.Clear();
_allCompanies.Clear();
// Load companies
List<CompanyDto> companies = await _companiesService.GetAllCompaniesAsync();
// Set count
CompanyCount = companies.Count;
CompanyCountText = _localizer["Companies in the database"];
// Build the full list in memory
foreach(CompanyDto company in companies)
{
// Extract id from UntypedNode
int companyId = UntypedNodeExtractor.ExtractInt(company.Id);
// Convert DateTimeOffset? to DateTime?
DateTime? foundedDate = company.Founded?.DateTime;
_allCompanies.Add(new CompanyListItem
{
Id = companyId,
Name = company.Name ?? string.Empty,
FoundationDate = foundedDate
});
}
// Apply current filter (will show all if SearchQuery is empty)
UpdateFilter(SearchQuery);
if(CompaniesList.Count == 0)
{
ErrorMessage = _localizer["No companies found"].Value;
HasError = true;
}
else
IsDataLoaded = true;
}
catch(Exception ex)
{
_logger.LogError("Error loading companies data: {Exception}", ex.Message);
ErrorMessage = _localizer["Failed to load companies data. Please try again later."].Value;
HasError = true;
}
finally
{
IsLoading = false;
}
}
/// <summary>
/// Handles back navigation
/// </summary>
private async Task GoBackAsync()
{
await _navigator.NavigateViewModelAsync<MainViewModel>(this);
}
/// <summary>
/// Navigates to company detail view
/// </summary>
private async Task NavigateToCompanyAsync(CompanyListItem? company)
{
if(company is null) return;
_logger.LogInformation("Navigating to company: {CompanyName} (ID: {CompanyId})", company.Name, company.Id);
// Navigate to company detail view with navigation parameter
var navParam = new CompanyDetailNavigationParameter
{
CompanyId = company.Id,
NavigationSource = this
};
await _navigator.NavigateViewModelAsync<CompanyDetailViewModel>(this, data: navParam);
}
/// <summary>
/// Updates the filtered list based on search query
/// </summary>
private void UpdateFilter(string? query)
{
string lowerQuery = string.IsNullOrWhiteSpace(query) ? string.Empty : query.Trim().ToLowerInvariant();
CompaniesList.Clear();
if(string.IsNullOrEmpty(lowerQuery))
{
// No filter, show all companies
foreach(CompanyListItem company in _allCompanies) CompaniesList.Add(company);
}
else
{
// Filter companies by name (case-insensitive)
var filtered = _allCompanies.Where(c => c.Name.Contains(lowerQuery, StringComparison.OrdinalIgnoreCase))
.ToList();
foreach(CompanyListItem company in filtered) CompaniesList.Add(company);
}
}
}
/// <summary>
/// Data model for a company in the list
/// </summary>
public class CompanyListItem
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public DateTime? FoundationDate { get; set; }
public string FoundationDateDisplay =>
FoundationDate.HasValue ? FoundationDate.Value.ToString("MMMM d, yyyy") : string.Empty;
}

View File

@@ -0,0 +1,373 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Marechai.App.Helpers;
using Marechai.App.Presentation.Models;
using Marechai.App.Services;
using Marechai.Data;
using Uno.Extensions.Navigation;
namespace Marechai.App.Presentation.ViewModels;
public partial class CompanyDetailViewModel : ObservableObject
{
private readonly CompanyDetailService _companyDetailService;
private readonly IStringLocalizer _localizer;
private readonly ILogger<CompanyDetailViewModel> _logger;
private readonly INavigator _navigator;
[ObservableProperty]
private CompanyDto? _company;
[ObservableProperty]
private int _companyId;
[ObservableProperty]
private ObservableCollection<CompanyDetailMachine> _computers = [];
[ObservableProperty]
private string _computersFilterText = string.Empty;
[ObservableProperty]
private string _consoelsFilterText = string.Empty;
[ObservableProperty]
private ObservableCollection<CompanyDetailMachine> _consoles = [];
[ObservableProperty]
private string _errorMessage = string.Empty;
[ObservableProperty]
private ObservableCollection<CompanyDetailMachine> _filteredComputers = [];
[ObservableProperty]
private ObservableCollection<CompanyDetailMachine> _filteredConsoles = [];
[ObservableProperty]
private bool _hasError;
[ObservableProperty]
private bool _isDataLoaded;
[ObservableProperty]
private bool _isLoading;
[ObservableProperty]
private CompanyDto? _soldToCompany;
public CompanyDetailViewModel(CompanyDetailService companyDetailService, IStringLocalizer localizer,
ILogger<CompanyDetailViewModel> logger, INavigator navigator)
{
_companyDetailService = companyDetailService;
_localizer = localizer;
_logger = logger;
_navigator = navigator;
LoadData = new AsyncRelayCommand(LoadDataAsync);
GoBackCommand = new AsyncRelayCommand(GoBackAsync);
NavigateToMachineCommand = new AsyncRelayCommand<CompanyDetailMachine>(NavigateToMachineAsync);
}
/// <summary>
/// Gets the display text for the company's status
/// </summary>
public string CompanyStatusDisplay => Company != null ? GetStatusMessage(Company) : string.Empty;
/// <summary>
/// Gets the display text for the company's founded date
/// </summary>
public string CompanyFoundedDateDisplay => Company != null ? GetFoundedDateDisplay(Company) : string.Empty;
public IAsyncRelayCommand LoadData { get; }
public ICommand GoBackCommand { get; }
public IAsyncRelayCommand<CompanyDetailMachine> NavigateToMachineCommand { get; }
public string Title { get; } = "Company Details";
partial void OnCompanyChanged(CompanyDto? oldValue, CompanyDto? newValue)
{
// Notify that computed properties have changed
OnPropertyChanged(nameof(CompanyStatusDisplay));
OnPropertyChanged(nameof(CompanyFoundedDateDisplay));
}
partial void OnComputersFilterTextChanged(string value)
{
FilterComputers(value);
}
partial void OnConsoelsFilterTextChanged(string value)
{
FilterConsoles(value);
}
private void FilterComputers(string filterText)
{
ObservableCollection<CompanyDetailMachine> filtered = string.IsNullOrWhiteSpace(filterText)
? new ObservableCollection<
CompanyDetailMachine>(Computers)
: new
ObservableCollection<
CompanyDetailMachine>(Computers.Where(c =>
c.Name.Contains(filterText,
StringComparison
.OrdinalIgnoreCase)));
FilteredComputers = filtered;
}
private void FilterConsoles(string filterText)
{
ObservableCollection<CompanyDetailMachine> filtered = string.IsNullOrWhiteSpace(filterText)
? new ObservableCollection<
CompanyDetailMachine>(Consoles)
: new
ObservableCollection<
CompanyDetailMachine>(Consoles.Where(c =>
c.Name.Contains(filterText,
StringComparison
.OrdinalIgnoreCase)));
FilteredConsoles = filtered;
}
private async Task NavigateToMachineAsync(CompanyDetailMachine? machine)
{
if(machine == null) return;
var navParam = new MachineViewNavigationParameter
{
MachineId = machine.Id,
NavigationSource = this
};
await _navigator.NavigateViewModelAsync<MachineViewViewModel>(this, data: navParam);
}
/// <summary>
/// Gets the formatted founding date with unknown handling
/// </summary>
public string GetFoundedDateDisplay(CompanyDto company)
{
if(company.Founded is null) return string.Empty;
DateTime date = company.Founded.Value.DateTime;
if(company.FoundedMonthIsUnknown ?? false) return $"{date.Year}.";
if(company.FoundedDayIsUnknown ?? false) return $"{date:Y}.";
return $"{date:D}.";
}
/// <summary>
/// Gets the formatted sold/event date with unknown handling
/// </summary>
public string GetEventDateDisplay(CompanyDto? company, bool monthUnknown = false, bool dayUnknown = false)
{
if(company?.Sold is null) return _localizer["unknown date"].Value;
DateTime date = company.Sold.Value.DateTime;
if(monthUnknown || (company.SoldMonthIsUnknown ?? false)) return $"{date.Year}";
if(dayUnknown || (company.SoldDayIsUnknown ?? false)) return $"{date:Y}";
return $"{date:D}";
}
/// <summary>
/// Gets the status message for the company
/// </summary>
public string GetStatusMessage(CompanyDto company)
{
return company.Status switch
{
1 => _localizer["Company is active."].Value,
2 => GetSoldStatusMessage(company),
3 => GetMergedStatusMessage(company),
4 => GetBankruptcyMessage(company),
5 => GetDefunctMessage(company),
6 => GetRenamedStatusMessage(company),
_ => _localizer["Current company status is unknown."].Value
};
}
private string GetSoldStatusMessage(CompanyDto company)
{
if(SoldToCompany != null)
{
return string.Format(_localizer["Company sold to {0} on {1}."].Value,
SoldToCompany.Name,
GetEventDateDisplay(company));
}
if(company.Sold != null)
{
return string.Format(_localizer["Company sold on {0} to an unknown company."].Value,
GetEventDateDisplay(company));
}
return SoldToCompany != null
? string.Format(_localizer["Company sold to {0} on an unknown date."].Value, SoldToCompany.Name)
: _localizer["Company was sold to an unknown company on an unknown date."].Value;
}
private string GetMergedStatusMessage(CompanyDto company)
{
if(SoldToCompany != null)
{
return string.Format(_localizer["Company merged on {0} to form {1}."].Value,
GetEventDateDisplay(company),
SoldToCompany.Name);
}
if(company.Sold != null)
{
return string.Format(_localizer["Company merged on {0} to form an unknown company."].Value,
GetEventDateDisplay(company));
}
return SoldToCompany != null
? string.Format(_localizer["Company merged on an unknown date to form {0}."].Value,
SoldToCompany.Name)
: _localizer["Company merged to form an unknown company on an unknown date."].Value;
}
private string GetBankruptcyMessage(CompanyDto company) => company.Sold != null
? string.Format(_localizer
["Company declared bankruptcy on {0}."]
.Value,
GetEventDateDisplay(company))
: _localizer
["Company declared bankruptcy on an unknown date."]
.Value;
private string GetDefunctMessage(CompanyDto company) => company.Sold != null
? string.Format(_localizer
["Company ceased operations on {0}."]
.Value,
GetEventDateDisplay(company))
: _localizer
["Company ceased operations on an unknown date."]
.Value;
private string GetRenamedStatusMessage(CompanyDto company)
{
if(SoldToCompany != null)
{
return string.Format(_localizer["Company renamed to {0} on {1}."].Value,
SoldToCompany.Name,
GetEventDateDisplay(company));
}
if(company.Sold != null)
{
return string.Format(_localizer["Company was renamed on {0} to an unknown name."].Value,
GetEventDateDisplay(company));
}
return SoldToCompany != null
? string.Format(_localizer["Company renamed to {0} on an unknown date."].Value, SoldToCompany.Name)
: _localizer["Company renamed to an unknown name on an unknown date."].Value;
}
/// <summary>
/// Loads company details from the API
/// </summary>
private async Task LoadDataAsync()
{
try
{
IsLoading = true;
ErrorMessage = string.Empty;
HasError = false;
IsDataLoaded = false;
if(CompanyId <= 0)
{
ErrorMessage = _localizer["Invalid company ID."].Value;
HasError = true;
return;
}
// Load company details
Company = await _companyDetailService.GetCompanyByIdAsync(CompanyId);
if(Company is null)
{
ErrorMessage = _localizer["Company not found."].Value;
HasError = true;
return;
}
// Load sold-to company if applicable
if(Company.SoldToId != null)
{
int soldToId = UntypedNodeExtractor.ExtractInt(Company.SoldToId);
if(soldToId > 0) SoldToCompany = await _companyDetailService.GetSoldToCompanyAsync(soldToId);
}
// Load computers and consoles made by this company
List<MachineDto> machines = await _companyDetailService.GetComputersByCompanyAsync(CompanyId);
Computers.Clear();
Consoles.Clear();
FilteredComputers.Clear();
FilteredConsoles.Clear();
foreach(MachineDto machine in machines)
{
int machineId = UntypedNodeExtractor.ExtractInt(machine.Id);
var machineItem = new CompanyDetailMachine
{
Id = machineId,
Name = machine.Name ?? string.Empty
};
// Categorize by machine type enum
if(machine.Type == (int)MachineType.Computer)
Computers.Add(machineItem);
else if(machine.Type == (int)MachineType.Console) Consoles.Add(machineItem);
}
// Initialize filtered lists
FilteredComputers = new ObservableCollection<CompanyDetailMachine>(Computers);
FilteredConsoles = new ObservableCollection<CompanyDetailMachine>(Consoles);
IsDataLoaded = true;
}
catch(Exception ex)
{
_logger.LogError("Error loading company details: {Exception}", ex.Message);
ErrorMessage = _localizer["Failed to load company details. Please try again later."].Value;
HasError = true;
}
finally
{
IsLoading = false;
}
}
/// <summary>
/// Handles back navigation
/// </summary>
private async Task GoBackAsync()
{
await _navigator.NavigateViewModelAsync<CompaniesViewModel>(this);
}
}
/// <summary>
/// Data model for a machine in the company detail view
/// </summary>
public class CompanyDetailMachine
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,251 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Marechai.App.Helpers;
using Marechai.App.Presentation.Models;
using Marechai.App.Services;
using Uno.Extensions.Navigation;
namespace Marechai.App.Presentation.ViewModels;
/// <summary>
/// ViewModel for displaying a filtered list of computers
/// </summary>
public partial class ComputersListViewModel : ObservableObject
{
private readonly ComputersService _computersService;
private readonly IComputersListFilterContext _filterContext;
private readonly IStringLocalizer _localizer;
private readonly ILogger<ComputersListViewModel> _logger;
private readonly INavigator _navigator;
[ObservableProperty]
private ObservableCollection<ComputerListItem> _computersList = [];
[ObservableProperty]
private string _errorMessage = string.Empty;
[ObservableProperty]
private string _filterDescription = string.Empty;
[ObservableProperty]
private bool _hasError;
[ObservableProperty]
private bool _isDataLoaded;
[ObservableProperty]
private bool _isLoading;
[ObservableProperty]
private string _pageTitle = string.Empty;
public ComputersListViewModel(ComputersService computersService, IStringLocalizer localizer,
ILogger<ComputersListViewModel> logger, INavigator navigator,
IComputersListFilterContext filterContext)
{
_computersService = computersService;
_localizer = localizer;
_logger = logger;
_navigator = navigator;
_filterContext = filterContext;
LoadData = new AsyncRelayCommand(LoadDataAsync);
GoBackCommand = new AsyncRelayCommand(GoBackAsync);
NavigateToComputerCommand = new AsyncRelayCommand<ComputerListItem>(NavigateToComputerAsync);
}
public IAsyncRelayCommand LoadData { get; }
public ICommand GoBackCommand { get; }
public IAsyncRelayCommand<ComputerListItem> NavigateToComputerCommand { get; }
/// <summary>
/// Gets or sets the filter type
/// </summary>
public ComputerListFilterType FilterType
{
get => _filterContext.FilterType;
set => _filterContext.FilterType = value;
}
/// <summary>
/// Gets or sets the filter value
/// </summary>
public string FilterValue
{
get => _filterContext.FilterValue;
set => _filterContext.FilterValue = value;
}
/// <summary>
/// Loads computers based on the current filter
/// </summary>
private async Task LoadDataAsync()
{
try
{
IsLoading = true;
ErrorMessage = string.Empty;
HasError = false;
IsDataLoaded = false;
ComputersList.Clear();
_logger.LogInformation("LoadDataAsync called. FilterType={FilterType}, FilterValue={FilterValue}",
FilterType,
FilterValue);
// Update title and filter description based on filter type
UpdateFilterDescription();
// Load computers from the API based on the current filter
await LoadComputersFromApiAsync();
_logger.LogInformation("LoadComputersFromApiAsync completed. ComputersList.Count={Count}",
ComputersList.Count);
if(ComputersList.Count == 0)
{
ErrorMessage = _localizer["No computers found for this filter"].Value;
HasError = true;
_logger.LogWarning("No computers found for filter: {FilterType} {FilterValue}",
FilterType,
FilterValue);
}
else
IsDataLoaded = true;
}
catch(Exception ex)
{
_logger.LogError(ex, "Error loading computers: {Exception}", ex.Message);
ErrorMessage = _localizer["Failed to load computers. Please try again later."].Value;
HasError = true;
}
finally
{
IsLoading = false;
}
}
/// <summary>
/// Updates the title and filter description based on the current filter
/// </summary>
private void UpdateFilterDescription()
{
switch(FilterType)
{
case ComputerListFilterType.All:
PageTitle = _localizer["All Computers"];
FilterDescription = _localizer["Browsing all computers in the database"];
break;
case ComputerListFilterType.Letter:
if(!string.IsNullOrEmpty(FilterValue) && FilterValue.Length == 1)
{
PageTitle = $"{_localizer["Computers Starting with"]} {FilterValue}";
FilterDescription = $"{_localizer["Showing computers that start with"]} {FilterValue}";
}
break;
case ComputerListFilterType.Year:
if(!string.IsNullOrEmpty(FilterValue) && int.TryParse(FilterValue, out int year))
{
PageTitle = $"{_localizer["Computers from"]} {year}";
FilterDescription = $"{_localizer["Showing computers released in"]} {year}";
}
break;
}
}
/// <summary>
/// Loads computers from the API based on the current filter
/// </summary>
private async Task LoadComputersFromApiAsync()
{
try
{
List<MachineDto> computers = FilterType switch
{
ComputerListFilterType.Letter when FilterValue.Length == 1 =>
await _computersService.GetComputersByLetterAsync(FilterValue[0]),
ComputerListFilterType.Year when int.TryParse(FilterValue, out int year) =>
await _computersService.GetComputersByYearAsync(year),
_ => await _computersService.GetAllComputersAsync()
};
// Add computers to the list sorted by name
foreach(MachineDto computer in computers.OrderBy(c => c.Name))
{
int year = computer.Introduced?.Year ?? 0;
int id = UntypedNodeExtractor.ExtractInt(computer.Id);
_logger.LogInformation("Computer: {Name}, Introduced: {Introduced}, Year: {Year}, Company: {Company}, ID: {Id}",
computer.Name,
computer.Introduced,
year,
computer.Company,
id);
ComputersList.Add(new ComputerListItem
{
Id = id,
Name = computer.Name ?? string.Empty,
Year = year,
Manufacturer = computer.Company ?? string.Empty
});
}
}
catch(Exception ex)
{
_logger.LogError(ex, "Error loading computers from API");
}
}
/// <summary>
/// Navigates back to the computers main view
/// </summary>
private async Task GoBackAsync()
{
await _navigator.NavigateViewModelAsync<ComputersViewModel>(this);
}
/// <summary>
/// Navigates to the computer detail view
/// </summary>
private async Task NavigateToComputerAsync(ComputerListItem? computer)
{
if(computer is null) return;
_logger.LogInformation("Navigating to computer detail: {ComputerName} (ID: {ComputerId})",
computer.Name,
computer.Id);
var navParam = new MachineViewNavigationParameter
{
MachineId = computer.Id,
NavigationSource = this
};
await _navigator.NavigateViewModelAsync<MachineViewViewModel>(this, data: navParam);
}
}
/// <summary>
/// Data model for a computer in the list
/// </summary>
public class ComputerListItem
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public int Year { get; set; }
public string Manufacturer { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,207 @@
using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using System.Windows.Input;
using Marechai.App.Services;
using Uno.Extensions.Navigation;
namespace Marechai.App.Presentation.ViewModels;
public partial class ComputersViewModel : ObservableObject
{
private readonly ComputersService _computersService;
private readonly IComputersListFilterContext _filterContext;
private readonly IStringLocalizer _localizer;
private readonly ILogger<ComputersViewModel> _logger;
private readonly INavigator _navigator;
[ObservableProperty]
private int _computerCount;
[ObservableProperty]
private string _computerCountText = string.Empty;
[ObservableProperty]
private string _errorMessage = string.Empty;
[ObservableProperty]
private bool _hasError;
[ObservableProperty]
private bool _isDataLoaded;
[ObservableProperty]
private bool _isLoading;
[ObservableProperty]
private ObservableCollection<char> _lettersList = [];
[ObservableProperty]
private int _maximumYear;
[ObservableProperty]
private int _minimumYear;
[ObservableProperty]
private string _yearsGridTitle = string.Empty;
[ObservableProperty]
private ObservableCollection<int> _yearsList = [];
public ComputersViewModel(ComputersService computersService, IStringLocalizer localizer,
ILogger<ComputersViewModel> logger, INavigator navigator,
IComputersListFilterContext filterContext)
{
_computersService = computersService;
_localizer = localizer;
_logger = logger;
_navigator = navigator;
_filterContext = filterContext;
LoadData = new AsyncRelayCommand(LoadDataAsync);
GoBackCommand = new AsyncRelayCommand(GoBackAsync);
NavigateByLetterCommand = new AsyncRelayCommand<char>(NavigateByLetterAsync);
NavigateByYearCommand = new AsyncRelayCommand<int>(NavigateByYearAsync);
NavigateAllComputersCommand = new AsyncRelayCommand(NavigateAllComputersAsync);
InitializeLetters();
}
public IAsyncRelayCommand LoadData { get; }
public ICommand GoBackCommand { get; }
public IAsyncRelayCommand<char> NavigateByLetterCommand { get; }
public IAsyncRelayCommand<int> NavigateByYearCommand { get; }
public IAsyncRelayCommand NavigateAllComputersCommand { get; }
public string Title { get; } = "Computers";
/// <summary>
/// Initializes the alphabet list (A-Z)
/// </summary>
private void InitializeLetters()
{
LettersList.Clear();
for(var c = 'A'; c <= 'Z'; c++) LettersList.Add(c);
}
/// <summary>
/// Loads computers count, minimum and maximum years from the API
/// </summary>
private async Task LoadDataAsync()
{
try
{
IsLoading = true;
ErrorMessage = string.Empty;
HasError = false;
IsDataLoaded = false;
YearsList.Clear();
// Load all data in parallel for better performance
Task<int> countTask = _computersService.GetComputersCountAsync();
Task<int> minYearTask = _computersService.GetMinimumYearAsync();
Task<int> maxYearTask = _computersService.GetMaximumYearAsync();
await Task.WhenAll(countTask, minYearTask, maxYearTask);
ComputerCount = countTask.Result;
MinimumYear = minYearTask.Result;
MaximumYear = maxYearTask.Result;
// Update display text
ComputerCountText = _localizer["Computers in the database"];
// Generate years list
if(MinimumYear > 0 && MaximumYear > 0)
{
for(int year = MinimumYear; year <= MaximumYear; year++) YearsList.Add(year);
YearsGridTitle = $"Browse by Year ({MinimumYear} - {MaximumYear})";
}
if(ComputerCount == 0)
{
ErrorMessage = _localizer["No computers found"].Value;
HasError = true;
}
else
IsDataLoaded = true;
}
catch(Exception ex)
{
_logger.LogError("Error loading computers data: {Exception}", ex.Message);
ErrorMessage = _localizer["Failed to load computers data. Please try again later."].Value;
HasError = true;
}
finally
{
IsLoading = false;
}
}
/// <summary>
/// Handles back navigation
/// </summary>
private async Task GoBackAsync()
{
await _navigator.NavigateViewModelAsync<MainViewModel>(this);
}
/// <summary>
/// Navigates to computers filtered by letter
/// </summary>
private async Task NavigateByLetterAsync(char letter)
{
try
{
_logger.LogInformation("Navigating to computers by letter: {Letter}", letter);
_filterContext.FilterType = ComputerListFilterType.Letter;
_filterContext.FilterValue = letter.ToString();
await _navigator.NavigateRouteAsync(this, "list-computers");
}
catch(Exception ex)
{
_logger.LogError("Error navigating to letter computers: {Exception}", ex.Message);
ErrorMessage = _localizer["Failed to navigate. Please try again."].Value;
HasError = true;
}
}
/// <summary>
/// Navigates to computers filtered by year
/// </summary>
private async Task NavigateByYearAsync(int year)
{
try
{
_logger.LogInformation("Navigating to computers by year: {Year}", year);
_filterContext.FilterType = ComputerListFilterType.Year;
_filterContext.FilterValue = year.ToString();
await _navigator.NavigateRouteAsync(this, "list-computers");
}
catch(Exception ex)
{
_logger.LogError("Error navigating to year computers: {Exception}", ex.Message);
ErrorMessage = _localizer["Failed to navigate. Please try again."].Value;
HasError = true;
}
}
/// <summary>
/// Navigates to all computers view
/// </summary>
private async Task NavigateAllComputersAsync()
{
try
{
_logger.LogInformation("Navigating to all computers");
_filterContext.FilterType = ComputerListFilterType.All;
_filterContext.FilterValue = string.Empty;
await _navigator.NavigateRouteAsync(this, "list-computers");
}
catch(Exception ex)
{
_logger.LogError("Error navigating to all computers: {Exception}", ex.Message);
ErrorMessage = _localizer["Failed to navigate. Please try again."].Value;
HasError = true;
}
}
}

View File

@@ -0,0 +1,249 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using Marechai.App.Helpers;
using Marechai.App.Presentation.Models;
using Marechai.App.Services;
using Uno.Extensions.Navigation;
namespace Marechai.App.Presentation.ViewModels;
/// <summary>
/// ViewModel for displaying a filtered list of consoles
/// </summary>
public partial class ConsolesListViewModel : ObservableObject
{
private readonly ConsolesService _consolesService;
private readonly IConsolesListFilterContext _filterContext;
private readonly IStringLocalizer _localizer;
private readonly ILogger<ConsolesListViewModel> _logger;
private readonly INavigator _navigator;
[ObservableProperty]
private ObservableCollection<ConsoleListItem> _consolesList = [];
[ObservableProperty]
private string _errorMessage = string.Empty;
[ObservableProperty]
private string _filterDescription = string.Empty;
[ObservableProperty]
private bool _hasError;
[ObservableProperty]
private bool _isDataLoaded;
[ObservableProperty]
private bool _isLoading;
[ObservableProperty]
private string _pageTitle = string.Empty;
public ConsolesListViewModel(ConsolesService consolesService, IStringLocalizer localizer,
ILogger<ConsolesListViewModel> logger, INavigator navigator,
IConsolesListFilterContext filterContext)
{
_consolesService = consolesService;
_localizer = localizer;
_logger = logger;
_navigator = navigator;
_filterContext = filterContext;
LoadData = new AsyncRelayCommand(LoadDataAsync);
GoBackCommand = new AsyncRelayCommand(GoBackAsync);
NavigateToConsoleCommand = new AsyncRelayCommand<ConsoleListItem>(NavigateToConsoleAsync);
}
public IAsyncRelayCommand LoadData { get; }
public ICommand GoBackCommand { get; }
public IAsyncRelayCommand<ConsoleListItem> NavigateToConsoleCommand { get; }
/// <summary>
/// Gets or sets the filter type
/// </summary>
public ConsoleListFilterType FilterType
{
get => _filterContext.FilterType;
set => _filterContext.FilterType = value;
}
/// <summary>
/// Gets or sets the filter value
/// </summary>
public string FilterValue
{
get => _filterContext.FilterValue;
set => _filterContext.FilterValue = value;
}
/// <summary>
/// Loads consoles based on the current filter
/// </summary>
private async Task LoadDataAsync()
{
try
{
IsLoading = true;
ErrorMessage = string.Empty;
HasError = false;
IsDataLoaded = false;
ConsolesList.Clear();
_logger.LogInformation("LoadDataAsync called. FilterType={FilterType}, FilterValue={FilterValue}",
FilterType,
FilterValue);
// Update title and filter description based on filter type
UpdateFilterDescription();
// Load consoles from the API based on the current filter
await LoadConsolesFromApiAsync();
_logger.LogInformation("LoadConsolesFromApiAsync completed. ConsolesList.Count={Count}",
ConsolesList.Count);
if(ConsolesList.Count == 0)
{
ErrorMessage = _localizer["No consoles found for this filter"].Value;
HasError = true;
_logger.LogWarning("No consoles found for filter: {FilterType} {FilterValue}", FilterType, FilterValue);
}
else
IsDataLoaded = true;
}
catch(Exception ex)
{
_logger.LogError(ex, "Error loading consoles: {Exception}", ex.Message);
ErrorMessage = _localizer["Failed to load consoles. Please try again later."].Value;
HasError = true;
}
finally
{
IsLoading = false;
}
}
/// <summary>
/// Updates the title and filter description based on the current filter
/// </summary>
private void UpdateFilterDescription()
{
switch(FilterType)
{
case ConsoleListFilterType.All:
PageTitle = _localizer["All Consoles"];
FilterDescription = _localizer["Browsing all consoles in the database"];
break;
case ConsoleListFilterType.Letter:
if(!string.IsNullOrEmpty(FilterValue) && FilterValue.Length == 1)
{
PageTitle = $"{_localizer["Consoles Starting with"]} {FilterValue}";
FilterDescription = $"{_localizer["Showing consoles that start with"]} {FilterValue}";
}
break;
case ConsoleListFilterType.Year:
if(!string.IsNullOrEmpty(FilterValue) && int.TryParse(FilterValue, out int year))
{
PageTitle = $"{_localizer["Consoles from"]} {year}";
FilterDescription = $"{_localizer["Showing consoles released in"]} {year}";
}
break;
}
}
/// <summary>
/// Loads consoles from the API based on the current filter
/// </summary>
private async Task LoadConsolesFromApiAsync()
{
try
{
List<MachineDto> consoles = FilterType switch
{
ConsoleListFilterType.Letter when FilterValue.Length == 1 =>
await _consolesService.GetConsolesByLetterAsync(FilterValue[0]),
ConsoleListFilterType.Year when int.TryParse(FilterValue, out int year) =>
await _consolesService.GetConsolesByYearAsync(year),
_ => await _consolesService.GetAllConsolesAsync()
};
// Add consoles to the list sorted by name
foreach(MachineDto console in consoles.OrderBy(c => c.Name))
{
int year = console.Introduced?.Year ?? 0;
int id = UntypedNodeExtractor.ExtractInt(console.Id);
_logger.LogInformation("Console: {Name}, Introduced: {Introduced}, Year: {Year}, Company: {Company}, ID: {Id}",
console.Name,
console.Introduced,
year,
console.Company,
id);
ConsolesList.Add(new ConsoleListItem
{
Id = id,
Name = console.Name ?? string.Empty,
Year = year,
Manufacturer = console.Company ?? string.Empty
});
}
}
catch(Exception ex)
{
_logger.LogError(ex, "Error loading consoles from API");
}
}
/// <summary>
/// Navigates back to the consoles main view
/// </summary>
private async Task GoBackAsync()
{
await _navigator.NavigateViewModelAsync<ConsolesViewModel>(this);
}
/// <summary>
/// Navigates to the console detail view
/// </summary>
private async Task NavigateToConsoleAsync(ConsoleListItem? console)
{
if(console is null) return;
_logger.LogInformation("Navigating to console detail: {ConsoleName} (ID: {ConsoleId})",
console.Name,
console.Id);
var navParam = new MachineViewNavigationParameter
{
MachineId = console.Id,
NavigationSource = this
};
await _navigator.NavigateViewModelAsync<MachineViewViewModel>(this, data: navParam);
}
}
/// <summary>
/// Data model for a console in the list
/// </summary>
public class ConsoleListItem
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public int Year { get; set; }
public string Manufacturer { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,207 @@
using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using System.Windows.Input;
using Marechai.App.Services;
using Uno.Extensions.Navigation;
namespace Marechai.App.Presentation.ViewModels;
public partial class ConsolesViewModel : ObservableObject
{
private readonly ConsolesService _consolesService;
private readonly IConsolesListFilterContext _filterContext;
private readonly IStringLocalizer _localizer;
private readonly ILogger<ConsolesViewModel> _logger;
private readonly INavigator _navigator;
[ObservableProperty]
private int _consoleCount;
[ObservableProperty]
private string _consoleCountText = string.Empty;
[ObservableProperty]
private string _errorMessage = string.Empty;
[ObservableProperty]
private bool _hasError;
[ObservableProperty]
private bool _isDataLoaded;
[ObservableProperty]
private bool _isLoading;
[ObservableProperty]
private ObservableCollection<char> _lettersList = [];
[ObservableProperty]
private int _maximumYear;
[ObservableProperty]
private int _minimumYear;
[ObservableProperty]
private string _yearsGridTitle = string.Empty;
[ObservableProperty]
private ObservableCollection<int> _yearsList = [];
public ConsolesViewModel(ConsolesService consolesService, IStringLocalizer localizer,
ILogger<ConsolesViewModel> logger, INavigator navigator,
IConsolesListFilterContext filterContext)
{
_consolesService = consolesService;
_localizer = localizer;
_logger = logger;
_navigator = navigator;
_filterContext = filterContext;
LoadData = new AsyncRelayCommand(LoadDataAsync);
GoBackCommand = new AsyncRelayCommand(GoBackAsync);
NavigateByLetterCommand = new AsyncRelayCommand<char>(NavigateByLetterAsync);
NavigateByYearCommand = new AsyncRelayCommand<int>(NavigateByYearAsync);
NavigateAllConsolesCommand = new AsyncRelayCommand(NavigateAllConsolesAsync);
InitializeLetters();
}
public IAsyncRelayCommand LoadData { get; }
public ICommand GoBackCommand { get; }
public IAsyncRelayCommand<char> NavigateByLetterCommand { get; }
public IAsyncRelayCommand<int> NavigateByYearCommand { get; }
public IAsyncRelayCommand NavigateAllConsolesCommand { get; }
public string Title { get; } = "Consoles";
/// <summary>
/// Initializes the alphabet list (A-Z)
/// </summary>
private void InitializeLetters()
{
LettersList.Clear();
for(var c = 'A'; c <= 'Z'; c++) LettersList.Add(c);
}
/// <summary>
/// Loads consoles count, minimum and maximum years from the API
/// </summary>
private async Task LoadDataAsync()
{
try
{
IsLoading = true;
ErrorMessage = string.Empty;
HasError = false;
IsDataLoaded = false;
YearsList.Clear();
// Load all data in parallel for better performance
Task<int> countTask = _consolesService.GetConsolesCountAsync();
Task<int> minYearTask = _consolesService.GetMinimumYearAsync();
Task<int> maxYearTask = _consolesService.GetMaximumYearAsync();
await Task.WhenAll(countTask, minYearTask, maxYearTask);
ConsoleCount = countTask.Result;
MinimumYear = minYearTask.Result;
MaximumYear = maxYearTask.Result;
// Update display text
ConsoleCountText = _localizer["Consoles in the database"];
// Generate years list
if(MinimumYear > 0 && MaximumYear > 0)
{
for(int year = MinimumYear; year <= MaximumYear; year++) YearsList.Add(year);
YearsGridTitle = $"Browse by Year ({MinimumYear} - {MaximumYear})";
}
if(ConsoleCount == 0)
{
ErrorMessage = _localizer["No consoles found"].Value;
HasError = true;
}
else
IsDataLoaded = true;
}
catch(Exception ex)
{
_logger.LogError("Error loading consoles data: {Exception}", ex.Message);
ErrorMessage = _localizer["Failed to load consoles data. Please try again later."].Value;
HasError = true;
}
finally
{
IsLoading = false;
}
}
/// <summary>
/// Handles back navigation
/// </summary>
private async Task GoBackAsync()
{
await _navigator.NavigateViewModelAsync<MainViewModel>(this);
}
/// <summary>
/// Navigates to consoles filtered by letter
/// </summary>
private async Task NavigateByLetterAsync(char letter)
{
try
{
_logger.LogInformation("Navigating to consoles by letter: {Letter}", letter);
_filterContext.FilterType = ConsoleListFilterType.Letter;
_filterContext.FilterValue = letter.ToString();
await _navigator.NavigateRouteAsync(this, "list-consoles");
}
catch(Exception ex)
{
_logger.LogError("Error navigating to letter consoles: {Exception}", ex.Message);
ErrorMessage = _localizer["Failed to navigate. Please try again."].Value;
HasError = true;
}
}
/// <summary>
/// Navigates to consoles filtered by year
/// </summary>
private async Task NavigateByYearAsync(int year)
{
try
{
_logger.LogInformation("Navigating to consoles by year: {Year}", year);
_filterContext.FilterType = ConsoleListFilterType.Year;
_filterContext.FilterValue = year.ToString();
await _navigator.NavigateRouteAsync(this, "list-consoles");
}
catch(Exception ex)
{
_logger.LogError("Error navigating to year consoles: {Exception}", ex.Message);
ErrorMessage = _localizer["Failed to navigate. Please try again."].Value;
HasError = true;
}
}
/// <summary>
/// Navigates to all consoles view
/// </summary>
private async Task NavigateAllConsolesAsync()
{
try
{
_logger.LogInformation("Navigating to all consoles");
_filterContext.FilterType = ConsoleListFilterType.All;
_filterContext.FilterValue = string.Empty;
await _navigator.NavigateRouteAsync(this, "list-consoles");
}
catch(Exception ex)
{
_logger.LogError("Error navigating to all consoles: {Exception}", ex.Message);
ErrorMessage = _localizer["Failed to navigate. Please try again."].Value;
HasError = true;
}
}
}

View File

@@ -0,0 +1,407 @@
/******************************************************************************
// MARECHAI: Master repository of computing history artifacts information
// ----------------------------------------------------------------------------
//
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// --[ License ] --------------------------------------------------------------
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2003-2026 Natalia Portillo
*******************************************************************************/
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Humanizer;
using Marechai.App.Helpers;
using Marechai.App.Presentation.Models;
using Marechai.App.Services;
using Marechai.Data;
using Microsoft.UI.Xaml;
using Uno.Extensions.Navigation;
namespace Marechai.App.Presentation.ViewModels;
public partial class MachineViewViewModel : ObservableObject
{
private readonly ComputersService _computersService;
private readonly ILogger<MachineViewViewModel> _logger;
private readonly INavigator _navigator;
[ObservableProperty]
private string _companyName = string.Empty;
[ObservableProperty]
private string _errorMessage = string.Empty;
[ObservableProperty]
private string? _familyName;
[ObservableProperty]
private bool _hasError;
[ObservableProperty]
private string? _introductionDateDisplay;
[ObservableProperty]
private bool _isDataLoaded;
[ObservableProperty]
private bool _isLoading;
[ObservableProperty]
private bool _isPrototype;
[ObservableProperty]
private string _machineName = string.Empty;
[ObservableProperty]
private string? _modelName;
private object? _navigationSource;
[ObservableProperty]
private Visibility _showFamily = Visibility.Collapsed;
[ObservableProperty]
private Visibility _showFamilyOrModel = Visibility.Collapsed;
[ObservableProperty]
private Visibility _showGpus = Visibility.Collapsed;
[ObservableProperty]
private Visibility _showIntroductionDate = Visibility.Collapsed;
[ObservableProperty]
private Visibility _showMemory = Visibility.Collapsed;
[ObservableProperty]
private Visibility _showModel = Visibility.Collapsed;
[ObservableProperty]
private Visibility _showProcessors = Visibility.Collapsed;
[ObservableProperty]
private Visibility _showSoundSynthesizers = Visibility.Collapsed;
[ObservableProperty]
private Visibility _showStorage = Visibility.Collapsed;
public MachineViewViewModel(ILogger<MachineViewViewModel> logger, INavigator navigator,
ComputersService computersService)
{
_logger = logger;
_navigator = navigator;
_computersService = computersService;
}
public ObservableCollection<ProcessorDisplayItem> Processors { get; } = [];
public ObservableCollection<MemoryDisplayItem> Memory { get; } = [];
public ObservableCollection<GpuDisplayItem> Gpus { get; } = [];
public ObservableCollection<SoundSynthesizerDisplayItem> SoundSynthesizers { get; } = [];
public ObservableCollection<StorageDisplayItem> Storage { get; } = [];
[RelayCommand]
public async Task GoBack()
{
// If we came from News, navigate back to News
if(_navigationSource is NewsViewModel)
{
await _navigator.NavigateViewModelAsync<NewsViewModel>(this);
return;
}
// If we came from CompanyDetailViewModel, navigate back to company details
if(_navigationSource is CompanyDetailViewModel companyVm)
{
var navParam = new CompanyDetailNavigationParameter
{
CompanyId = companyVm.CompanyId
};
await _navigator.NavigateViewModelAsync<CompanyDetailViewModel>(this, data: navParam);
return;
}
// If we came from ConsolesListViewModel, navigate back to consoles list
if(_navigationSource is ConsolesListViewModel)
{
await _navigator.NavigateViewModelAsync<ConsolesListViewModel>(this);
return;
}
// If we came from ComputersListViewModel, navigate back to computers list
if(_navigationSource is ComputersListViewModel)
{
await _navigator.NavigateViewModelAsync<ComputersListViewModel>(this);
return;
}
// Otherwise, try to go back in the navigation stack
await _navigator.GoBack(this);
}
/// <summary>
/// Sets the navigation source (where we came from).
/// </summary>
public void SetNavigationSource(object? source)
{
_navigationSource = source;
}
[RelayCommand]
public Task LoadData()
{
// Placeholder for retry functionality
HasError = false;
ErrorMessage = string.Empty;
return Task.CompletedTask;
}
public async Task LoadMachineAsync(int machineId)
{
try
{
IsLoading = true;
IsDataLoaded = false;
HasError = false;
ErrorMessage = string.Empty;
Processors.Clear();
Memory.Clear();
Gpus.Clear();
SoundSynthesizers.Clear();
Storage.Clear();
_logger.LogInformation("Loading machine {MachineId}", machineId);
// Fetch machine data from API
MachineDto? machine = await _computersService.GetMachineByIdAsync(machineId);
if(machine is null)
{
HasError = true;
ErrorMessage = "Machine not found";
IsLoading = false;
return;
}
// Populate basic information
MachineName = machine.Name ?? string.Empty;
CompanyName = machine.Company ?? string.Empty;
FamilyName = machine.FamilyName;
ModelName = machine.Model;
// Check if this is a prototype (year 1000 is used as placeholder for prototypes)
IsPrototype = machine.Introduced?.Year == 1000;
// Set introduction date if available and not a prototype
if(machine.Introduced.HasValue && machine.Introduced.Value.Year != 1000)
IntroductionDateDisplay = machine.Introduced.Value.ToString("MMMM d, yyyy");
// Populate processors
if(machine.Processors != null)
{
foreach(ProcessorDto processor in machine.Processors)
{
var details = new List<string>();
int speed = UntypedNodeExtractor.ExtractInt(processor.Speed);
int gprSize = UntypedNodeExtractor.ExtractInt(processor.GprSize);
int cores = UntypedNodeExtractor.ExtractInt(processor.Cores);
if(speed > 0) details.Add($"{speed} MHz");
if(gprSize > 0) details.Add($"{gprSize} bits");
if(cores > 1) details.Add($"{cores} cores");
Processors.Add(new ProcessorDisplayItem
{
DisplayName = processor.Name ?? string.Empty,
Manufacturer = processor.Company ?? string.Empty,
HasDetails = details.Count > 0,
DetailsText = string.Join(", ", details)
});
}
}
// Populate memory
if(machine.Memory != null)
{
foreach(MemoryDto mem in machine.Memory)
{
long size = UntypedNodeExtractor.ExtractLong(mem.Size);
string sizeStr = size > 0
? size > 1024 ? $"{size} bytes ({size.Bytes().Humanize()})" : $"{size} bytes"
: "Unknown";
// Get humanized memory usage description
string usageDescription = mem.Usage.HasValue
? ((MemoryUsage)mem.Usage.Value).Humanize()
: "Unknown";
Memory.Add(new MemoryDisplayItem
{
SizeDisplay = sizeStr,
TypeDisplay = usageDescription
});
}
} // Populate GPUs
if(machine.Gpus != null)
{
foreach(GpuDto gpu in machine.Gpus)
{
Gpus.Add(new GpuDisplayItem
{
DisplayName = gpu.Name ?? string.Empty,
Manufacturer = gpu.Company ?? string.Empty,
HasManufacturer = !string.IsNullOrEmpty(gpu.Company)
});
}
}
// Populate sound synthesizers
if(machine.SoundSynthesizers != null)
{
foreach(SoundSynthDto synth in machine.SoundSynthesizers)
{
var details = new List<string>();
int voices = UntypedNodeExtractor.ExtractInt(synth.Voices);
if(voices > 0) details.Add($"{voices} voices");
SoundSynthesizers.Add(new SoundSynthesizerDisplayItem
{
DisplayName = synth.Name ?? string.Empty,
HasDetails = details.Count > 0,
DetailsText = string.Join(", ", details)
});
}
}
// Populate storage
if(machine.Storage != null)
{
foreach(StorageDto storage in machine.Storage)
{
long capacity = UntypedNodeExtractor.ExtractLong(storage.Capacity);
string displayText = capacity > 0
? capacity > 1024
? $"{capacity} bytes ({capacity.Bytes().Humanize()})"
: $"{capacity} bytes"
: "Storage";
// Get humanized storage type description
string typeNote = storage.Type.HasValue ? ((StorageType)storage.Type.Value).Humanize() : "Unknown";
Storage.Add(new StorageDisplayItem
{
DisplayText = displayText,
TypeNote = typeNote
});
}
}
UpdateVisibilities();
IsDataLoaded = true;
IsLoading = false;
}
catch(Exception ex)
{
_logger.LogError(ex, "Error loading machine {MachineId}", machineId);
HasError = true;
ErrorMessage = ex.Message;
IsLoading = false;
}
}
private void UpdateVisibilities()
{
ShowIntroductionDate =
!string.IsNullOrEmpty(IntroductionDateDisplay) ? Visibility.Visible : Visibility.Collapsed;
ShowFamily = !string.IsNullOrEmpty(FamilyName) ? Visibility.Visible : Visibility.Collapsed;
ShowModel = !string.IsNullOrEmpty(ModelName) ? Visibility.Visible : Visibility.Collapsed;
ShowFamilyOrModel = ShowFamily == Visibility.Visible || ShowModel == Visibility.Visible
? Visibility.Visible
: Visibility.Collapsed;
ShowProcessors = Processors.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
ShowMemory = Memory.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
ShowGpus = Gpus.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
ShowSoundSynthesizers = SoundSynthesizers.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
ShowStorage = Storage.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
}
}
/// <summary>
/// Display item for processor information
/// </summary>
public class ProcessorDisplayItem
{
public string DisplayName { get; set; } = string.Empty;
public string Manufacturer { get; set; } = string.Empty;
public bool HasDetails { get; set; }
public string DetailsText { get; set; } = string.Empty;
}
/// <summary>
/// Display item for memory information
/// </summary>
public class MemoryDisplayItem
{
public string SizeDisplay { get; set; } = string.Empty;
public string TypeDisplay { get; set; } = string.Empty;
}
/// <summary>
/// Display item for GPU information
/// </summary>
public class GpuDisplayItem
{
public string DisplayName { get; set; } = string.Empty;
public string Manufacturer { get; set; } = string.Empty;
public bool HasManufacturer { get; set; }
}
/// <summary>
/// Display item for sound synthesizer information
/// </summary>
public class SoundSynthesizerDisplayItem
{
public string DisplayName { get; set; } = string.Empty;
public bool HasDetails { get; set; }
public string DetailsText { get; set; } = string.Empty;
}
/// <summary>
/// Display item for storage information
/// </summary>
public class StorageDisplayItem
{
public string DisplayText { get; set; } = string.Empty;
public string TypeNote { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,177 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows.Input;
using Uno.Extensions.Navigation;
namespace Marechai.App.Presentation.ViewModels;
public partial class MainViewModel : ObservableObject
{
private readonly IStringLocalizer _localizer;
private readonly INavigator _navigator;
[ObservableProperty]
private bool _isSidebarOpen = true;
[ObservableProperty]
private Dictionary<string, string> _localizedStrings = new();
[ObservableProperty]
private string _loginLogoutButtonText = "";
[ObservableProperty]
private string? _name;
[ObservableProperty]
private NewsViewModel? _newsViewModel;
[ObservableProperty]
private bool _sidebarContentVisible = true;
public MainViewModel(IStringLocalizer localizer, IOptions<AppConfig> appInfo, INavigator navigator,
NewsViewModel newsViewModel)
{
_navigator = navigator;
_localizer = localizer;
NewsViewModel = newsViewModel;
Title = "Marechai";
Title += $" - {localizer["ApplicationName"]}";
if(appInfo?.Value?.Environment != null) Title += $" - {appInfo.Value.Environment}";
GoToSecond = new AsyncRelayCommand(GoToSecondView);
// Initialize localized strings
InitializeLocalizedStrings();
// Initialize commands
NavigateToNewsCommand = new AsyncRelayCommand(NavigateToMainAsync);
NavigateToBooksCommand = new AsyncRelayCommand(() => NavigateTo("books"));
NavigateToCompaniesCommand = new AsyncRelayCommand(() => NavigateTo("companies"));
NavigateToComputersCommand = new AsyncRelayCommand(() => NavigateTo("computers"));
NavigateToConsolesCommand = new AsyncRelayCommand(() => NavigateTo("consoles"));
NavigateToDocumentsCommand = new AsyncRelayCommand(() => NavigateTo("documents"));
NavigateToDumpsCommand = new AsyncRelayCommand(() => NavigateTo("dumps"));
NavigateToGraphicalProcessingUnitsCommand = new AsyncRelayCommand(() => NavigateTo("gpus"));
NavigateToMagazinesCommand = new AsyncRelayCommand(() => NavigateTo("magazines"));
NavigateToPeopleCommand = new AsyncRelayCommand(() => NavigateTo("people"));
NavigateToProcessorsCommand = new AsyncRelayCommand(() => NavigateTo("processors"));
NavigateToSoftwareCommand = new AsyncRelayCommand(() => NavigateTo("software"));
NavigateToSoundSynthesizersCommand = new AsyncRelayCommand(() => NavigateTo("soundsynthesizers"));
NavigateToSettingsCommand = new AsyncRelayCommand(() => NavigateTo("settings"));
LoginLogoutCommand = new RelayCommand(HandleLoginLogout);
ToggleSidebarCommand = new RelayCommand(() => IsSidebarOpen = !IsSidebarOpen);
UpdateLoginLogoutButtonText();
}
public string? Title { get; }
public ICommand GoToSecond { get; }
public ICommand NavigateToNewsCommand { get; }
public ICommand NavigateToBooksCommand { get; }
public ICommand NavigateToCompaniesCommand { get; }
public ICommand NavigateToComputersCommand { get; }
public ICommand NavigateToConsolesCommand { get; }
public ICommand NavigateToDocumentsCommand { get; }
public ICommand NavigateToDumpsCommand { get; }
public ICommand NavigateToGraphicalProcessingUnitsCommand { get; }
public ICommand NavigateToMagazinesCommand { get; }
public ICommand NavigateToPeopleCommand { get; }
public ICommand NavigateToProcessorsCommand { get; }
public ICommand NavigateToSoftwareCommand { get; }
public ICommand NavigateToSoundSynthesizersCommand { get; }
public ICommand NavigateToSettingsCommand { get; }
public ICommand LoginLogoutCommand { get; }
public ICommand ToggleSidebarCommand { get; }
private void InitializeLocalizedStrings()
{
LocalizedStrings = new Dictionary<string, string>
{
{
"News", _localizer["News"]
},
{
"Books", _localizer["Books"]
},
{
"Companies", _localizer["Companies"]
},
{
"Computers", _localizer["Computers"]
},
{
"Consoles", _localizer["Consoles"]
},
{
"Documents", _localizer["Documents"]
},
{
"Dumps", _localizer["Dumps"]
},
{
"GraphicalProcessingUnits", _localizer["GraphicalProcessingUnits"]
},
{
"Magazines", _localizer["Magazines"]
},
{
"People", _localizer["People"]
},
{
"Processors", _localizer["Processors"]
},
{
"Software", _localizer["Software"]
},
{
"SoundSynthesizers", _localizer["SoundSynthesizers"]
},
{
"Settings", _localizer["Settings"]
},
{
"Login", _localizer["Login"]
},
{
"Logout", _localizer["Logout"]
}
};
}
private void UpdateLoginLogoutButtonText()
{
// TODO: Check if user is logged in
// For now, always show "Login"
LoginLogoutButtonText = LocalizedStrings["Login"];
}
private static void HandleLoginLogout()
{
// TODO: Implement login/logout logic
}
private async Task NavigateTo(string destination)
{
try
{
// Navigate within the Main region using relative navigation
// The "./" prefix means navigate within the current page's region
await _navigator.NavigateRouteAsync(this, $"./{destination}");
}
catch(Exception)
{
// Navigation error - fail silently for now
// TODO: Add error handling/logging
}
}
private async Task NavigateToMainAsync()
{
// Navigate to News page (the default/home page)
await NavigateTo("News");
}
private async Task GoToSecondView()
{
// Navigate to Second view model providing qualifier and data
await _navigator.NavigateViewModelAsync<SecondViewModel>(this, "Second", new Entity(Name ?? ""));
}
}

View File

@@ -0,0 +1,176 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Marechai.App.Helpers;
using Marechai.App.Presentation.Models;
using Marechai.App.Services;
using Marechai.Data;
using Uno.Extensions.Navigation;
namespace Marechai.App.Presentation.ViewModels;
/// <summary>
/// Wrapper for NewsDto with generated display text
/// </summary>
public class NewsItemViewModel
{
public required NewsDto News { get; init; }
public required string DisplayText { get; init; }
public required IAsyncRelayCommand<NewsDto> NavigateToItemCommand { get; init; }
/// <summary>
/// Determines if this news item can be navigated to (only computers and consoles)
/// </summary>
public bool CanNavigateToItem
{
get
{
if(News?.Type is null) return false;
var type = (NewsType)News.Type.Value;
return type is NewsType.NewComputerInDb
or NewsType.NewConsoleInDb
or NewsType.UpdatedComputerInDb
or NewsType.UpdatedConsoleInDb
or NewsType.NewComputerInCollection
or NewsType.NewConsoleInCollection
or NewsType.UpdatedComputerInCollection
or NewsType.UpdatedConsoleInCollection;
}
}
}
public partial class NewsViewModel : ObservableObject
{
private readonly IStringLocalizer _localizer;
private readonly ILogger<NewsViewModel> _logger;
private readonly INavigator _navigator;
private readonly NewsService _newsService;
[ObservableProperty]
private string _errorMessage = string.Empty;
[ObservableProperty]
private bool _hasError;
[ObservableProperty]
private bool _isLoading;
[ObservableProperty]
private ObservableCollection<NewsItemViewModel> _newsList = [];
public NewsViewModel(NewsService newsService, IStringLocalizer localizer, ILogger<NewsViewModel> logger,
INavigator navigator)
{
_newsService = newsService;
_localizer = localizer;
_logger = logger;
_navigator = navigator;
LoadNews = new AsyncRelayCommand(LoadNewsAsync);
}
public IAsyncRelayCommand LoadNews { get; }
[RelayCommand]
private async Task NavigateToNewsItem(NewsDto news)
{
if(news?.Type is null) return;
var newsType = (NewsType)news.Type.Value;
// Only navigate for computer and console news items
bool isComputerOrConsole = newsType is NewsType.NewComputerInDb
or NewsType.NewConsoleInDb
or NewsType.UpdatedComputerInDb
or NewsType.UpdatedConsoleInDb
or NewsType.NewComputerInCollection
or NewsType.NewConsoleInCollection
or NewsType.UpdatedComputerInCollection
or NewsType.UpdatedConsoleInCollection;
if(!isComputerOrConsole) return;
// Extract the machine ID from AffectedId
if(news.AffectedId is null) return;
int machineId = UntypedNodeExtractor.ExtractInt(news.AffectedId);
if(machineId <= 0) return;
// Navigate to machine view with source information
var navParam = new MachineViewNavigationParameter
{
MachineId = machineId,
NavigationSource = this
};
await _navigator.NavigateViewModelAsync<MachineViewViewModel>(this, data: navParam);
}
/// <summary>
/// Helper to extract int from UntypedNode
/// </summary>
/// <summary>
/// Generates localized text based on NewsType
/// </summary>
private string GetLocalizedTextForNewsType(NewsType type)
{
return type switch
{
NewsType.NewComputerInDb => _localizer["New computer in database"].Value,
NewsType.NewConsoleInDb => _localizer["New console in database"].Value,
NewsType.NewComputerInCollection => _localizer["New computer in collection"].Value,
NewsType.NewConsoleInCollection => _localizer["New console in collection"].Value,
NewsType.UpdatedComputerInDb => _localizer["Updated computer in database"].Value,
NewsType.UpdatedConsoleInDb => _localizer["Updated console in database"].Value,
NewsType.UpdatedComputerInCollection => _localizer["Updated computer in collection"].Value,
NewsType.UpdatedConsoleInCollection => _localizer["Updated console in collection"].Value,
_ => string.Empty
};
}
/// <summary>
/// Loads the latest news from the API
/// </summary>
private async Task LoadNewsAsync()
{
try
{
IsLoading = true;
ErrorMessage = string.Empty;
HasError = false;
NewsList.Clear();
List<NewsDto> news = await _newsService.GetLatestNewsAsync();
if(news.Count == 0)
{
ErrorMessage = _localizer["No news available"].Value;
HasError = true;
}
else
{
foreach(NewsDto item in news)
{
NewsList.Add(new NewsItemViewModel
{
News = item,
DisplayText = GetLocalizedTextForNewsType((NewsType)(item.Type ?? 0)),
NavigateToItemCommand = NavigateToNewsItemCommand
});
}
}
}
catch(Exception ex)
{
_logger.LogError("Error loading news: {Exception}", ex.Message);
ErrorMessage = _localizer["Failed to load news. Please try again later."].Value;
HasError = true;
}
finally
{
IsLoading = false;
}
}
}

View File

@@ -0,0 +1,3 @@
namespace Marechai.App.Presentation.ViewModels;
public record SecondViewModel(Entity Entity) {}

View File

@@ -0,0 +1,12 @@
using Uno.Extensions.Navigation;
namespace Marechai.App.Presentation.ViewModels;
public class ShellViewModel
{
private readonly INavigator _navigator;
public ShellViewModel(INavigator navigator) => _navigator = navigator;
// Add code here to initialize or attach event handlers to singleton services
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0"
encoding="utf-8"?>
<Page x:Class="Marechai.App.Presentation.Views.CompaniesPage"
x:Name="PageRoot"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:utu="using:Uno.Toolkit.UI"
NavigationCacheMode="Required"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid utu:SafeArea.Insets="VisibleBounds">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Header -->
<utu:NavigationBar Grid.Row="0"
Content="{Binding Path=Title}">
<utu:NavigationBar.MainCommand>
<AppBarButton Icon="Back"
Label="Back"
Command="{Binding GoBackCommand}"
AutomationProperties.Name="Go back" />
</utu:NavigationBar.MainCommand>
</utu:NavigationBar>
<!-- Content -->
<ScrollViewer Grid.Row="1">
<StackPanel Padding="16"
Spacing="16">
<!-- Company Count Display -->
<StackPanel HorizontalAlignment="Center"
Spacing="8">
<TextBlock Text="{Binding CompanyCountText}"
TextAlignment="Center"
FontSize="18"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemBaseMediumColor}" />
<TextBlock Text="{Binding CompanyCount}"
TextAlignment="Center"
FontSize="48"
FontWeight="Bold"
Foreground="{ThemeResource SystemAccentColor}" />
</StackPanel>
<!-- Search Box -->
<AutoSuggestBox x:Name="CompaniesSearchBox"
HorizontalAlignment="Stretch"
PlaceholderText="Search companies..."
Text="{Binding SearchQuery, Mode=TwoWay}"
QuerySubmitted="OnSearchQuerySubmitted"
TextChanged="OnSearchTextChanged">
<AutoSuggestBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</AutoSuggestBox.ItemTemplate>
</AutoSuggestBox>
<!-- Loading State -->
<StackPanel Visibility="{Binding IsLoading}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Padding="32"
Spacing="16">
<ProgressRing IsActive="True"
IsIndeterminate="True"
Height="48"
Width="48" />
<TextBlock Text="Loading..."
TextAlignment="Center"
FontSize="14" />
</StackPanel>
<!-- Error State -->
<StackPanel Visibility="{Binding HasError}"
Padding="16"
Background="{ThemeResource SystemErrorBackgroundColor}"
CornerRadius="8"
Spacing="8">
<TextBlock Text="Error"
FontWeight="Bold"
Foreground="{ThemeResource SystemErrorTextForegroundColor}" />
<TextBlock Text="{Binding ErrorMessage}"
Foreground="{ThemeResource SystemErrorTextForegroundColor}"
TextWrapping="Wrap" />
</StackPanel>
<!-- Companies List -->
<StackPanel Visibility="{Binding IsDataLoaded}"
Spacing="8">
<ItemsRepeater ItemsSource="{Binding CompaniesList}">
<ItemsRepeater.ItemTemplate>
<DataTemplate>
<Button HorizontalAlignment="Stretch"
Command="{Binding DataContext.NavigateToCompanyCommand, ElementName=PageRoot}"
CommandParameter="{Binding}">
<StackPanel Spacing="4"
Padding="8">
<TextBlock Text="{Binding Name}"
FontSize="16"
FontWeight="SemiBold" />
<TextBlock Text="{Binding FoundationDateDisplay}"
FontSize="12"
Opacity="0.6" />
</StackPanel>
</Button>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Grid>
</Page>

View File

@@ -0,0 +1,59 @@
using Marechai.App.Presentation.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation;
namespace Marechai.App.Presentation.Views;
public sealed partial class CompaniesPage : Page
{
public CompaniesPage()
{
InitializeComponent();
DataContextChanged += CompaniesPage_DataContextChanged;
Loaded += CompaniesPage_Loaded;
}
private void CompaniesPage_Loaded(object sender, RoutedEventArgs e)
{
if(DataContext is not CompaniesViewModel viewModel) return;
// Trigger data loading
_ = viewModel.LoadData.ExecuteAsync(null);
}
private void CompaniesPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
{
if(args.NewValue is CompaniesViewModel viewModel)
{
// Trigger data loading when data context changes
_ = viewModel.LoadData.ExecuteAsync(null);
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if(DataContext is CompaniesViewModel viewModel)
{
// Trigger data loading when navigating to the page
_ = viewModel.LoadData.ExecuteAsync(null);
}
}
private void OnSearchTextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{
if(args.Reason == AutoSuggestionBoxTextChangeReason.UserInput)
{
// The two-way binding will automatically update SearchQuery in ViewModel,
// which will trigger OnSearchQueryChanged and filter the list
}
}
private void OnSearchQuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
{
// The two-way binding will automatically update SearchQuery in ViewModel,
// which will trigger OnSearchQueryChanged and filter the list
}
}

View File

@@ -0,0 +1,273 @@
<?xml version="1.0" encoding="utf-8"?>
<Page x:Class="Marechai.App.Presentation.Views.CompanyDetailPage"
x:Name="PageRoot"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:utu="using:Uno.Toolkit.UI"
NavigationCacheMode="Required"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid utu:SafeArea.Insets="VisibleBounds">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Header -->
<utu:NavigationBar Grid.Row="0"
Content="{Binding Path=Title}">
<utu:NavigationBar.MainCommand>
<AppBarButton Icon="Back"
Label="Back"
Command="{Binding GoBackCommand}"
AutomationProperties.Name="Go back" />
</utu:NavigationBar.MainCommand>
</utu:NavigationBar>
<!-- Content -->
<ScrollViewer Grid.Row="1">
<StackPanel Padding="16"
Spacing="16">
<!-- Loading State -->
<StackPanel Visibility="{Binding IsLoading}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Padding="32"
Spacing="16">
<ProgressRing IsActive="True"
IsIndeterminate="True"
Height="48"
Width="48" />
<TextBlock Text="Loading..."
TextAlignment="Center"
FontSize="14" />
</StackPanel>
<!-- Error State -->
<StackPanel Visibility="{Binding HasError}"
Padding="16"
Background="{ThemeResource SystemErrorBackgroundColor}"
CornerRadius="8"
Spacing="8">
<TextBlock Text="Error"
FontWeight="Bold"
Foreground="{ThemeResource SystemErrorTextForegroundColor}" />
<TextBlock Text="{Binding ErrorMessage}"
Foreground="{ThemeResource SystemErrorTextForegroundColor}"
TextWrapping="Wrap" />
</StackPanel>
<!-- Company Details -->
<StackPanel Visibility="{Binding IsDataLoaded}"
Spacing="16">
<!-- Company Name -->
<TextBlock Text="{Binding Company.Name}"
FontSize="28"
FontWeight="Bold"
TextWrapping="Wrap" />
<!-- Company Status -->
<StackPanel Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="8"
Padding="12"
Spacing="8">
<TextBlock Text="Status"
FontSize="14"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemBaseMediumColor}" />
<TextBlock Text="{Binding CompanyStatusDisplay}"
FontSize="14"
TextWrapping="Wrap" />
</StackPanel>
<!-- Founded Date -->
<StackPanel Visibility="{Binding Company.Founded, Converter={StaticResource ObjectToVisibilityConverter}}"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="8"
Padding="12"
Spacing="8">
<TextBlock Text="Founded"
FontSize="14"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemBaseMediumColor}" />
<TextBlock Text="{Binding CompanyFoundedDateDisplay}"
FontSize="14" />
</StackPanel>
<!-- Legal Name -->
<StackPanel Visibility="{Binding Company.LegalName, Converter={StaticResource StringToVisibilityConverter}}"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="8"
Padding="12"
Spacing="8">
<TextBlock Text="Legal Name"
FontSize="14"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemBaseMediumColor}" />
<TextBlock Text="{Binding Company.LegalName}"
FontSize="14" />
</StackPanel>
<!-- Country -->
<StackPanel Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="8"
Padding="12"
Spacing="8">
<TextBlock Text="Country"
FontSize="14"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemBaseMediumColor}" />
<TextBlock Text="{Binding Company.Country}"
FontSize="14" />
</StackPanel>
<!-- Address -->
<StackPanel Visibility="{Binding Company.Address, Converter={StaticResource StringToVisibilityConverter}}"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
CornerRadius="8"
Padding="12"
Spacing="8">
<TextBlock Text="Address"
FontSize="14"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemBaseMediumColor}" />
<TextBlock TextWrapping="Wrap">
<Run Text="{Binding Company.Address}" />
<Run Text="{Binding Company.City}" />
<Run Text="{Binding Company.PostalCode}" />
<Run Text="{Binding Company.Province}" />
</TextBlock>
</StackPanel>
<!-- Links Section -->
<StackPanel Spacing="8">
<TextBlock Text="Links"
FontSize="14"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemBaseMediumColor}" />
<!-- Website -->
<HyperlinkButton Visibility="{Binding Company.Website, Converter={StaticResource StringToVisibilityConverter}}"
NavigateUri="{Binding Company.Website}">
<TextBlock Text="Website" />
</HyperlinkButton>
<!-- Twitter -->
<HyperlinkButton Visibility="{Binding Company.Twitter, Converter={StaticResource StringToVisibilityConverter}}"
Click="OnTwitterClick">
<TextBlock Text="Twitter" />
</HyperlinkButton>
<!-- Facebook -->
<HyperlinkButton Visibility="{Binding Company.Facebook, Converter={StaticResource StringToVisibilityConverter}}"
Click="OnFacebookClick">
<TextBlock Text="Facebook" />
</HyperlinkButton>
</StackPanel>
<!-- Computers Section -->
<StackPanel Visibility="{Binding Computers.Count, Converter={StaticResource ZeroToVisibilityConverter}}"
Spacing="8">
<StackPanel Orientation="Horizontal"
Spacing="8">
<TextBlock Text="Computers"
FontSize="14"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemBaseMediumColor}" />
<TextBlock Text="{Binding Computers.Count}"
FontSize="14"
FontWeight="Bold"
Foreground="{ThemeResource SystemAccentColor}" />
</StackPanel>
<!-- Filter Box -->
<AutoSuggestBox PlaceholderText="Filter computers..."
Text="{Binding ComputersFilterText, Mode=TwoWay}"
BorderThickness="1"
BorderBrush="{ThemeResource ControlElevationBorderBrush}" />
<!-- Scrollable Computers List -->
<ScrollViewer Height="200"
BorderThickness="1"
BorderBrush="{ThemeResource ControlElevationBorderBrush}"
CornerRadius="8">
<ItemsRepeater ItemsSource="{Binding FilteredComputers}"
Margin="0">
<ItemsRepeater.Layout>
<StackLayout Spacing="4" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate>
<Button Command="{Binding DataContext.NavigateToMachineCommand, ElementName=PageRoot}"
CommandParameter="{Binding}"
Padding="12,8"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left">
<TextBlock Text="{Binding Name}"
FontSize="12"
TextWrapping="Wrap" />
</Button>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</ScrollViewer>
</StackPanel>
<!-- Consoles Section -->
<StackPanel Visibility="{Binding Consoles.Count, Converter={StaticResource ZeroToVisibilityConverter}}"
Spacing="8">
<StackPanel Orientation="Horizontal"
Spacing="8">
<TextBlock Text="Consoles"
FontSize="14"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemBaseMediumColor}" />
<TextBlock Text="{Binding Consoles.Count}"
FontSize="14"
FontWeight="Bold"
Foreground="{ThemeResource SystemAccentColor}" />
</StackPanel>
<!-- Filter Box -->
<AutoSuggestBox PlaceholderText="Filter consoles..."
Text="{Binding ConsoelsFilterText, Mode=TwoWay}"
BorderThickness="1"
BorderBrush="{ThemeResource ControlElevationBorderBrush}" />
<!-- Scrollable Consoles List -->
<ScrollViewer Height="200"
BorderThickness="1"
BorderBrush="{ThemeResource ControlElevationBorderBrush}"
CornerRadius="8">
<ItemsRepeater ItemsSource="{Binding FilteredConsoles}"
Margin="0">
<ItemsRepeater.Layout>
<StackLayout Spacing="4" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate>
<Button Command="{Binding DataContext.NavigateToMachineCommand, ElementName=PageRoot}"
CommandParameter="{Binding}"
Padding="12,8"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left">
<TextBlock Text="{Binding Name}"
FontSize="12"
TextWrapping="Wrap" />
</Button>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</ScrollViewer>
</StackPanel>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Grid>
</Page>

View File

@@ -0,0 +1,77 @@
#nullable enable
using System;
using Windows.System;
using Marechai.App.Presentation.Models;
using Marechai.App.Presentation.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation;
namespace Marechai.App.Presentation.Views;
public sealed partial class CompanyDetailPage : Page
{
private object? _navigationSource;
private int? _pendingCompanyId;
public CompanyDetailPage()
{
InitializeComponent();
DataContextChanged += CompanyDetailPage_DataContextChanged;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
int? companyId = null;
// Handle both int and CompanyDetailNavigationParameter
if(e.Parameter is int intId)
companyId = intId;
else if(e.Parameter is CompanyDetailNavigationParameter navParam)
{
companyId = navParam.CompanyId;
_navigationSource = navParam.NavigationSource;
}
if(companyId.HasValue)
{
_pendingCompanyId = companyId;
if(DataContext is CompanyDetailViewModel viewModel)
{
viewModel.CompanyId = companyId.Value;
_ = viewModel.LoadData.ExecuteAsync(null);
}
}
}
private void CompanyDetailPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
{
if(DataContext is CompanyDetailViewModel viewModel && _pendingCompanyId.HasValue)
{
viewModel.CompanyId = _pendingCompanyId.Value;
_ = viewModel.LoadData.ExecuteAsync(null);
}
}
private async void OnTwitterClick(object sender, RoutedEventArgs e)
{
if(DataContext is CompanyDetailViewModel viewModel && viewModel.Company?.Twitter is not null)
{
var uri = new Uri($"https://www.twitter.com/{viewModel.Company.Twitter}");
await Launcher.LaunchUriAsync(uri);
}
}
private async void OnFacebookClick(object sender, RoutedEventArgs e)
{
if(DataContext is CompanyDetailViewModel viewModel && viewModel.Company?.Facebook is not null)
{
var uri = new Uri($"https://www.facebook.com/{viewModel.Company.Facebook}");
await Launcher.LaunchUriAsync(uri);
}
}
}

View File

@@ -0,0 +1,261 @@
<?xml version="1.0"
encoding="utf-8"?>
<Page x:Class="Marechai.App.Presentation.Views.ComputersListPage"
x:Name="PageRoot"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
NavigationCacheMode="Required"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid RowSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Header with Back Button and Title -->
<Grid Grid.Row="0"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
BorderThickness="0,0,0,1"
Padding="16,12,16,12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Back Button -->
<Button Grid.Column="0"
Command="{Binding GoBackCommand}"
Style="{ThemeResource AlternateButtonStyle}"
ToolTipService.ToolTip="Go back"
Padding="8"
MinWidth="44"
MinHeight="44"
VerticalAlignment="Center">
<FontIcon Glyph="&#xE72B;"
FontSize="16" />
</Button>
<!-- Page Title -->
<StackPanel Grid.Column="1"
Margin="16,0,0,0"
VerticalAlignment="Center">
<TextBlock Text="{Binding PageTitle}"
FontSize="20"
FontWeight="SemiBold"
Foreground="{ThemeResource TextControlForeground}" />
<TextBlock Text="{Binding FilterDescription}"
FontSize="12"
Foreground="{ThemeResource SystemBaseMediumColor}"
Margin="0,4,0,0" />
</StackPanel>
</Grid>
<!-- Main Content -->
<Grid Grid.Row="1">
<!-- Loading State -->
<StackPanel Visibility="{Binding IsLoading}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Padding="32"
Spacing="16">
<ProgressRing IsActive="True"
IsIndeterminate="True"
Height="64"
Width="64"
Foreground="{ThemeResource SystemAccentColor}" />
<TextBlock Text="Loading computers..."
FontSize="14"
TextAlignment="Center"
Foreground="{ThemeResource SystemBaseMediumColor}" />
</StackPanel>
<!-- Error State -->
<StackPanel Visibility="{Binding HasError}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Padding="24"
Spacing="16"
MaxWidth="400">
<InfoBar IsOpen="True"
Severity="Error"
Title="Unable to Load Computers"
Message="{Binding ErrorMessage}"
IsClosable="False" />
<Button Content="Retry"
Command="{Binding LoadData}"
HorizontalAlignment="Center"
Style="{ThemeResource AccentButtonStyle}" />
</StackPanel>
<!-- Computers List -->
<Grid Visibility="{Binding IsDataLoaded}">
<ScrollViewer VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled">
<Grid Padding="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Count Header -->
<StackPanel Grid.Row="0"
Padding="16,12"
Orientation="Horizontal"
Spacing="4">
<TextBlock FontSize="12"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemBaseMediumColor}"
Text="RESULTS:" />
<TextBlock FontSize="12"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemBaseMediumColor}"
Text="{Binding ComputersList.Count}" />
<TextBlock FontSize="12"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemBaseMediumColor}"
Text="computers" />
</StackPanel>
<!-- Computers List -->
<ItemsControl Grid.Row="1"
ItemsSource="{Binding ComputersList}"
Padding="0"
Margin="0,8,0,0"
HorizontalAlignment="Stretch">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"
Spacing="0"
HorizontalAlignment="Stretch" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Padding="0"
Margin="0,0,0,8"
MinHeight="80"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
HorizontalAlignment="Stretch"
Command="{Binding DataContext.NavigateToComputerCommand, ElementName=PageRoot}"
CommandParameter="{Binding}"
Background="Transparent"
BorderThickness="0">
<Button.Template>
<ControlTemplate TargetType="Button">
<Grid MinHeight="80"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<!-- Shadow effect -->
<Border x:Name="ShadowBorder"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="16,12"
Translation="0, 0, 4"
VerticalAlignment="Stretch">
<Border.Shadow>
<ThemeShadow />
</Border.Shadow>
<Grid ColumnSpacing="16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- Computer Info -->
<StackPanel Grid.Column="0"
Spacing="8"
VerticalAlignment="Center"
HorizontalAlignment="Stretch">
<TextBlock Text="{Binding Name}"
FontSize="16"
FontWeight="SemiBold"
Foreground="{ThemeResource TextControlForeground}"
TextTrimming="CharacterEllipsis" />
<Grid ColumnSpacing="16"
Height="20"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0"
Orientation="Horizontal"
Spacing="6"
VerticalAlignment="Center">
<FontIcon Glyph="&#xE731;"
FontSize="14"
Foreground="{ThemeResource SystemAccentColor}" />
<TextBlock Text="{Binding Manufacturer}"
FontSize="13"
Foreground="{ThemeResource SystemBaseMediumColor}" />
</StackPanel>
<StackPanel Grid.Column="1"
Orientation="Horizontal"
Spacing="6"
VerticalAlignment="Center">
<FontIcon Glyph="&#xE787;"
FontSize="14"
Foreground="{ThemeResource SystemAccentColor}" />
<TextBlock FontSize="13"
Foreground="{ThemeResource SystemBaseMediumColor}">
<Run Text="{Binding Year}" />
</TextBlock>
</StackPanel>
</Grid>
</StackPanel>
<!-- Navigation Arrow -->
<StackPanel Grid.Column="1"
VerticalAlignment="Center">
<FontIcon Glyph="&#xE72A;"
FontSize="18"
Foreground="{ThemeResource SystemAccentColor}" />
</StackPanel>
</Grid>
</Border>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<VisualState.Setters>
<Setter Target="ShadowBorder.Background"
Value="{ThemeResource CardBackgroundFillColorSecondaryBrush}" />
<Setter Target="ShadowBorder.Translation"
Value="0, -2, 8" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<VisualState.Setters>
<Setter Target="ShadowBorder.Background"
Value="{ThemeResource CardBackgroundFillColorTertiaryBrush}" />
<Setter Target="ShadowBorder.Translation"
Value="0, 0, 2" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</ScrollViewer>
</Grid>
</Grid>
</Grid>
</Page>

View File

@@ -0,0 +1,37 @@
using Marechai.App.Presentation.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Marechai.App.Presentation.Views;
/// <summary>
/// Professional list view for displaying computers filtered by letter, year, or all.
/// Features responsive layout, modern styling, and smooth navigation.
/// </summary>
public sealed partial class ComputersListPage : Page
{
public ComputersListPage()
{
InitializeComponent();
Loaded += ComputersListPage_Loaded;
DataContextChanged += ComputersListPage_DataContextChanged;
}
private void ComputersListPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
{
if(DataContext is ComputersListViewModel vm)
{
// Load data when DataContext is set
vm.LoadData.Execute(null);
}
}
private void ComputersListPage_Loaded(object sender, RoutedEventArgs e)
{
if(DataContext is ComputersListViewModel vm)
{
// Load data when page is loaded (fallback)
vm.LoadData.Execute(null);
}
}
}

View File

@@ -0,0 +1,311 @@
<?xml version="1.0"
encoding="utf-8"?>
<Page x:Class="Marechai.App.Presentation.Views.ComputersPage"
x:Name="PageRoot"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:utu="using:Uno.Toolkit.UI"
NavigationCacheMode="Required"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid utu:SafeArea.Insets="VisibleBounds">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Header -->
<utu:NavigationBar Grid.Row="0"
Content="{Binding Path=Title}">
<utu:NavigationBar.MainCommand>
<AppBarButton Icon="Back"
Label="Back"
Command="{Binding GoBackCommand}"
AutomationProperties.Name="Go back" />
</utu:NavigationBar.MainCommand>
</utu:NavigationBar>
<!-- Content -->
<ScrollViewer Grid.Row="1">
<StackPanel Padding="16"
Spacing="24">
<!-- Computer Count Display -->
<StackPanel HorizontalAlignment="Center"
Spacing="8">
<TextBlock Text="{Binding ComputerCountText}"
TextAlignment="Center"
FontSize="18"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemBaseMediumColor}" />
<TextBlock Text="{Binding ComputerCount}"
TextAlignment="Center"
FontSize="48"
FontWeight="Bold"
Foreground="{ThemeResource SystemAccentColor}" />
</StackPanel>
<!-- Loading State -->
<StackPanel Visibility="{Binding IsLoading}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Padding="32"
Spacing="16">
<ProgressRing IsActive="True"
IsIndeterminate="True"
Height="48"
Width="48" />
<TextBlock Text="Loading..."
TextAlignment="Center"
FontSize="14" />
</StackPanel>
<!-- Error State -->
<StackPanel Visibility="{Binding HasError}"
Padding="16"
Background="{ThemeResource SystemErrorBackgroundColor}"
CornerRadius="8"
Spacing="8">
<TextBlock Text="Error"
FontWeight="Bold"
Foreground="{ThemeResource SystemErrorTextForegroundColor}" />
<TextBlock Text="{Binding ErrorMessage}"
Foreground="{ThemeResource SystemErrorTextForegroundColor}"
TextWrapping="Wrap" />
</StackPanel>
<!-- Main Content (visible when loaded and no error) -->
<StackPanel Visibility="{Binding IsDataLoaded}"
Spacing="24">
<!-- Letters Grid Section -->
<StackPanel Spacing="12">
<TextBlock Text="Browse by Letter"
FontSize="16"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemBaseMediumColor}" />
<ItemsRepeater ItemsSource="{Binding LettersList}"
Layout="{StaticResource LettersGridLayout}">
<ItemsRepeater.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}"
Command="{Binding DataContext.NavigateByLetterCommand, ElementName=PageRoot}"
CommandParameter="{Binding}"
Style="{StaticResource KeyboardKeyButtonStyle}" />
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</StackPanel>
<!-- Years Grid Section -->
<StackPanel Spacing="12">
<TextBlock Text="{Binding YearsGridTitle}"
FontSize="16"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemBaseMediumColor}" />
<ItemsRepeater ItemsSource="{Binding YearsList}"
Layout="{StaticResource YearsGridLayout}">
<ItemsRepeater.ItemTemplate>
<DataTemplate>
<Button Content="{Binding}"
Command="{Binding DataContext.NavigateByYearCommand, ElementName=PageRoot}"
CommandParameter="{Binding}"
Style="{StaticResource KeyboardKeyButtonStyle}" />
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</StackPanel>
<!-- All Computers and Search Section -->
<StackPanel Spacing="12">
<Button Content="All Computers"
Padding="16,12"
HorizontalAlignment="Stretch"
FontSize="16"
FontWeight="SemiBold"
Command="{Binding NavigateAllComputersCommand}"
Style="{StaticResource AccentButtonStyle}" />
<!-- Search Field (placeholder for future implementation) -->
<TextBox PlaceholderText="Search computers..."
Padding="12"
IsEnabled="False"
Opacity="0.5" />
</StackPanel>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Grid>
<Page.Resources>
<!-- Keyboard Key Button Style (revised: more padding, simplified borders to avoid clipping, darker scheme) -->
<Style x:Key="KeyboardKeyButtonStyle"
TargetType="Button">
<!-- Base appearance -->
<Setter Property="Foreground"
Value="#1A1A1A" />
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<GradientStop Color="#D6D6D6"
Offset="0" />
<GradientStop Color="#C2C2C2"
Offset="0.55" />
<GradientStop Color="#B0B0B0"
Offset="1" />
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="BorderBrush"
Value="#7A7A7A" />
<Setter Property="BorderThickness"
Value="1" />
<Setter Property="CornerRadius"
Value="6" />
<Setter Property="Padding"
Value="14,12" /> <!-- Increased vertical padding to prevent cutoff -->
<Setter Property="Margin"
Value="4" />
<Setter Property="FontFamily"
Value="Segoe UI" />
<Setter Property="FontWeight"
Value="SemiBold" />
<Setter Property="FontSize"
Value="15" />
<Setter Property="HorizontalAlignment"
Value="Stretch" />
<Setter Property="VerticalAlignment"
Value="Stretch" />
<Setter Property="HorizontalContentAlignment"
Value="Center" />
<Setter Property="VerticalContentAlignment"
Value="Center" />
<Setter Property="MinWidth"
Value="52" />
<Setter Property="MinHeight"
Value="52" /> <!-- Larger min height avoids clipping ascenders/descenders -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<!-- Shadow (simple) -->
<Border x:Name="Shadow"
CornerRadius="6"
Background="#33000000"
Margin="2,4,4,2" />
<!-- Key surface -->
<Border x:Name="KeyBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<!-- Inner highlight & content -->
<Grid>
<Border CornerRadius="{TemplateBinding CornerRadius}"
BorderBrush="#60FFFFFF"
BorderThickness="1,1,0,0" />
<Border CornerRadius="{TemplateBinding CornerRadius}"
BorderBrush="#30000000"
BorderThickness="0,0,1,1" />
<ContentPresenter x:Name="ContentPresenter"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="0"
TextWrapping="NoWrap" />
</Grid>
</Border>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<VisualState.Setters>
<Setter Target="KeyBorder.Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<GradientStop Color="#E0E0E0"
Offset="0" />
<GradientStop Color="#CFCFCF"
Offset="0.55" />
<GradientStop Color="#BDBDBD"
Offset="1" />
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Target="KeyBorder.BorderBrush"
Value="#5F5F5F" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<VisualState.Setters>
<Setter Target="KeyBorder.Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<GradientStop Color="#9C9C9C"
Offset="0" />
<GradientStop Color="#A8A8A8"
Offset="0.55" />
<GradientStop Color="#B4B4B4"
Offset="1" />
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Target="KeyBorder.BorderBrush"
Value="#4A4A4A" />
<Setter Target="KeyBorder.RenderTransform">
<Setter.Value>
<TranslateTransform Y="2" />
</Setter.Value>
</Setter>
<Setter Target="Shadow.Opacity"
Value="0.15" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="KeyBorder.Opacity"
Value="0.45" />
<Setter Target="ContentPresenter.Foreground"
Value="#777777" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused">
<VisualState.Setters>
<Setter Target="KeyBorder.BorderBrush"
Value="#3A7AFE" />
<Setter Target="KeyBorder.BorderThickness"
Value="2" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Unfocused" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Responsive Grid Layouts -->
<UniformGridLayout x:Key="LettersGridLayout"
ItemsStretch="Fill"
MinItemWidth="44"
MinItemHeight="44"
MaximumRowsOrColumns="13" />
<UniformGridLayout x:Key="YearsGridLayout"
ItemsStretch="Fill"
MinItemWidth="54"
MinItemHeight="44"
MaximumRowsOrColumns="10" />
</Page.Resources>
</Page>

View File

@@ -0,0 +1,44 @@
using Marechai.App.Presentation.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation;
namespace Marechai.App.Presentation.Views;
public sealed partial class ComputersPage : Page
{
public ComputersPage()
{
InitializeComponent();
DataContextChanged += ComputersPage_DataContextChanged;
Loaded += ComputersPage_Loaded;
}
private void ComputersPage_Loaded(object sender, RoutedEventArgs e)
{
if(DataContext is not ComputersViewModel viewModel) return;
// Trigger data loading
_ = viewModel.LoadData.ExecuteAsync(null);
}
private void ComputersPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
{
if(args.NewValue is ComputersViewModel viewModel)
{
// Trigger data loading when data context changes
_ = viewModel.LoadData.ExecuteAsync(null);
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if(DataContext is ComputersViewModel viewModel)
{
// Trigger data loading when navigating to the page
_ = viewModel.LoadData.ExecuteAsync(null);
}
}
}

View File

@@ -0,0 +1,261 @@
<?xml version="1.0"
encoding="utf-8"?>
<Page x:Class="Marechai.App.Presentation.Views.ConsolesListPage"
x:Name="PageRoot"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
NavigationCacheMode="Required"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid RowSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Header with Back Button and Title -->
<Grid Grid.Row="0"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
BorderThickness="0,0,0,1"
Padding="16,12,16,12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Back Button -->
<Button Grid.Column="0"
Command="{Binding GoBackCommand}"
Style="{ThemeResource AlternateButtonStyle}"
ToolTipService.ToolTip="Go back"
Padding="8"
MinWidth="44"
MinHeight="44"
VerticalAlignment="Center">
<FontIcon Glyph="&#xE72B;"
FontSize="16" />
</Button>
<!-- Page Title -->
<StackPanel Grid.Column="1"
Margin="16,0,0,0"
VerticalAlignment="Center">
<TextBlock Text="{Binding PageTitle}"
FontSize="20"
FontWeight="SemiBold"
Foreground="{ThemeResource TextControlForeground}" />
<TextBlock Text="{Binding FilterDescription}"
FontSize="12"
Foreground="{ThemeResource SystemBaseMediumColor}"
Margin="0,4,0,0" />
</StackPanel>
</Grid>
<!-- Main Content -->
<Grid Grid.Row="1">
<!-- Loading State -->
<StackPanel Visibility="{Binding IsLoading}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Padding="32"
Spacing="16">
<ProgressRing IsActive="True"
IsIndeterminate="True"
Height="64"
Width="64"
Foreground="{ThemeResource SystemAccentColor}" />
<TextBlock Text="Loading consoles..."
FontSize="14"
TextAlignment="Center"
Foreground="{ThemeResource SystemBaseMediumColor}" />
</StackPanel>
<!-- Error State -->
<StackPanel Visibility="{Binding HasError}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Padding="24"
Spacing="16"
MaxWidth="400">
<InfoBar IsOpen="True"
Severity="Error"
Title="Unable to Load Consoles"
Message="{Binding ErrorMessage}"
IsClosable="False" />
<Button Content="Retry"
Command="{Binding LoadData}"
HorizontalAlignment="Center"
Style="{ThemeResource AccentButtonStyle}" />
</StackPanel>
<!-- Consoles List -->
<Grid Visibility="{Binding IsDataLoaded}">
<ScrollViewer VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled">
<Grid Padding="8">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Count Header -->
<StackPanel Grid.Row="0"
Padding="16,12"
Orientation="Horizontal"
Spacing="4">
<TextBlock FontSize="12"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemBaseMediumColor}"
Text="RESULTS:" />
<TextBlock FontSize="12"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemBaseMediumColor}"
Text="{Binding ConsolesList.Count}" />
<TextBlock FontSize="12"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemBaseMediumColor}"
Text="consoles" />
</StackPanel>
<!-- Consoles List -->
<ItemsControl Grid.Row="1"
ItemsSource="{Binding ConsolesList}"
Padding="0"
Margin="0,8,0,0"
HorizontalAlignment="Stretch">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"
Spacing="0"
HorizontalAlignment="Stretch" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Padding="0"
Margin="0,0,0,8"
MinHeight="80"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
HorizontalAlignment="Stretch"
Command="{Binding DataContext.NavigateToConsoleCommand, ElementName=PageRoot}"
CommandParameter="{Binding}"
Background="Transparent"
BorderThickness="0">
<Button.Template>
<ControlTemplate TargetType="Button">
<Grid MinHeight="80"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<!-- Shadow effect -->
<Border x:Name="ShadowBorder"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="16,12"
Translation="0, 0, 4"
VerticalAlignment="Stretch">
<Border.Shadow>
<ThemeShadow />
</Border.Shadow>
<Grid ColumnSpacing="16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<!-- Console Info -->
<StackPanel Grid.Column="0"
Spacing="8"
VerticalAlignment="Center"
HorizontalAlignment="Stretch">
<TextBlock Text="{Binding Name}"
FontSize="16"
FontWeight="SemiBold"
Foreground="{ThemeResource TextControlForeground}"
TextTrimming="CharacterEllipsis" />
<Grid ColumnSpacing="16"
Height="20"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0"
Orientation="Horizontal"
Spacing="6"
VerticalAlignment="Center">
<FontIcon Glyph="&#xE731;"
FontSize="14"
Foreground="{ThemeResource SystemAccentColor}" />
<TextBlock Text="{Binding Manufacturer}"
FontSize="13"
Foreground="{ThemeResource SystemBaseMediumColor}" />
</StackPanel>
<StackPanel Grid.Column="1"
Orientation="Horizontal"
Spacing="6"
VerticalAlignment="Center">
<FontIcon Glyph="&#xE787;"
FontSize="14"
Foreground="{ThemeResource SystemAccentColor}" />
<TextBlock FontSize="13"
Foreground="{ThemeResource SystemBaseMediumColor}">
<Run Text="{Binding Year}" />
</TextBlock>
</StackPanel>
</Grid>
</StackPanel>
<!-- Navigation Arrow -->
<StackPanel Grid.Column="1"
VerticalAlignment="Center">
<FontIcon Glyph="&#xE72A;"
FontSize="18"
Foreground="{ThemeResource SystemAccentColor}" />
</StackPanel>
</Grid>
</Border>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<VisualState.Setters>
<Setter Target="ShadowBorder.Background"
Value="{ThemeResource CardBackgroundFillColorSecondaryBrush}" />
<Setter Target="ShadowBorder.Translation"
Value="0, -2, 8" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<VisualState.Setters>
<Setter Target="ShadowBorder.Background"
Value="{ThemeResource CardBackgroundFillColorTertiaryBrush}" />
<Setter Target="ShadowBorder.Translation"
Value="0, 0, 2" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</ScrollViewer>
</Grid>
</Grid>
</Grid>
</Page>

View File

@@ -0,0 +1,37 @@
using Marechai.App.Presentation.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Marechai.App.Presentation.Views;
/// <summary>
/// Professional list view for displaying consoles filtered by letter, year, or all.
/// Features responsive layout, modern styling, and smooth navigation.
/// </summary>
public sealed partial class ConsolesListPage : Page
{
public ConsolesListPage()
{
InitializeComponent();
Loaded += ConsolesListPage_Loaded;
DataContextChanged += ConsolesListPage_DataContextChanged;
}
private void ConsolesListPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
{
if(DataContext is ConsolesListViewModel vm)
{
// Load data when DataContext is set
vm.LoadData.Execute(null);
}
}
private void ConsolesListPage_Loaded(object sender, RoutedEventArgs e)
{
if(DataContext is ConsolesListViewModel vm)
{
// Load data when page is loaded (fallback)
vm.LoadData.Execute(null);
}
}
}

View File

@@ -0,0 +1,297 @@
<?xml version="1.0"
encoding="utf-8"?>
<Page x:Class="Marechai.App.Presentation.Views.ConsolesPage"
x:Name="PageRoot"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:utu="using:Uno.Toolkit.UI"
NavigationCacheMode="Required"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid utu:SafeArea.Insets="VisibleBounds">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Header -->
<utu:NavigationBar Grid.Row="0"
Content="{Binding Path=Title}">
<utu:NavigationBar.MainCommand>
<AppBarButton Icon="Back"
Label="Back"
Command="{Binding GoBackCommand}"
AutomationProperties.Name="Go back" />
</utu:NavigationBar.MainCommand>
</utu:NavigationBar>
<!-- Content -->
<ScrollViewer Grid.Row="1">
<StackPanel Padding="16"
Spacing="24">
<!-- Console Count Display -->
<StackPanel HorizontalAlignment="Center"
Spacing="8">
<TextBlock Text="{Binding ConsoleCountText}"
TextAlignment="Center"
FontSize="18"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemBaseMediumColor}" />
<TextBlock Text="{Binding ConsoleCount}"
TextAlignment="Center"
FontSize="48"
FontWeight="Bold"
Foreground="{ThemeResource SystemAccentColor}" />
</StackPanel>
<!-- Loading State -->
<StackPanel Visibility="{Binding IsLoading}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Padding="32"
Spacing="16">
<ProgressRing IsActive="True"
IsIndeterminate="True"
Height="48"
Width="48" />
<TextBlock Text="Loading..."
TextAlignment="Center"
FontSize="14" />
</StackPanel>
<!-- Error State -->
<StackPanel Visibility="{Binding HasError}"
Padding="16"
Background="{ThemeResource SystemErrorBackgroundColor}"
CornerRadius="8"
Spacing="8">
<TextBlock Text="Error"
FontWeight="Bold"
Foreground="{ThemeResource SystemErrorTextForegroundColor}" />
<TextBlock Text="{Binding ErrorMessage}"
Foreground="{ThemeResource SystemErrorTextForegroundColor}"
TextWrapping="Wrap" />
</StackPanel>
<!-- Main Content (visible when loaded and no error) -->
<StackPanel Visibility="{Binding IsDataLoaded}"
Spacing="24">
<!-- Letters Grid Section -->
<StackPanel Spacing="12">
<TextBlock Text="Browse by Letter"
FontSize="16"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemBaseMediumColor}" />
<ItemsRepeater ItemsSource="{Binding LettersList}"
Layout="{StaticResource LettersGridLayout}">
<ItemsRepeater.ItemTemplate></ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</StackPanel>
<!-- Years Grid Section -->
<StackPanel Spacing="12">
<TextBlock Text="{Binding YearsGridTitle}"
FontSize="16"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemBaseMediumColor}" />
<ItemsRepeater ItemsSource="{Binding YearsList}"
Layout="{StaticResource YearsGridLayout}">
<ItemsRepeater.ItemTemplate></ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</StackPanel>
<!-- All Consoles and Search Section -->
<StackPanel Spacing="12">
<Button Content="All Consoles"
Padding="16,12"
HorizontalAlignment="Stretch"
FontSize="16"
FontWeight="SemiBold"
Command="{Binding NavigateAllConsolesCommand}"
Style="{StaticResource AccentButtonStyle}" />
<!-- Search Field (placeholder for future implementation) -->
<TextBox PlaceholderText="Search consoles..."
Padding="12"
IsEnabled="False"
Opacity="0.5" />
</StackPanel>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Grid>
<Page.Resources>
<!-- Keyboard Key Button Style (revised: more padding, simplified borders to avoid clipping, darker scheme) -->
<Style x:Key="KeyboardKeyButtonStyle"
TargetType="Button">
<!-- Base appearance -->
<Setter Property="Foreground"
Value="#1A1A1A" />
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<GradientStop Color="#D6D6D6"
Offset="0" />
<GradientStop Color="#C2C2C2"
Offset="0.55" />
<GradientStop Color="#B0B0B0"
Offset="1" />
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="BorderBrush"
Value="#7A7A7A" />
<Setter Property="BorderThickness"
Value="1" />
<Setter Property="CornerRadius"
Value="6" />
<Setter Property="Padding"
Value="14,12" /> <!-- Increased vertical padding to prevent cutoff -->
<Setter Property="Margin"
Value="4" />
<Setter Property="FontFamily"
Value="Segoe UI" />
<Setter Property="FontWeight"
Value="SemiBold" />
<Setter Property="FontSize"
Value="15" />
<Setter Property="HorizontalAlignment"
Value="Stretch" />
<Setter Property="VerticalAlignment"
Value="Stretch" />
<Setter Property="HorizontalContentAlignment"
Value="Center" />
<Setter Property="VerticalContentAlignment"
Value="Center" />
<Setter Property="MinWidth"
Value="52" />
<Setter Property="MinHeight"
Value="52" /> <!-- Larger min height avoids clipping ascenders/descenders -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<!-- Shadow (simple) -->
<Border x:Name="Shadow"
CornerRadius="6"
Background="#33000000"
Margin="2,4,4,2" />
<!-- Key surface -->
<Border x:Name="KeyBorder"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}">
<!-- Inner highlight & content -->
<Grid>
<Border CornerRadius="{TemplateBinding CornerRadius}"
BorderBrush="#60FFFFFF"
BorderThickness="1,1,0,0" />
<Border CornerRadius="{TemplateBinding CornerRadius}"
BorderBrush="#30000000"
BorderThickness="0,0,1,1" />
<ContentPresenter x:Name="ContentPresenter"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="0"
TextWrapping="NoWrap" />
</Grid>
</Border>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<VisualState.Setters>
<Setter Target="KeyBorder.Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<GradientStop Color="#E0E0E0"
Offset="0" />
<GradientStop Color="#CFCFCF"
Offset="0.55" />
<GradientStop Color="#BDBDBD"
Offset="1" />
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Target="KeyBorder.BorderBrush"
Value="#5F5F5F" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<VisualState.Setters>
<Setter Target="KeyBorder.Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0"
EndPoint="0,1">
<GradientStop Color="#9C9C9C"
Offset="0" />
<GradientStop Color="#A8A8A8"
Offset="0.55" />
<GradientStop Color="#B4B4B4"
Offset="1" />
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Target="KeyBorder.BorderBrush"
Value="#4A4A4A" />
<Setter Target="KeyBorder.RenderTransform">
<Setter.Value>
<TranslateTransform Y="2" />
</Setter.Value>
</Setter>
<Setter Target="Shadow.Opacity"
Value="0.15" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="KeyBorder.Opacity"
Value="0.45" />
<Setter Target="ContentPresenter.Foreground"
Value="#777777" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused">
<VisualState.Setters>
<Setter Target="KeyBorder.BorderBrush"
Value="#3A7AFE" />
<Setter Target="KeyBorder.BorderThickness"
Value="2" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Unfocused" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Responsive Grid Layouts -->
<UniformGridLayout x:Key="LettersGridLayout"
ItemsStretch="Fill"
MinItemWidth="44"
MinItemHeight="44"
MaximumRowsOrColumns="13" />
<UniformGridLayout x:Key="YearsGridLayout"
ItemsStretch="Fill"
MinItemWidth="54"
MinItemHeight="44"
MaximumRowsOrColumns="10" />
</Page.Resources>
</Page>

View File

@@ -0,0 +1,44 @@
using Marechai.App.Presentation.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation;
namespace Marechai.App.Presentation.Views;
public sealed partial class ConsolesPage : Page
{
public ConsolesPage()
{
InitializeComponent();
DataContextChanged += ConsolesPage_DataContextChanged;
Loaded += ConsolesPage_Loaded;
}
private void ConsolesPage_Loaded(object sender, RoutedEventArgs e)
{
if(DataContext is not ConsolesViewModel viewModel) return;
// Trigger data loading
_ = viewModel.LoadData.ExecuteAsync(null);
}
private void ConsolesPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
{
if(args.NewValue is ConsolesViewModel viewModel)
{
// Trigger data loading when data context changes
_ = viewModel.LoadData.ExecuteAsync(null);
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if(DataContext is ConsolesViewModel viewModel)
{
// Trigger data loading when navigating to the page
_ = viewModel.LoadData.ExecuteAsync(null);
}
}
}

View File

@@ -0,0 +1,385 @@
<?xml version="1.0"
encoding="utf-8"?>
<Page x:Class="Marechai.App.Presentation.Views.MachineViewPage"
x:Name="PageRoot"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
NavigationCacheMode="Disabled"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid RowSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Header with Back Button -->
<Grid Grid.Row="0"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
BorderThickness="0,0,0,1"
Padding="12,12,16,12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Back Button -->
<Button Grid.Column="0"
Command="{Binding GoBackCommand}"
Style="{ThemeResource AlternateButtonStyle}"
ToolTipService.ToolTip="Go back"
Padding="8"
MinWidth="44"
MinHeight="44"
VerticalAlignment="Center">
<FontIcon Glyph="&#xE72B;"
FontSize="16" />
</Button>
<!-- Title Section -->
<StackPanel Grid.Column="1"
Margin="12,0,0,0"
VerticalAlignment="Center">
<TextBlock Text="{Binding MachineName}"
FontSize="20"
FontWeight="SemiBold"
Foreground="{ThemeResource TextControlForeground}"
TextTrimming="CharacterEllipsis" />
<TextBlock Text="{Binding CompanyName}"
FontSize="12"
Foreground="{ThemeResource SystemBaseMediumColor}"
Margin="0,4,0,0" />
</StackPanel>
</Grid>
<!-- Main Content -->
<Grid Grid.Row="1">
<!-- Loading State -->
<StackPanel Visibility="{Binding IsLoading}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Padding="32"
Spacing="16">
<ProgressRing IsActive="True"
IsIndeterminate="True"
Height="64"
Width="64"
Foreground="{ThemeResource SystemAccentColor}" />
<TextBlock Text="Loading machine details..."
FontSize="14"
TextAlignment="Center"
Foreground="{ThemeResource SystemBaseMediumColor}" />
</StackPanel>
<!-- Error State -->
<StackPanel Visibility="{Binding HasError}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Padding="24"
Spacing="16"
MaxWidth="400">
<InfoBar IsOpen="True"
Severity="Error"
Title="Unable to Load Machine"
Message="{Binding ErrorMessage}"
IsClosable="False" />
<Button Content="Retry"
Command="{Binding LoadData}"
HorizontalAlignment="Center"
Style="{ThemeResource AccentButtonStyle}" />
</StackPanel>
<!-- Machine Details -->
<ScrollViewer Visibility="{Binding IsDataLoaded}"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled">
<StackPanel Padding="16"
Spacing="24">
<!-- Prototype Badge -->
<StackPanel Visibility="{Binding IsPrototype}">
<Border Background="{ThemeResource WarningFillColorTertiaryBrush}"
BorderBrush="{ThemeResource WarningBorderColorDefaultBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="16,12">
<TextBlock Text="PROTOTYPE"
FontSize="14"
FontWeight="Bold"
Foreground="{ThemeResource WarningForegroundColorDefaultBrush}"
TextAlignment="Center" />
</Border>
</StackPanel>
<!-- Introduction Date -->
<StackPanel Visibility="{Binding ShowIntroductionDate}"
Spacing="8">
<TextBlock Text="Introduction Date"
FontSize="12"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemBaseMediumColor}" />
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="16,12">
<TextBlock Text="{Binding IntroductionDateDisplay}"
FontSize="16"
Foreground="{ThemeResource TextControlForeground}" />
</Border>
</StackPanel>
<!-- Family and Model -->
<Grid Visibility="{Binding ShowFamilyOrModel}"
ColumnSpacing="16">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Family -->
<StackPanel Grid.Column="0"
Visibility="{Binding ShowFamily}"
Spacing="8">
<TextBlock Text="Family"
FontSize="12"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemBaseMediumColor}" />
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="16,12">
<TextBlock Text="{Binding FamilyName}"
FontSize="14"
Foreground="{ThemeResource TextControlForeground}"
TextTrimming="CharacterEllipsis" />
</Border>
</StackPanel>
<!-- Model -->
<StackPanel Grid.Column="1"
Visibility="{Binding ShowModel}"
Spacing="8">
<TextBlock Text="Model"
FontSize="12"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemBaseMediumColor}" />
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="16,12">
<TextBlock Text="{Binding ModelName}"
FontSize="14"
Foreground="{ThemeResource TextControlForeground}"
TextTrimming="CharacterEllipsis" />
</Border>
</StackPanel>
</Grid>
<!-- Processors Section -->
<StackPanel Visibility="{Binding ShowProcessors}"
Spacing="12">
<TextBlock Text="Processors"
FontSize="14"
FontWeight="SemiBold"
Foreground="{ThemeResource TextControlForeground}" />
<ItemsControl ItemsSource="{Binding Processors}"
HorizontalAlignment="Stretch">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"
Spacing="8" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="16,12">
<StackPanel Spacing="8">
<TextBlock Text="{Binding DisplayName}"
FontSize="14"
FontWeight="SemiBold"
Foreground="{ThemeResource TextControlForeground}" />
<TextBlock Text="{Binding Manufacturer}"
FontSize="12"
Foreground="{ThemeResource SystemBaseMediumColor}" />
<TextBlock Text="{Binding DetailsText}"
FontSize="12"
Foreground="{ThemeResource SystemBaseMediumColor}"
TextWrapping="Wrap"
Visibility="{Binding HasDetails}" />
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<!-- Memory Section -->
<StackPanel Visibility="{Binding ShowMemory}"
Spacing="12">
<TextBlock Text="Memory"
FontSize="14"
FontWeight="SemiBold"
Foreground="{ThemeResource TextControlForeground}" />
<ItemsControl ItemsSource="{Binding Memory}"
HorizontalAlignment="Stretch">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"
Spacing="8" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="16,12">
<StackPanel Spacing="6">
<TextBlock Text="{Binding SizeDisplay}"
FontSize="14"
FontWeight="SemiBold"
Foreground="{ThemeResource TextControlForeground}" />
<TextBlock Text="{Binding TypeDisplay}"
FontSize="12"
Foreground="{ThemeResource SystemBaseMediumColor}" />
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<!-- GPUs Section -->
<StackPanel Visibility="{Binding ShowGpus}"
Spacing="12">
<TextBlock Text="Graphics Processing Units"
FontSize="14"
FontWeight="SemiBold"
Foreground="{ThemeResource TextControlForeground}" />
<ItemsControl ItemsSource="{Binding Gpus}"
HorizontalAlignment="Stretch">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"
Spacing="8" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="16,12">
<StackPanel Spacing="8">
<TextBlock Text="{Binding DisplayName}"
FontSize="14"
FontWeight="SemiBold"
Foreground="{ThemeResource TextControlForeground}" />
<TextBlock Text="{Binding Manufacturer}"
FontSize="12"
Foreground="{ThemeResource SystemBaseMediumColor}"
Visibility="{Binding HasManufacturer}" />
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<!-- Sound Synthesizers Section -->
<StackPanel Visibility="{Binding ShowSoundSynthesizers}"
Spacing="12">
<TextBlock Text="Sound Synthesizers"
FontSize="14"
FontWeight="SemiBold"
Foreground="{ThemeResource TextControlForeground}" />
<ItemsControl ItemsSource="{Binding SoundSynthesizers}"
HorizontalAlignment="Stretch">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"
Spacing="8" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="16,12">
<StackPanel Spacing="8">
<TextBlock Text="{Binding DisplayName}"
FontSize="14"
FontWeight="SemiBold"
Foreground="{ThemeResource TextControlForeground}" />
<TextBlock Text="{Binding DetailsText}"
FontSize="12"
Foreground="{ThemeResource SystemBaseMediumColor}"
TextWrapping="Wrap"
Visibility="{Binding HasDetails}" />
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<!-- Storage Section -->
<StackPanel Visibility="{Binding ShowStorage}"
Spacing="12">
<TextBlock Text="Storage"
FontSize="14"
FontWeight="SemiBold"
Foreground="{ThemeResource TextControlForeground}" />
<ItemsControl ItemsSource="{Binding Storage}"
HorizontalAlignment="Stretch">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"
Spacing="8" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
CornerRadius="8"
Padding="16,12">
<StackPanel Spacing="6">
<TextBlock Text="{Binding DisplayText}"
FontSize="13"
Foreground="{ThemeResource TextControlForeground}"
TextWrapping="Wrap" />
<TextBlock Text="{Binding TypeNote}"
FontSize="11"
Foreground="{ThemeResource SystemBaseMediumColor}"
TextWrapping="Wrap" />
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<!-- Bottom Spacing -->
<Border Height="24" />
</StackPanel>
</ScrollViewer>
</Grid>
</Grid>
</Page>

View File

@@ -0,0 +1,82 @@
/******************************************************************************
// MARECHAI: Master repository of computing history artifacts information
// ----------------------------------------------------------------------------
//
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// --[ License ] --------------------------------------------------------------
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2003-2026 Natalia Portillo
*******************************************************************************/
#nullable enable
using Marechai.App.Presentation.Models;
using Marechai.App.Presentation.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation;
namespace Marechai.App.Presentation.Views;
public sealed partial class MachineViewPage : Page
{
private object? _navigationSource;
private int? _pendingMachineId;
public MachineViewPage()
{
InitializeComponent();
DataContextChanged += MachineViewPage_DataContextChanged;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
int? machineId = null;
// Handle both int and MachineViewNavigationParameter
if(e.Parameter is int intId)
machineId = intId;
else if(e.Parameter is MachineViewNavigationParameter navParam)
{
machineId = navParam.MachineId;
_navigationSource = navParam.NavigationSource;
}
if(machineId.HasValue)
{
_pendingMachineId = machineId;
if(DataContext is MachineViewViewModel viewModel)
{
viewModel.SetNavigationSource(_navigationSource);
_ = viewModel.LoadMachineAsync(machineId.Value);
}
}
}
private void MachineViewPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
{
if(DataContext is MachineViewViewModel viewModel && _pendingMachineId.HasValue)
{
viewModel.SetNavigationSource(_navigationSource);
_ = viewModel.LoadMachineAsync(_pendingMachineId.Value);
}
}
}

View File

@@ -0,0 +1,55 @@
<?xml version="1.0"
encoding="utf-8"?>
<Page x:Class="Marechai.App.Presentation.Views.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:utu="using:Uno.Toolkit.UI"
xmlns:uen="using:Uno.Extensions.Navigation.UI"
xmlns:components="clr-namespace:Marechai.App.Presentation.Components"
NavigationCacheMode="Required"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid utu:SafeArea.Insets="VisibleBounds">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="SidebarColumn"
Width="280" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Sidebar -->
<Grid x:Name="SidebarWrapper"
Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="0"
Width="280"
HorizontalAlignment="Left">
<components:Sidebar x:Name="SidebarPanel"
DataContext="{Binding}"
VerticalAlignment="Stretch" />
</Grid>
<!-- Header -->
<utu:NavigationBar Grid.Row="0"
Grid.Column="1"
Content="{Binding Title}">
<utu:NavigationBar.MainCommand>
<AppBarButton Icon="GlobalNavigationButton"
Command="{Binding ToggleSidebarCommand}"
Label="Toggle Sidebar"
AutomationProperties.Name="Toggle sidebar visibility" />
</utu:NavigationBar.MainCommand>
</utu:NavigationBar>
<!-- Content Region for Navigation -->
<ContentControl Grid.Row="1"
Grid.Column="1"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
uen:Region.Attached="True"
uen:Region.Name="Main" />
</Grid>
</Page>

View File

@@ -0,0 +1,98 @@
using System;
using System.ComponentModel;
using Marechai.App.Presentation.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Marechai.App.Presentation.Views;
public sealed partial class MainPage : Page
{
private PropertyChangedEventHandler _sidebarPropertyChangedHandler;
public MainPage()
{
InitializeComponent();
DataContextChanged += MainPage_DataContextChanged;
Loaded += MainPage_Loaded;
}
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
if(DataContext is not MainViewModel viewModel) return;
SidebarWrapper.Width = viewModel.IsSidebarOpen ? 280 : 60;
if(_sidebarPropertyChangedHandler != null) return;
_sidebarPropertyChangedHandler = (_, propArgs) =>
{
if(propArgs.PropertyName != nameof(MainViewModel.IsSidebarOpen)) return;
AnimateSidebarWidth(((MainViewModel)DataContext).IsSidebarOpen);
};
((INotifyPropertyChanged)viewModel).PropertyChanged += _sidebarPropertyChangedHandler;
}
void AnimateSidebarWidth(bool isOpen)
{
double start = SidebarColumn.Width.Value;
double end = isOpen ? 280 : 60;
if(Math.Abs(start - end) < 0.1) return;
// If expanding, show content immediately
if(isOpen && DataContext is MainViewModel vm) vm.SidebarContentVisible = true;
const int durationMs = 250;
const int fps = 60;
var steps = (int)(durationMs / (1000.0 / fps));
var currentStep = 0;
var timer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(1000.0 / fps)
};
timer.Tick += (_, _) =>
{
currentStep++;
double t = (double)currentStep / steps;
// Ease in-out cubic
double eased = t < 0.5 ? 4 * t * t * t : 1 - Math.Pow(-2 * t + 2, 3) / 2;
double value = start + (end - start) * eased;
SidebarColumn.Width = new GridLength(value, GridUnitType.Pixel);
SidebarWrapper.Width = value;
if(currentStep >= steps)
{
SidebarColumn.Width = new GridLength(end, GridUnitType.Pixel);
SidebarWrapper.Width = end;
timer.Stop();
// After collapse animation completes, hide sidebar content
if(!isOpen && DataContext is MainViewModel vm) vm.SidebarContentVisible = false;
}
};
timer.Start();
}
private void MainPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
{
if(args.NewValue is MainViewModel vm && _sidebarPropertyChangedHandler == null)
{
SidebarWrapper.Width = vm.IsSidebarOpen ? 280 : 60;
_sidebarPropertyChangedHandler = (_, propArgs) =>
{
if(propArgs.PropertyName != nameof(MainViewModel.IsSidebarOpen)) return;
AnimateSidebarWidth(vm.IsSidebarOpen);
};
((INotifyPropertyChanged)vm).PropertyChanged += _sidebarPropertyChangedHandler;
}
}
}

View File

@@ -1,7 +1,7 @@
<?xml version="1.0"
<?xml version="1.0"
encoding="utf-8"?>
<Page x:Class="Marechai.App.Presentation.MainPage"
<Page x:Class="Marechai.App.Presentation.Views.NewsPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:utu="using:Uno.Toolkit.UI"
@@ -9,16 +9,7 @@
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid utu:SafeArea.Insets="VisibleBounds">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Header -->
<utu:NavigationBar Content="{Binding Title}" />
<!-- Refresh Container with Pull-to-Refresh -->
<RefreshContainer Grid.Row="1"
x:Name="RefreshContainer"
<RefreshContainer x:Name="RefreshContainer"
RefreshRequested="RefreshContainer_RefreshRequested">
<ScrollViewer>
<Grid Padding="16">
@@ -42,7 +33,7 @@
<!-- Loading State -->
<StackPanel Grid.Row="2"
Visibility="{Binding NewsViewModel.IsLoading}"
Visibility="{Binding IsLoading}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Padding="32"
@@ -60,62 +51,69 @@
<!-- Error State -->
<StackPanel Grid.Row="2"
Visibility="{Binding NewsViewModel.HasError}"
Visibility="{Binding HasError}"
VerticalAlignment="Center"
Spacing="16"
Padding="32">
<InfoBar IsOpen="True"
Severity="Error"
Title="Unable to Load News"
Message="{Binding NewsViewModel.ErrorMessage}"
Message="{Binding ErrorMessage}"
IsClosable="False" />
<Button Content="Retry"
Command="{Binding NewsViewModel.LoadNews}"
Command="{Binding LoadNews}"
HorizontalAlignment="Center"
Style="{ThemeResource AccentButtonStyle}" />
</StackPanel>
<!-- News Feed -->
<ItemsControl Grid.Row="2"
ItemsSource="{Binding NewsViewModel.NewsList}">
ItemsSource="{Binding NewsList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="0,0,0,12"
CornerRadius="8"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
Padding="16">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Button Margin="0,0,0,12"
Padding="0"
Background="Transparent"
BorderThickness="0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Command="{Binding NavigateToItemCommand}"
CommandParameter="{Binding News}">
<Border CornerRadius="8"
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
BorderThickness="1"
Padding="16">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!-- Date -->
<TextBlock Grid.Row="0"
Text="{Binding News.Timestamp}"
FontSize="12"
Foreground="{ThemeResource SystemBaseMediumColor}"
Margin="0,0,0,12" />
<!-- Date -->
<TextBlock Grid.Row="0"
Text="{Binding News.Timestamp}"
FontSize="12"
Foreground="{ThemeResource SystemBaseMediumColor}"
Margin="0,0,0,12" />
<!-- News Title/Text (Localized) -->
<TextBlock Grid.Row="1"
Text="{Binding DisplayText}"
FontSize="16"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemBaseHighColor}"
TextWrapping="Wrap"
Margin="0,0,0,12" />
<!-- News Title/Text (Localized) -->
<TextBlock Grid.Row="1"
Text="{Binding DisplayText}"
FontSize="16"
FontWeight="SemiBold"
Foreground="{ThemeResource SystemBaseHighColor}"
TextWrapping="Wrap"
Margin="0,0,0,12" />
<!-- Item Name Link -->
<HyperlinkButton Grid.Row="2"
Content="{Binding News.ItemName}"
FontSize="14"
Padding="0,4,0,4"
Foreground="{ThemeResource SystemAccentColor}" />
</Grid>
</Border>
<!-- Item Name -->
<TextBlock Grid.Row="2"
Text="{Binding News.ItemName}"
FontSize="14"
Foreground="{ThemeResource SystemAccentColor}" />
</Grid>
</Border>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>

View File

@@ -0,0 +1,73 @@
using System;
using Windows.Foundation;
using Marechai.App.Presentation.ViewModels;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation;
namespace Marechai.App.Presentation.Views;
public sealed partial class NewsPage : Page
{
private bool _initialNewsLoaded;
public NewsPage()
{
InitializeComponent();
DataContextChanged += NewsPage_DataContextChanged;
Loaded += NewsPage_Loaded;
}
private void NewsPage_Loaded(object sender, RoutedEventArgs e)
{
if(_initialNewsLoaded) return;
if(DataContext is NewsViewModel viewModel)
{
_initialNewsLoaded = true;
_ = viewModel.LoadNews.ExecuteAsync(null);
DataContextChanged -= NewsPage_DataContextChanged;
}
}
private void NewsPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
{
if(_initialNewsLoaded) return;
if(args.NewValue is NewsViewModel viewModel)
{
_initialNewsLoaded = true;
_ = viewModel.LoadNews.ExecuteAsync(null);
DataContextChanged -= NewsPage_DataContextChanged;
}
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if(_initialNewsLoaded) return;
if(DataContext is NewsViewModel viewModel)
{
_initialNewsLoaded = true;
_ = viewModel.LoadNews.ExecuteAsync(null);
DataContextChanged -= NewsPage_DataContextChanged;
}
}
private async void RefreshContainer_RefreshRequested(RefreshContainer sender, RefreshRequestedEventArgs args)
{
// Handle pull-to-refresh
using Deferral deferral = args.GetDeferral();
try
{
if(DataContext is NewsViewModel viewModel) await viewModel.LoadNews.ExecuteAsync(null);
}
catch(Exception)
{
// Swallow to avoid process crash; NewsViewModel already logs errors.
}
}
}

View File

@@ -0,0 +1,22 @@
<Page x:Class="Marechai.App.Presentation.Views.SecondPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:utu="using:Uno.Toolkit.UI"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid utu:SafeArea.Insets="VisibleBounds">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" /> <RowDefinition />
</Grid.RowDefinitions>
<utu:NavigationBar Content="Second Page" />
<StackPanel Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock Text="{Binding Entity.Name}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="8" />
</StackPanel>
</Grid>
</Page>

View File

@@ -1,11 +1,11 @@
using Microsoft.UI.Xaml.Controls;
namespace Marechai.App.Presentation;
namespace Marechai.App.Presentation.Views;
public sealed partial class SecondPage : Page
{
public SecondPage()
{
this.InitializeComponent();
InitializeComponent();
}
}
}

View File

@@ -0,0 +1,34 @@
<UserControl x:Class="Marechai.App.Presentation.Views.Shell"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:utu="using:Uno.Toolkit.UI"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Border Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<utu:ExtendedSplashScreen x:Name="Splash"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch">
<utu:ExtendedSplashScreen.LoadingContentTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*" /> <RowDefinition />
</Grid.RowDefinitions>
<ProgressRing IsActive="True"
Grid.Row="1"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Height="100"
Width="100" />
</Grid>
</DataTemplate>
</utu:ExtendedSplashScreen.LoadingContentTemplate>
</utu:ExtendedSplashScreen>
</Border>
</UserControl>

View File

@@ -1,14 +1,14 @@
using Microsoft.UI.Xaml.Controls;
using Uno.Extensions.Hosting;
namespace Marechai.App.Presentation;
namespace Marechai.App.Presentation.Views;
public sealed partial class Shell : UserControl, IContentControlProvider
{
public Shell()
{
this.InitializeComponent();
InitializeComponent();
}
public ContentControl ContentControl => Splash;
}
}

View File

@@ -3,12 +3,9 @@
using Marechai.App.Auth;
using Marechai.App.Books;
using Marechai.App.BrowserTests;
using Marechai.App.ByLetter;
using Marechai.App.ByYear;
using Marechai.App.Companies;
using Marechai.App.Computers;
using Marechai.App.Consoles;
using Marechai.App.Count;
using Marechai.App.Countries;
using Marechai.App.Currencies;
using Marechai.App.Documents;
@@ -25,10 +22,8 @@ using Marechai.App.Machines;
using Marechai.App.Magazines;
using Marechai.App.MagazinesByMachine;
using Marechai.App.MagazinesByMachineFamily;
using Marechai.App.MaximumYear;
using Marechai.App.Medias;
using Marechai.App.MemoriesByMachine;
using Marechai.App.MinimumYear;
using Marechai.App.News;
using Marechai.App.People;
using Marechai.App.PeopleByBook;
@@ -79,16 +74,6 @@ namespace Marechai.App
{
get => new global::Marechai.App.BrowserTests.BrowserTestsRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>The byLetter property</summary>
public global::Marechai.App.ByLetter.ByLetterRequestBuilder ByLetter
{
get => new global::Marechai.App.ByLetter.ByLetterRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>The byYear property</summary>
public global::Marechai.App.ByYear.ByYearRequestBuilder ByYear
{
get => new global::Marechai.App.ByYear.ByYearRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>The companies property</summary>
public global::Marechai.App.Companies.CompaniesRequestBuilder Companies
{
@@ -104,11 +89,6 @@ namespace Marechai.App
{
get => new global::Marechai.App.Consoles.ConsolesRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>The count property</summary>
public global::Marechai.App.Count.CountRequestBuilder Count
{
get => new global::Marechai.App.Count.CountRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>The countries property</summary>
public global::Marechai.App.Countries.CountriesRequestBuilder Countries
{
@@ -189,11 +169,6 @@ namespace Marechai.App
{
get => new global::Marechai.App.MagazinesByMachineFamily.MagazinesByMachineFamilyRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>The maximumYear property</summary>
public global::Marechai.App.MaximumYear.MaximumYearRequestBuilder MaximumYear
{
get => new global::Marechai.App.MaximumYear.MaximumYearRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>The medias property</summary>
public global::Marechai.App.Medias.MediasRequestBuilder Medias
{
@@ -204,11 +179,6 @@ namespace Marechai.App
{
get => new global::Marechai.App.MemoriesByMachine.MemoriesByMachineRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>The minimumYear property</summary>
public global::Marechai.App.MinimumYear.MinimumYearRequestBuilder MinimumYear
{
get => new global::Marechai.App.MinimumYear.MinimumYearRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>The news property</summary>
public global::Marechai.App.News.NewsRequestBuilder News
{
@@ -309,7 +279,7 @@ namespace Marechai.App
ApiClientBuilder.RegisterDefaultDeserializer<FormParseNodeFactory>();
if (string.IsNullOrEmpty(RequestAdapter.BaseUrl))
{
RequestAdapter.BaseUrl = "http://localhost:5023";
RequestAdapter.BaseUrl = "https://localhost:7163";
}
PathParameters.TryAdd("baseurl", RequestAdapter.BaseUrl);
}

View File

@@ -0,0 +1,48 @@
// <auto-generated/>
#pragma warning disable CS0618
using Marechai.App.Computers.ByLetter.Item;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System;
namespace Marechai.App.Computers.ByLetter
{
/// <summary>
/// Builds and executes requests for operations under \computers\by-letter
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class ByLetterRequestBuilder : BaseRequestBuilder
{
/// <summary>Gets an item from the Marechai.App.computers.byLetter.item collection</summary>
/// <param name="position">Unique identifier of the item</param>
/// <returns>A <see cref="global::Marechai.App.Computers.ByLetter.Item.WithCItemRequestBuilder"/></returns>
public global::Marechai.App.Computers.ByLetter.Item.WithCItemRequestBuilder this[string position]
{
get
{
var urlTplParams = new Dictionary<string, object>(PathParameters);
urlTplParams.Add("c", position);
return new global::Marechai.App.Computers.ByLetter.Item.WithCItemRequestBuilder(urlTplParams, RequestAdapter);
}
}
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Computers.ByLetter.ByLetterRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="pathParameters">Path parameters for the request</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public ByLetterRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/computers/by-letter", pathParameters)
{
}
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Computers.ByLetter.ByLetterRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public ByLetterRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/computers/by-letter", rawUrl)
{
}
}
}
#pragma warning restore CS0618

View File

@@ -0,0 +1,92 @@
// <auto-generated/>
#pragma warning disable CS0618
using Marechai.App.Models;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Threading;
using System;
namespace Marechai.App.Computers.ByLetter.Item
{
/// <summary>
/// Builds and executes requests for operations under \computers\by-letter\{c}
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class WithCItemRequestBuilder : BaseRequestBuilder
{
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Computers.ByLetter.Item.WithCItemRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="pathParameters">Path parameters for the request</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public WithCItemRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/computers/by-letter/{c}", pathParameters)
{
}
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Computers.ByLetter.Item.WithCItemRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public WithCItemRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/computers/by-letter/{c}", rawUrl)
{
}
/// <returns>A List&lt;global::Marechai.App.Models.MachineDto&gt;</returns>
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 400 status code</exception>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public async Task<List<global::Marechai.App.Models.MachineDto>?> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
{
#nullable restore
#else
public async Task<List<global::Marechai.App.Models.MachineDto>> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
{
#endif
var requestInfo = ToGetRequestInformation(requestConfiguration);
var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
{
{ "400", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
};
var collectionResult = await RequestAdapter.SendCollectionAsync<global::Marechai.App.Models.MachineDto>(requestInfo, global::Marechai.App.Models.MachineDto.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false);
return collectionResult?.AsList();
}
/// <returns>A <see cref="RequestInformation"/></returns>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
{
#nullable restore
#else
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
{
#endif
var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters);
requestInfo.Configure(requestConfiguration);
requestInfo.Headers.TryAdd("Accept", "text/plain;q=0.9");
return requestInfo;
}
/// <summary>
/// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored.
/// </summary>
/// <returns>A <see cref="global::Marechai.App.Computers.ByLetter.Item.WithCItemRequestBuilder"/></returns>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
public global::Marechai.App.Computers.ByLetter.Item.WithCItemRequestBuilder WithUrl(string rawUrl)
{
return new global::Marechai.App.Computers.ByLetter.Item.WithCItemRequestBuilder(rawUrl, RequestAdapter);
}
/// <summary>
/// Configuration for the request such as headers, query parameters, and middleware options.
/// </summary>
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class WithCItemRequestBuilderGetRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
{
}
}
}
#pragma warning restore CS0618

View File

@@ -0,0 +1,61 @@
// <auto-generated/>
#pragma warning disable CS0618
using Marechai.App.Computers.ByYear.Item;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System;
namespace Marechai.App.Computers.ByYear
{
/// <summary>
/// Builds and executes requests for operations under \computers\by-year
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class ByYearRequestBuilder : BaseRequestBuilder
{
/// <summary>Gets an item from the Marechai.App.computers.byYear.item collection</summary>
/// <param name="position">Unique identifier of the item</param>
/// <returns>A <see cref="global::Marechai.App.Computers.ByYear.Item.WithYearItemRequestBuilder"/></returns>
public global::Marechai.App.Computers.ByYear.Item.WithYearItemRequestBuilder this[int position]
{
get
{
var urlTplParams = new Dictionary<string, object>(PathParameters);
urlTplParams.Add("year", position);
return new global::Marechai.App.Computers.ByYear.Item.WithYearItemRequestBuilder(urlTplParams, RequestAdapter);
}
}
/// <summary>Gets an item from the Marechai.App.computers.byYear.item collection</summary>
/// <param name="position">Unique identifier of the item</param>
/// <returns>A <see cref="global::Marechai.App.Computers.ByYear.Item.WithYearItemRequestBuilder"/></returns>
[Obsolete("This indexer is deprecated and will be removed in the next major version. Use the one with the typed parameter instead.")]
public global::Marechai.App.Computers.ByYear.Item.WithYearItemRequestBuilder this[string position]
{
get
{
var urlTplParams = new Dictionary<string, object>(PathParameters);
if (!string.IsNullOrWhiteSpace(position)) urlTplParams.Add("year", position);
return new global::Marechai.App.Computers.ByYear.Item.WithYearItemRequestBuilder(urlTplParams, RequestAdapter);
}
}
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Computers.ByYear.ByYearRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="pathParameters">Path parameters for the request</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public ByYearRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/computers/by-year", pathParameters)
{
}
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Computers.ByYear.ByYearRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public ByYearRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/computers/by-year", rawUrl)
{
}
}
}
#pragma warning restore CS0618

View File

@@ -0,0 +1,92 @@
// <auto-generated/>
#pragma warning disable CS0618
using Marechai.App.Models;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Threading;
using System;
namespace Marechai.App.Computers.ByYear.Item
{
/// <summary>
/// Builds and executes requests for operations under \computers\by-year\{year}
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class WithYearItemRequestBuilder : BaseRequestBuilder
{
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Computers.ByYear.Item.WithYearItemRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="pathParameters">Path parameters for the request</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public WithYearItemRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/computers/by-year/{year}", pathParameters)
{
}
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Computers.ByYear.Item.WithYearItemRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public WithYearItemRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/computers/by-year/{year}", rawUrl)
{
}
/// <returns>A List&lt;global::Marechai.App.Models.MachineDto&gt;</returns>
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 400 status code</exception>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public async Task<List<global::Marechai.App.Models.MachineDto>?> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
{
#nullable restore
#else
public async Task<List<global::Marechai.App.Models.MachineDto>> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
{
#endif
var requestInfo = ToGetRequestInformation(requestConfiguration);
var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
{
{ "400", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
};
var collectionResult = await RequestAdapter.SendCollectionAsync<global::Marechai.App.Models.MachineDto>(requestInfo, global::Marechai.App.Models.MachineDto.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false);
return collectionResult?.AsList();
}
/// <returns>A <see cref="RequestInformation"/></returns>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
{
#nullable restore
#else
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
{
#endif
var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters);
requestInfo.Configure(requestConfiguration);
requestInfo.Headers.TryAdd("Accept", "text/plain;q=0.9");
return requestInfo;
}
/// <summary>
/// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored.
/// </summary>
/// <returns>A <see cref="global::Marechai.App.Computers.ByYear.Item.WithYearItemRequestBuilder"/></returns>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
public global::Marechai.App.Computers.ByYear.Item.WithYearItemRequestBuilder WithUrl(string rawUrl)
{
return new global::Marechai.App.Computers.ByYear.Item.WithYearItemRequestBuilder(rawUrl, RequestAdapter);
}
/// <summary>
/// Configuration for the request such as headers, query parameters, and middleware options.
/// </summary>
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class WithYearItemRequestBuilderGetRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
{
}
}
}
#pragma warning restore CS0618

View File

@@ -1,5 +1,10 @@
// <auto-generated/>
#pragma warning disable CS0618
using Marechai.App.Computers.ByLetter;
using Marechai.App.Computers.ByYear;
using Marechai.App.Computers.Count;
using Marechai.App.Computers.MaximumYear;
using Marechai.App.Computers.MinimumYear;
using Marechai.App.Models;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
@@ -17,6 +22,31 @@ namespace Marechai.App.Computers
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class ComputersRequestBuilder : BaseRequestBuilder
{
/// <summary>The byLetter property</summary>
public global::Marechai.App.Computers.ByLetter.ByLetterRequestBuilder ByLetter
{
get => new global::Marechai.App.Computers.ByLetter.ByLetterRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>The byYear property</summary>
public global::Marechai.App.Computers.ByYear.ByYearRequestBuilder ByYear
{
get => new global::Marechai.App.Computers.ByYear.ByYearRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>The count property</summary>
public global::Marechai.App.Computers.Count.CountRequestBuilder Count
{
get => new global::Marechai.App.Computers.Count.CountRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>The maximumYear property</summary>
public global::Marechai.App.Computers.MaximumYear.MaximumYearRequestBuilder MaximumYear
{
get => new global::Marechai.App.Computers.MaximumYear.MaximumYearRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>The minimumYear property</summary>
public global::Marechai.App.Computers.MinimumYear.MinimumYearRequestBuilder MinimumYear
{
get => new global::Marechai.App.Computers.MinimumYear.MinimumYearRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Computers.ComputersRequestBuilder"/> and sets the default values.
/// </summary>

View File

@@ -0,0 +1,91 @@
// <auto-generated/>
#pragma warning disable CS0618
using Marechai.App.Models;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Threading;
using System;
namespace Marechai.App.Computers.Count
{
/// <summary>
/// Builds and executes requests for operations under \computers\count
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class CountRequestBuilder : BaseRequestBuilder
{
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Computers.Count.CountRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="pathParameters">Path parameters for the request</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public CountRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/computers/count", pathParameters)
{
}
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Computers.Count.CountRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public CountRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/computers/count", rawUrl)
{
}
/// <returns>A <see cref="UntypedNode"/></returns>
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 400 status code</exception>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public async Task<UntypedNode?> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
{
#nullable restore
#else
public async Task<UntypedNode> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
{
#endif
var requestInfo = ToGetRequestInformation(requestConfiguration);
var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
{
{ "400", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
};
return await RequestAdapter.SendAsync<UntypedNode>(requestInfo, UntypedNode.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false);
}
/// <returns>A <see cref="RequestInformation"/></returns>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
{
#nullable restore
#else
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
{
#endif
var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters);
requestInfo.Configure(requestConfiguration);
requestInfo.Headers.TryAdd("Accept", "text/plain;q=0.9");
return requestInfo;
}
/// <summary>
/// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored.
/// </summary>
/// <returns>A <see cref="global::Marechai.App.Computers.Count.CountRequestBuilder"/></returns>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
public global::Marechai.App.Computers.Count.CountRequestBuilder WithUrl(string rawUrl)
{
return new global::Marechai.App.Computers.Count.CountRequestBuilder(rawUrl, RequestAdapter);
}
/// <summary>
/// Configuration for the request such as headers, query parameters, and middleware options.
/// </summary>
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class CountRequestBuilderGetRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
{
}
}
}
#pragma warning restore CS0618

View File

@@ -0,0 +1,91 @@
// <auto-generated/>
#pragma warning disable CS0618
using Marechai.App.Models;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Threading;
using System;
namespace Marechai.App.Computers.MaximumYear
{
/// <summary>
/// Builds and executes requests for operations under \computers\maximum-year
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class MaximumYearRequestBuilder : BaseRequestBuilder
{
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Computers.MaximumYear.MaximumYearRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="pathParameters">Path parameters for the request</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public MaximumYearRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/computers/maximum-year", pathParameters)
{
}
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Computers.MaximumYear.MaximumYearRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public MaximumYearRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/computers/maximum-year", rawUrl)
{
}
/// <returns>A <see cref="UntypedNode"/></returns>
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 400 status code</exception>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public async Task<UntypedNode?> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
{
#nullable restore
#else
public async Task<UntypedNode> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
{
#endif
var requestInfo = ToGetRequestInformation(requestConfiguration);
var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
{
{ "400", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
};
return await RequestAdapter.SendAsync<UntypedNode>(requestInfo, UntypedNode.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false);
}
/// <returns>A <see cref="RequestInformation"/></returns>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
{
#nullable restore
#else
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
{
#endif
var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters);
requestInfo.Configure(requestConfiguration);
requestInfo.Headers.TryAdd("Accept", "text/plain;q=0.9");
return requestInfo;
}
/// <summary>
/// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored.
/// </summary>
/// <returns>A <see cref="global::Marechai.App.Computers.MaximumYear.MaximumYearRequestBuilder"/></returns>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
public global::Marechai.App.Computers.MaximumYear.MaximumYearRequestBuilder WithUrl(string rawUrl)
{
return new global::Marechai.App.Computers.MaximumYear.MaximumYearRequestBuilder(rawUrl, RequestAdapter);
}
/// <summary>
/// Configuration for the request such as headers, query parameters, and middleware options.
/// </summary>
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class MaximumYearRequestBuilderGetRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
{
}
}
}
#pragma warning restore CS0618

View File

@@ -0,0 +1,91 @@
// <auto-generated/>
#pragma warning disable CS0618
using Marechai.App.Models;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Threading;
using System;
namespace Marechai.App.Computers.MinimumYear
{
/// <summary>
/// Builds and executes requests for operations under \computers\minimum-year
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class MinimumYearRequestBuilder : BaseRequestBuilder
{
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Computers.MinimumYear.MinimumYearRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="pathParameters">Path parameters for the request</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public MinimumYearRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/computers/minimum-year", pathParameters)
{
}
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Computers.MinimumYear.MinimumYearRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public MinimumYearRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/computers/minimum-year", rawUrl)
{
}
/// <returns>A <see cref="UntypedNode"/></returns>
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 400 status code</exception>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public async Task<UntypedNode?> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
{
#nullable restore
#else
public async Task<UntypedNode> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
{
#endif
var requestInfo = ToGetRequestInformation(requestConfiguration);
var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
{
{ "400", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
};
return await RequestAdapter.SendAsync<UntypedNode>(requestInfo, UntypedNode.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false);
}
/// <returns>A <see cref="RequestInformation"/></returns>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
{
#nullable restore
#else
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
{
#endif
var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters);
requestInfo.Configure(requestConfiguration);
requestInfo.Headers.TryAdd("Accept", "text/plain;q=0.9");
return requestInfo;
}
/// <summary>
/// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored.
/// </summary>
/// <returns>A <see cref="global::Marechai.App.Computers.MinimumYear.MinimumYearRequestBuilder"/></returns>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
public global::Marechai.App.Computers.MinimumYear.MinimumYearRequestBuilder WithUrl(string rawUrl)
{
return new global::Marechai.App.Computers.MinimumYear.MinimumYearRequestBuilder(rawUrl, RequestAdapter);
}
/// <summary>
/// Configuration for the request such as headers, query parameters, and middleware options.
/// </summary>
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class MinimumYearRequestBuilderGetRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
{
}
}
}
#pragma warning restore CS0618

View File

@@ -0,0 +1,48 @@
// <auto-generated/>
#pragma warning disable CS0618
using Marechai.App.Consoles.ByLetter.Item;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System;
namespace Marechai.App.Consoles.ByLetter
{
/// <summary>
/// Builds and executes requests for operations under \consoles\by-letter
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class ByLetterRequestBuilder : BaseRequestBuilder
{
/// <summary>Gets an item from the Marechai.App.consoles.byLetter.item collection</summary>
/// <param name="position">Unique identifier of the item</param>
/// <returns>A <see cref="global::Marechai.App.Consoles.ByLetter.Item.WithCItemRequestBuilder"/></returns>
public global::Marechai.App.Consoles.ByLetter.Item.WithCItemRequestBuilder this[string position]
{
get
{
var urlTplParams = new Dictionary<string, object>(PathParameters);
urlTplParams.Add("c", position);
return new global::Marechai.App.Consoles.ByLetter.Item.WithCItemRequestBuilder(urlTplParams, RequestAdapter);
}
}
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Consoles.ByLetter.ByLetterRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="pathParameters">Path parameters for the request</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public ByLetterRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/consoles/by-letter", pathParameters)
{
}
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Consoles.ByLetter.ByLetterRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public ByLetterRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/consoles/by-letter", rawUrl)
{
}
}
}
#pragma warning restore CS0618

View File

@@ -0,0 +1,92 @@
// <auto-generated/>
#pragma warning disable CS0618
using Marechai.App.Models;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Threading;
using System;
namespace Marechai.App.Consoles.ByLetter.Item
{
/// <summary>
/// Builds and executes requests for operations under \consoles\by-letter\{c}
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class WithCItemRequestBuilder : BaseRequestBuilder
{
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Consoles.ByLetter.Item.WithCItemRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="pathParameters">Path parameters for the request</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public WithCItemRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/consoles/by-letter/{c}", pathParameters)
{
}
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Consoles.ByLetter.Item.WithCItemRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public WithCItemRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/consoles/by-letter/{c}", rawUrl)
{
}
/// <returns>A List&lt;global::Marechai.App.Models.MachineDto&gt;</returns>
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 400 status code</exception>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public async Task<List<global::Marechai.App.Models.MachineDto>?> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
{
#nullable restore
#else
public async Task<List<global::Marechai.App.Models.MachineDto>> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
{
#endif
var requestInfo = ToGetRequestInformation(requestConfiguration);
var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
{
{ "400", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
};
var collectionResult = await RequestAdapter.SendCollectionAsync<global::Marechai.App.Models.MachineDto>(requestInfo, global::Marechai.App.Models.MachineDto.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false);
return collectionResult?.AsList();
}
/// <returns>A <see cref="RequestInformation"/></returns>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
{
#nullable restore
#else
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
{
#endif
var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters);
requestInfo.Configure(requestConfiguration);
requestInfo.Headers.TryAdd("Accept", "text/plain;q=0.9");
return requestInfo;
}
/// <summary>
/// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored.
/// </summary>
/// <returns>A <see cref="global::Marechai.App.Consoles.ByLetter.Item.WithCItemRequestBuilder"/></returns>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
public global::Marechai.App.Consoles.ByLetter.Item.WithCItemRequestBuilder WithUrl(string rawUrl)
{
return new global::Marechai.App.Consoles.ByLetter.Item.WithCItemRequestBuilder(rawUrl, RequestAdapter);
}
/// <summary>
/// Configuration for the request such as headers, query parameters, and middleware options.
/// </summary>
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class WithCItemRequestBuilderGetRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
{
}
}
}
#pragma warning restore CS0618

View File

@@ -0,0 +1,61 @@
// <auto-generated/>
#pragma warning disable CS0618
using Marechai.App.Consoles.ByYear.Item;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System;
namespace Marechai.App.Consoles.ByYear
{
/// <summary>
/// Builds and executes requests for operations under \consoles\by-year
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class ByYearRequestBuilder : BaseRequestBuilder
{
/// <summary>Gets an item from the Marechai.App.consoles.byYear.item collection</summary>
/// <param name="position">Unique identifier of the item</param>
/// <returns>A <see cref="global::Marechai.App.Consoles.ByYear.Item.WithYearItemRequestBuilder"/></returns>
public global::Marechai.App.Consoles.ByYear.Item.WithYearItemRequestBuilder this[int position]
{
get
{
var urlTplParams = new Dictionary<string, object>(PathParameters);
urlTplParams.Add("year", position);
return new global::Marechai.App.Consoles.ByYear.Item.WithYearItemRequestBuilder(urlTplParams, RequestAdapter);
}
}
/// <summary>Gets an item from the Marechai.App.consoles.byYear.item collection</summary>
/// <param name="position">Unique identifier of the item</param>
/// <returns>A <see cref="global::Marechai.App.Consoles.ByYear.Item.WithYearItemRequestBuilder"/></returns>
[Obsolete("This indexer is deprecated and will be removed in the next major version. Use the one with the typed parameter instead.")]
public global::Marechai.App.Consoles.ByYear.Item.WithYearItemRequestBuilder this[string position]
{
get
{
var urlTplParams = new Dictionary<string, object>(PathParameters);
if (!string.IsNullOrWhiteSpace(position)) urlTplParams.Add("year", position);
return new global::Marechai.App.Consoles.ByYear.Item.WithYearItemRequestBuilder(urlTplParams, RequestAdapter);
}
}
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Consoles.ByYear.ByYearRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="pathParameters">Path parameters for the request</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public ByYearRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/consoles/by-year", pathParameters)
{
}
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Consoles.ByYear.ByYearRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public ByYearRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/consoles/by-year", rawUrl)
{
}
}
}
#pragma warning restore CS0618

View File

@@ -0,0 +1,92 @@
// <auto-generated/>
#pragma warning disable CS0618
using Marechai.App.Models;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Threading;
using System;
namespace Marechai.App.Consoles.ByYear.Item
{
/// <summary>
/// Builds and executes requests for operations under \consoles\by-year\{year}
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class WithYearItemRequestBuilder : BaseRequestBuilder
{
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Consoles.ByYear.Item.WithYearItemRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="pathParameters">Path parameters for the request</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public WithYearItemRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/consoles/by-year/{year}", pathParameters)
{
}
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Consoles.ByYear.Item.WithYearItemRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public WithYearItemRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/consoles/by-year/{year}", rawUrl)
{
}
/// <returns>A List&lt;global::Marechai.App.Models.MachineDto&gt;</returns>
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 400 status code</exception>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public async Task<List<global::Marechai.App.Models.MachineDto>?> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
{
#nullable restore
#else
public async Task<List<global::Marechai.App.Models.MachineDto>> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
{
#endif
var requestInfo = ToGetRequestInformation(requestConfiguration);
var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
{
{ "400", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
};
var collectionResult = await RequestAdapter.SendCollectionAsync<global::Marechai.App.Models.MachineDto>(requestInfo, global::Marechai.App.Models.MachineDto.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false);
return collectionResult?.AsList();
}
/// <returns>A <see cref="RequestInformation"/></returns>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
{
#nullable restore
#else
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
{
#endif
var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters);
requestInfo.Configure(requestConfiguration);
requestInfo.Headers.TryAdd("Accept", "text/plain;q=0.9");
return requestInfo;
}
/// <summary>
/// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored.
/// </summary>
/// <returns>A <see cref="global::Marechai.App.Consoles.ByYear.Item.WithYearItemRequestBuilder"/></returns>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
public global::Marechai.App.Consoles.ByYear.Item.WithYearItemRequestBuilder WithUrl(string rawUrl)
{
return new global::Marechai.App.Consoles.ByYear.Item.WithYearItemRequestBuilder(rawUrl, RequestAdapter);
}
/// <summary>
/// Configuration for the request such as headers, query parameters, and middleware options.
/// </summary>
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class WithYearItemRequestBuilderGetRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
{
}
}
}
#pragma warning restore CS0618

View File

@@ -1,5 +1,10 @@
// <auto-generated/>
#pragma warning disable CS0618
using Marechai.App.Consoles.ByLetter;
using Marechai.App.Consoles.ByYear;
using Marechai.App.Consoles.Count;
using Marechai.App.Consoles.MaximumYear;
using Marechai.App.Consoles.MinimumYear;
using Marechai.App.Models;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
@@ -17,6 +22,31 @@ namespace Marechai.App.Consoles
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class ConsolesRequestBuilder : BaseRequestBuilder
{
/// <summary>The byLetter property</summary>
public global::Marechai.App.Consoles.ByLetter.ByLetterRequestBuilder ByLetter
{
get => new global::Marechai.App.Consoles.ByLetter.ByLetterRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>The byYear property</summary>
public global::Marechai.App.Consoles.ByYear.ByYearRequestBuilder ByYear
{
get => new global::Marechai.App.Consoles.ByYear.ByYearRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>The count property</summary>
public global::Marechai.App.Consoles.Count.CountRequestBuilder Count
{
get => new global::Marechai.App.Consoles.Count.CountRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>The maximumYear property</summary>
public global::Marechai.App.Consoles.MaximumYear.MaximumYearRequestBuilder MaximumYear
{
get => new global::Marechai.App.Consoles.MaximumYear.MaximumYearRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>The minimumYear property</summary>
public global::Marechai.App.Consoles.MinimumYear.MinimumYearRequestBuilder MinimumYear
{
get => new global::Marechai.App.Consoles.MinimumYear.MinimumYearRequestBuilder(PathParameters, RequestAdapter);
}
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Consoles.ConsolesRequestBuilder"/> and sets the default values.
/// </summary>

View File

@@ -0,0 +1,91 @@
// <auto-generated/>
#pragma warning disable CS0618
using Marechai.App.Models;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Threading;
using System;
namespace Marechai.App.Consoles.Count
{
/// <summary>
/// Builds and executes requests for operations under \consoles\count
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class CountRequestBuilder : BaseRequestBuilder
{
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Consoles.Count.CountRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="pathParameters">Path parameters for the request</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public CountRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/consoles/count", pathParameters)
{
}
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Consoles.Count.CountRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public CountRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/consoles/count", rawUrl)
{
}
/// <returns>A <see cref="UntypedNode"/></returns>
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 400 status code</exception>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public async Task<UntypedNode?> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
{
#nullable restore
#else
public async Task<UntypedNode> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
{
#endif
var requestInfo = ToGetRequestInformation(requestConfiguration);
var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
{
{ "400", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
};
return await RequestAdapter.SendAsync<UntypedNode>(requestInfo, UntypedNode.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false);
}
/// <returns>A <see cref="RequestInformation"/></returns>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
{
#nullable restore
#else
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
{
#endif
var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters);
requestInfo.Configure(requestConfiguration);
requestInfo.Headers.TryAdd("Accept", "text/plain;q=0.9");
return requestInfo;
}
/// <summary>
/// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored.
/// </summary>
/// <returns>A <see cref="global::Marechai.App.Consoles.Count.CountRequestBuilder"/></returns>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
public global::Marechai.App.Consoles.Count.CountRequestBuilder WithUrl(string rawUrl)
{
return new global::Marechai.App.Consoles.Count.CountRequestBuilder(rawUrl, RequestAdapter);
}
/// <summary>
/// Configuration for the request such as headers, query parameters, and middleware options.
/// </summary>
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class CountRequestBuilderGetRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
{
}
}
}
#pragma warning restore CS0618

View File

@@ -0,0 +1,91 @@
// <auto-generated/>
#pragma warning disable CS0618
using Marechai.App.Models;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Threading;
using System;
namespace Marechai.App.Consoles.MaximumYear
{
/// <summary>
/// Builds and executes requests for operations under \consoles\maximum-year
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class MaximumYearRequestBuilder : BaseRequestBuilder
{
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Consoles.MaximumYear.MaximumYearRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="pathParameters">Path parameters for the request</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public MaximumYearRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/consoles/maximum-year", pathParameters)
{
}
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Consoles.MaximumYear.MaximumYearRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public MaximumYearRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/consoles/maximum-year", rawUrl)
{
}
/// <returns>A <see cref="UntypedNode"/></returns>
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 400 status code</exception>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public async Task<UntypedNode?> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
{
#nullable restore
#else
public async Task<UntypedNode> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
{
#endif
var requestInfo = ToGetRequestInformation(requestConfiguration);
var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
{
{ "400", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
};
return await RequestAdapter.SendAsync<UntypedNode>(requestInfo, UntypedNode.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false);
}
/// <returns>A <see cref="RequestInformation"/></returns>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
{
#nullable restore
#else
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
{
#endif
var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters);
requestInfo.Configure(requestConfiguration);
requestInfo.Headers.TryAdd("Accept", "text/plain;q=0.9");
return requestInfo;
}
/// <summary>
/// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored.
/// </summary>
/// <returns>A <see cref="global::Marechai.App.Consoles.MaximumYear.MaximumYearRequestBuilder"/></returns>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
public global::Marechai.App.Consoles.MaximumYear.MaximumYearRequestBuilder WithUrl(string rawUrl)
{
return new global::Marechai.App.Consoles.MaximumYear.MaximumYearRequestBuilder(rawUrl, RequestAdapter);
}
/// <summary>
/// Configuration for the request such as headers, query parameters, and middleware options.
/// </summary>
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class MaximumYearRequestBuilderGetRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
{
}
}
}
#pragma warning restore CS0618

View File

@@ -0,0 +1,91 @@
// <auto-generated/>
#pragma warning disable CS0618
using Marechai.App.Models;
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using Microsoft.Kiota.Abstractions;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Threading;
using System;
namespace Marechai.App.Consoles.MinimumYear
{
/// <summary>
/// Builds and executes requests for operations under \consoles\minimum-year
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class MinimumYearRequestBuilder : BaseRequestBuilder
{
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Consoles.MinimumYear.MinimumYearRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="pathParameters">Path parameters for the request</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public MinimumYearRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/consoles/minimum-year", pathParameters)
{
}
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Consoles.MinimumYear.MinimumYearRequestBuilder"/> and sets the default values.
/// </summary>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
public MinimumYearRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/consoles/minimum-year", rawUrl)
{
}
/// <returns>A <see cref="UntypedNode"/></returns>
/// <param name="cancellationToken">Cancellation token to use when cancelling requests</param>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
/// <exception cref="global::Marechai.App.Models.ProblemDetails">When receiving a 400 status code</exception>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public async Task<UntypedNode?> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default, CancellationToken cancellationToken = default)
{
#nullable restore
#else
public async Task<UntypedNode> GetAsync(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default, CancellationToken cancellationToken = default)
{
#endif
var requestInfo = ToGetRequestInformation(requestConfiguration);
var errorMapping = new Dictionary<string, ParsableFactory<IParsable>>
{
{ "400", global::Marechai.App.Models.ProblemDetails.CreateFromDiscriminatorValue },
};
return await RequestAdapter.SendAsync<UntypedNode>(requestInfo, UntypedNode.CreateFromDiscriminatorValue, errorMapping, cancellationToken).ConfigureAwait(false);
}
/// <returns>A <see cref="RequestInformation"/></returns>
/// <param name="requestConfiguration">Configuration for the request such as headers, query parameters, and middleware options.</param>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>>? requestConfiguration = default)
{
#nullable restore
#else
public RequestInformation ToGetRequestInformation(Action<RequestConfiguration<DefaultQueryParameters>> requestConfiguration = default)
{
#endif
var requestInfo = new RequestInformation(Method.GET, UrlTemplate, PathParameters);
requestInfo.Configure(requestConfiguration);
requestInfo.Headers.TryAdd("Accept", "text/plain;q=0.9");
return requestInfo;
}
/// <summary>
/// Returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored.
/// </summary>
/// <returns>A <see cref="global::Marechai.App.Consoles.MinimumYear.MinimumYearRequestBuilder"/></returns>
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
public global::Marechai.App.Consoles.MinimumYear.MinimumYearRequestBuilder WithUrl(string rawUrl)
{
return new global::Marechai.App.Consoles.MinimumYear.MinimumYearRequestBuilder(rawUrl, RequestAdapter);
}
/// <summary>
/// Configuration for the request such as headers, query parameters, and middleware options.
/// </summary>
[Obsolete("This class is deprecated. Please use the generic RequestConfiguration class generated by the generator.")]
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
public partial class MinimumYearRequestBuilderGetRequestConfiguration : RequestConfiguration<DefaultQueryParameters>
{
}
}
}
#pragma warning restore CS0618

View File

@@ -55,6 +55,14 @@ namespace Marechai.App.Models
#nullable restore
#else
public string FamilyName { get; set; }
#endif
/// <summary>The gpus property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public List<global::Marechai.App.Models.GpuDto>? Gpus { get; set; }
#nullable restore
#else
public List<global::Marechai.App.Models.GpuDto> Gpus { get; set; }
#endif
/// <summary>The id property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
@@ -66,6 +74,14 @@ namespace Marechai.App.Models
#endif
/// <summary>The introduced property</summary>
public DateTimeOffset? Introduced { get; set; }
/// <summary>The memory property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public List<global::Marechai.App.Models.MemoryDto>? Memory { get; set; }
#nullable restore
#else
public List<global::Marechai.App.Models.MemoryDto> Memory { get; set; }
#endif
/// <summary>The model property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
@@ -81,6 +97,30 @@ namespace Marechai.App.Models
#nullable restore
#else
public string Name { get; set; }
#endif
/// <summary>The processors property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public List<global::Marechai.App.Models.ProcessorDto>? Processors { get; set; }
#nullable restore
#else
public List<global::Marechai.App.Models.ProcessorDto> Processors { get; set; }
#endif
/// <summary>The soundSynthesizers property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public List<global::Marechai.App.Models.SoundSynthDto>? SoundSynthesizers { get; set; }
#nullable restore
#else
public List<global::Marechai.App.Models.SoundSynthDto> SoundSynthesizers { get; set; }
#endif
/// <summary>The storage property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public List<global::Marechai.App.Models.StorageDto>? Storage { get; set; }
#nullable restore
#else
public List<global::Marechai.App.Models.StorageDto> Storage { get; set; }
#endif
/// <summary>The type property</summary>
public int? Type { get; set; }
@@ -115,10 +155,15 @@ namespace Marechai.App.Models
{ "family", n => { Family = n.GetStringValue(); } },
{ "family_id", n => { FamilyId = n.GetObjectValue<UntypedNode>(UntypedNode.CreateFromDiscriminatorValue); } },
{ "family_name", n => { FamilyName = n.GetStringValue(); } },
{ "gpus", n => { Gpus = n.GetCollectionOfObjectValues<global::Marechai.App.Models.GpuDto>(global::Marechai.App.Models.GpuDto.CreateFromDiscriminatorValue)?.AsList(); } },
{ "id", n => { Id = n.GetObjectValue<UntypedNode>(UntypedNode.CreateFromDiscriminatorValue); } },
{ "introduced", n => { Introduced = n.GetDateTimeOffsetValue(); } },
{ "memory", n => { Memory = n.GetCollectionOfObjectValues<global::Marechai.App.Models.MemoryDto>(global::Marechai.App.Models.MemoryDto.CreateFromDiscriminatorValue)?.AsList(); } },
{ "model", n => { Model = n.GetStringValue(); } },
{ "name", n => { Name = n.GetStringValue(); } },
{ "processors", n => { Processors = n.GetCollectionOfObjectValues<global::Marechai.App.Models.ProcessorDto>(global::Marechai.App.Models.ProcessorDto.CreateFromDiscriminatorValue)?.AsList(); } },
{ "soundSynthesizers", n => { SoundSynthesizers = n.GetCollectionOfObjectValues<global::Marechai.App.Models.SoundSynthDto>(global::Marechai.App.Models.SoundSynthDto.CreateFromDiscriminatorValue)?.AsList(); } },
{ "storage", n => { Storage = n.GetCollectionOfObjectValues<global::Marechai.App.Models.StorageDto>(global::Marechai.App.Models.StorageDto.CreateFromDiscriminatorValue)?.AsList(); } },
{ "type", n => { Type = n.GetIntValue(); } },
};
}
@@ -135,10 +180,15 @@ namespace Marechai.App.Models
writer.WriteStringValue("family", Family);
writer.WriteObjectValue<UntypedNode>("family_id", FamilyId);
writer.WriteStringValue("family_name", FamilyName);
writer.WriteCollectionOfObjectValues<global::Marechai.App.Models.GpuDto>("gpus", Gpus);
writer.WriteObjectValue<UntypedNode>("id", Id);
writer.WriteDateTimeOffsetValue("introduced", Introduced);
writer.WriteCollectionOfObjectValues<global::Marechai.App.Models.MemoryDto>("memory", Memory);
writer.WriteStringValue("model", Model);
writer.WriteStringValue("name", Name);
writer.WriteCollectionOfObjectValues<global::Marechai.App.Models.ProcessorDto>("processors", Processors);
writer.WriteCollectionOfObjectValues<global::Marechai.App.Models.SoundSynthDto>("soundSynthesizers", SoundSynthesizers);
writer.WriteCollectionOfObjectValues<global::Marechai.App.Models.StorageDto>("storage", Storage);
writer.WriteIntValue("type", Type);
writer.WriteAdditionalData(AdditionalData);
}

View File

@@ -0,0 +1,83 @@
// <auto-generated/>
#pragma warning disable CS0618
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using System.Collections.Generic;
using System.IO;
using System;
namespace Marechai.App.Models
{
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
#pragma warning disable CS1591
public partial class MemoryDto : IAdditionalDataHolder, IParsable
#pragma warning restore CS1591
{
/// <summary>Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.</summary>
public IDictionary<string, object> AdditionalData { get; set; }
/// <summary>The size property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public UntypedNode? Size { get; set; }
#nullable restore
#else
public UntypedNode Size { get; set; }
#endif
/// <summary>The speed property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public UntypedNode? Speed { get; set; }
#nullable restore
#else
public UntypedNode Speed { get; set; }
#endif
/// <summary>The type property</summary>
public int? Type { get; set; }
/// <summary>The usage property</summary>
public int? Usage { get; set; }
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Models.MemoryDto"/> and sets the default values.
/// </summary>
public MemoryDto()
{
AdditionalData = new Dictionary<string, object>();
}
/// <summary>
/// Creates a new instance of the appropriate class based on discriminator value
/// </summary>
/// <returns>A <see cref="global::Marechai.App.Models.MemoryDto"/></returns>
/// <param name="parseNode">The parse node to use to read the discriminator value and create the object</param>
public static global::Marechai.App.Models.MemoryDto CreateFromDiscriminatorValue(IParseNode parseNode)
{
if(ReferenceEquals(parseNode, null)) throw new ArgumentNullException(nameof(parseNode));
return new global::Marechai.App.Models.MemoryDto();
}
/// <summary>
/// The deserialization information for the current model
/// </summary>
/// <returns>A IDictionary&lt;string, Action&lt;IParseNode&gt;&gt;</returns>
public virtual IDictionary<string, Action<IParseNode>> GetFieldDeserializers()
{
return new Dictionary<string, Action<IParseNode>>
{
{ "size", n => { Size = n.GetObjectValue<UntypedNode>(UntypedNode.CreateFromDiscriminatorValue); } },
{ "speed", n => { Speed = n.GetObjectValue<UntypedNode>(UntypedNode.CreateFromDiscriminatorValue); } },
{ "type", n => { Type = n.GetIntValue(); } },
{ "usage", n => { Usage = n.GetIntValue(); } },
};
}
/// <summary>
/// Serializes information the current object
/// </summary>
/// <param name="writer">Serialization writer to use to serialize this model</param>
public virtual void Serialize(ISerializationWriter writer)
{
if(ReferenceEquals(writer, null)) throw new ArgumentNullException(nameof(writer));
writer.WriteObjectValue<UntypedNode>("size", Size);
writer.WriteObjectValue<UntypedNode>("speed", Speed);
writer.WriteIntValue("type", Type);
writer.WriteIntValue("usage", Usage);
writer.WriteAdditionalData(AdditionalData);
}
}
}
#pragma warning restore CS0618

View File

@@ -0,0 +1,73 @@
// <auto-generated/>
#pragma warning disable CS0618
using Microsoft.Kiota.Abstractions.Extensions;
using Microsoft.Kiota.Abstractions.Serialization;
using System.Collections.Generic;
using System.IO;
using System;
namespace Marechai.App.Models
{
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
#pragma warning disable CS1591
public partial class StorageDto : IAdditionalDataHolder, IParsable
#pragma warning restore CS1591
{
/// <summary>Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well.</summary>
public IDictionary<string, object> AdditionalData { get; set; }
/// <summary>The capacity property</summary>
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
#nullable enable
public UntypedNode? Capacity { get; set; }
#nullable restore
#else
public UntypedNode Capacity { get; set; }
#endif
/// <summary>The interface property</summary>
public int? Interface { get; set; }
/// <summary>The type property</summary>
public int? Type { get; set; }
/// <summary>
/// Instantiates a new <see cref="global::Marechai.App.Models.StorageDto"/> and sets the default values.
/// </summary>
public StorageDto()
{
AdditionalData = new Dictionary<string, object>();
}
/// <summary>
/// Creates a new instance of the appropriate class based on discriminator value
/// </summary>
/// <returns>A <see cref="global::Marechai.App.Models.StorageDto"/></returns>
/// <param name="parseNode">The parse node to use to read the discriminator value and create the object</param>
public static global::Marechai.App.Models.StorageDto CreateFromDiscriminatorValue(IParseNode parseNode)
{
if(ReferenceEquals(parseNode, null)) throw new ArgumentNullException(nameof(parseNode));
return new global::Marechai.App.Models.StorageDto();
}
/// <summary>
/// The deserialization information for the current model
/// </summary>
/// <returns>A IDictionary&lt;string, Action&lt;IParseNode&gt;&gt;</returns>
public virtual IDictionary<string, Action<IParseNode>> GetFieldDeserializers()
{
return new Dictionary<string, Action<IParseNode>>
{
{ "capacity", n => { Capacity = n.GetObjectValue<UntypedNode>(UntypedNode.CreateFromDiscriminatorValue); } },
{ "interface", n => { Interface = n.GetIntValue(); } },
{ "type", n => { Type = n.GetIntValue(); } },
};
}
/// <summary>
/// Serializes information the current object
/// </summary>
/// <param name="writer">Serialization writer to use to serialize this model</param>
public virtual void Serialize(ISerializationWriter writer)
{
if(ReferenceEquals(writer, null)) throw new ArgumentNullException(nameof(writer));
writer.WriteObjectValue<UntypedNode>("capacity", Capacity);
writer.WriteIntValue("interface", Interface);
writer.WriteIntValue("type", Type);
writer.WriteAdditionalData(AdditionalData);
}
}
}
#pragma warning restore CS0618

View File

@@ -1,5 +1,5 @@
{
"descriptionHash": "4C4317F1318B3D4CA2225D2F84196E63FE3991CAA0B4FAC3BDA2432C98D3457223C321CFD97E2BC392D20DCE9E265F204F4B0E90C7FE300BC876152A4FC07520",
"descriptionHash": "D113F1327CF7DE3F19E825040CA90DB79CEEC1636CED7E314A2D47CB82985CF9BC542185433D69366B0B36C3836966BDE3778B69AA15A0D11A063BE5571BCA5D",
"descriptionLocation": "http://localhost:5023/openapi/v1.json",
"lockFileVersion": "1.0.0",
"kiotaVersion": "1.29.0",

View File

@@ -0,0 +1,77 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Marechai.App.Services;
/// <summary>
/// Service for fetching companies data from the API
/// </summary>
public class CompaniesService
{
private readonly ApiClient _apiClient;
private readonly ILogger<CompaniesService> _logger;
public CompaniesService(ApiClient apiClient, ILogger<CompaniesService> logger)
{
_apiClient = apiClient;
_logger = logger;
}
/// <summary>
/// Gets all companies
/// </summary>
public async Task<List<CompanyDto>> GetAllCompaniesAsync()
{
try
{
_logger.LogInformation("Fetching all companies from API");
List<CompanyDto>? companies = await _apiClient.Companies.GetAsync();
if(companies == null) return [];
_logger.LogInformation("Successfully fetched {Count} total companies", companies.Count);
return companies;
}
catch(Exception ex)
{
_logger.LogError(ex, "Error fetching all companies from API");
return [];
}
}
/// <summary>
/// Gets a single company by ID
/// </summary>
public async Task<CompanyDto?> GetCompanyByIdAsync(int companyId)
{
try
{
_logger.LogInformation("Fetching company {CompanyId} from API", companyId);
CompanyDto? company = await _apiClient.Companies[companyId].GetAsync();
if(company == null)
{
_logger.LogWarning("Company {CompanyId} not found", companyId);
return null;
}
_logger.LogInformation("Successfully fetched company {CompanyId}: {CompanyName}", companyId, company.Name);
return company;
}
catch(Exception ex)
{
_logger.LogError(ex, "Error fetching company {CompanyId} from API", companyId);
return null;
}
}
}

View File

@@ -0,0 +1,101 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Marechai.App.Models;
namespace Marechai.App.Services;
/// <summary>
/// Service for fetching company details from the API
/// </summary>
public class CompanyDetailService
{
private readonly ApiClient _apiClient;
private readonly ILogger<CompanyDetailService> _logger;
public CompanyDetailService(ApiClient apiClient, ILogger<CompanyDetailService> logger)
{
_apiClient = apiClient;
_logger = logger;
}
/// <summary>
/// Gets a single company by ID with full details
/// </summary>
public async Task<CompanyDto?> GetCompanyByIdAsync(int companyId)
{
try
{
_logger.LogInformation("Fetching company {CompanyId} from API", companyId);
CompanyDto? company = await _apiClient.Companies[companyId].GetAsync();
if(company == null)
{
_logger.LogWarning("Company {CompanyId} not found", companyId);
return null;
}
_logger.LogInformation("Successfully fetched company {CompanyId}: {CompanyName}", companyId, company.Name);
return company;
}
catch(Exception ex)
{
_logger.LogError(ex, "Error fetching company {CompanyId} from API", companyId);
return null;
}
}
/// <summary>
/// Gets machines (computers) made by a company
/// </summary>
public async Task<List<MachineDto>> GetComputersByCompanyAsync(int companyId)
{
try
{
_logger.LogInformation("Fetching computers for company {CompanyId}", companyId);
List<MachineDto>? machines = await _apiClient.Companies[companyId].Machines.GetAsync();
if(machines == null) return [];
_logger.LogInformation("Successfully fetched {Count} computers for company {CompanyId}", machines.Count, companyId);
return machines;
}
catch(Exception ex)
{
_logger.LogError(ex, "Error fetching computers for company {CompanyId}", companyId);
return [];
}
}
/// <summary>
/// Gets the sold-to company (when company was sold, merged, or renamed)
/// </summary>
public async Task<CompanyDto?> GetSoldToCompanyAsync(int? companyId)
{
if(companyId is null or <= 0) return null;
try
{
_logger.LogInformation("Fetching sold-to company {CompanyId}", companyId);
CompanyDto? company = await _apiClient.Companies[companyId.Value].GetAsync();
return company;
}
catch(Exception ex)
{
_logger.LogError(ex, "Error fetching sold-to company {CompanyId}", companyId);
return null;
}
}
}

View File

@@ -8,7 +8,7 @@ namespace Marechai.App.Services;
public class CompositeSerializationWriterFactory : ISerializationWriterFactory
{
// Internal list of registered factories.
private readonly List<ISerializationWriterFactory> _factories = new();
private readonly List<ISerializationWriterFactory> _factories = [];
// This method loops through each registered factory and returns the first one that supports the content type.
public ISerializationWriter GetSerializationWriter(string contentType)

View File

@@ -0,0 +1,29 @@
namespace Marechai.App.Services;
/// <summary>
/// Service to hold the current filter context for the computers list view
/// </summary>
public interface IComputersListFilterContext
{
ComputerListFilterType FilterType { get; set; }
string FilterValue { get; set; }
}
/// <summary>
/// Implementation of the computers list filter context
/// </summary>
public class ComputersListFilterContext : IComputersListFilterContext
{
public ComputerListFilterType FilterType { get; set; } = ComputerListFilterType.All;
public string FilterValue { get; set; } = string.Empty;
}
/// <summary>
/// Enum for computer filter types
/// </summary>
public enum ComputerListFilterType
{
All,
Letter,
Year
}

View File

@@ -0,0 +1,208 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Marechai.App.Helpers;
using Microsoft.Kiota.Abstractions.Serialization;
namespace Marechai.App.Services;
/// <summary>
/// Service for fetching and managing computers from the Marechai API
/// </summary>
public class ComputersService
{
private readonly ApiClient _apiClient;
private readonly ILogger<ComputersService> _logger;
public ComputersService(ApiClient apiClient, ILogger<ComputersService> logger)
{
_apiClient = apiClient;
_logger = logger;
}
/// <summary>
/// Fetches the total count of computers from the API
/// </summary>
/// <returns>Total number of computers, or 0 if API call fails</returns>
public async Task<int> GetComputersCountAsync()
{
try
{
_logger.LogInformation("Fetching computers count from API");
UntypedNode result = await _apiClient.Computers.Count.GetAsync();
// Extract integer value from UntypedNode
// UntypedNode wraps a JsonElement, we need to parse it
int count = UntypedNodeExtractor.ExtractInt(result);
_logger.LogInformation("Successfully fetched computers count: {Count}", count);
return count;
}
catch(Exception ex)
{
_logger.LogError(ex, "Error fetching computers count from API");
return 0;
}
}
/// <summary>
/// Fetches the minimum year of computers from the API
/// </summary>
/// <returns>Minimum year, or 0 if API call fails</returns>
public async Task<int> GetMinimumYearAsync()
{
try
{
_logger.LogInformation("Fetching minimum year from API");
UntypedNode result = await _apiClient.Computers.MinimumYear.GetAsync();
// Extract integer value from UntypedNode
int year = UntypedNodeExtractor.ExtractInt(result);
_logger.LogInformation("Successfully fetched minimum year: {Year}", year);
return year;
}
catch(Exception ex)
{
_logger.LogError(ex, "Error fetching minimum year from API");
return 0;
}
}
/// <summary>
/// Fetches the maximum year of computers from the API
/// </summary>
/// <returns>Maximum year, or 0 if API call fails</returns>
public async Task<int> GetMaximumYearAsync()
{
try
{
_logger.LogInformation("Fetching maximum year from API");
UntypedNode result = await _apiClient.Computers.MaximumYear.GetAsync();
// Extract integer value from UntypedNode
int year = UntypedNodeExtractor.ExtractInt(result);
_logger.LogInformation("Successfully fetched maximum year: {Year}", year);
return year;
}
catch(Exception ex)
{
_logger.LogError(ex, "Error fetching maximum year from API");
return 0;
}
}
/// <summary>
/// Helper method to extract an integer from an UntypedNode
/// </summary>
/// <summary>
/// Fetches computers filtered by starting letter from the API
/// </summary>
public async Task<List<MachineDto>> GetComputersByLetterAsync(char letter)
{
try
{
_logger.LogInformation("Fetching computers starting with '{Letter}' from API", letter);
List<MachineDto> computers = await _apiClient.Computers.ByLetter[letter.ToString()].GetAsync();
if(computers == null) return [];
_logger.LogInformation("Successfully fetched {Count} computers starting with '{Letter}'",
computers.Count,
letter);
return computers;
}
catch(Exception ex)
{
_logger.LogError(ex, "Error fetching computers by letter '{Letter}' from API", letter);
return [];
}
}
/// <summary>
/// Fetches computers filtered by year from the API
/// </summary>
public async Task<List<MachineDto>> GetComputersByYearAsync(int year)
{
try
{
_logger.LogInformation("Fetching computers from year {Year} from API", year);
List<MachineDto> computers = await _apiClient.Computers.ByYear[year].GetAsync();
if(computers == null) return [];
_logger.LogInformation("Successfully fetched {Count} computers from year {Year}", computers.Count, year);
return computers;
}
catch(Exception ex)
{
_logger.LogError(ex, "Error fetching computers by year {Year} from API", year);
return [];
}
}
/// <summary>
/// Fetches all computers from the API
/// </summary>
public async Task<List<MachineDto>> GetAllComputersAsync()
{
try
{
_logger.LogInformation("Fetching all computers from API");
List<MachineDto> computers = await _apiClient.Computers.GetAsync();
if(computers == null) return [];
_logger.LogInformation("Successfully fetched {Count} total computers", computers.Count);
return computers;
}
catch(Exception ex)
{
_logger.LogError(ex, "Error fetching all computers from API");
return [];
}
}
/// <summary>
/// Fetches a single machine with full details by ID from the API
/// </summary>
public async Task<MachineDto?> GetMachineByIdAsync(int machineId)
{
try
{
_logger.LogInformation("Fetching machine {MachineId} from API", machineId);
MachineDto? machine = await _apiClient.Machines[machineId].Full.GetAsync();
if(machine == null)
{
_logger.LogWarning("Machine {MachineId} not found", machineId);
return null;
}
_logger.LogInformation("Successfully fetched machine {MachineId}: {MachineName}", machineId, machine.Name);
return machine;
}
catch(Exception ex)
{
_logger.LogError(ex, "Error fetching machine {MachineId} from API", machineId);
return null;
}
}
}

View File

@@ -0,0 +1,29 @@
namespace Marechai.App.Services;
/// <summary>
/// Context for filtering consoles by various criteria
/// </summary>
public interface IConsolesListFilterContext
{
ConsoleListFilterType FilterType { get; set; }
string FilterValue { get; set; }
}
/// <summary>
/// Implementation of the consoles list filter context
/// </summary>
public class ConsolesListFilterContext : IConsolesListFilterContext
{
public ConsoleListFilterType FilterType { get; set; } = ConsoleListFilterType.All;
public string FilterValue { get; set; } = string.Empty;
}
/// <summary>
/// Enumeration for console list filter types
/// </summary>
public enum ConsoleListFilterType
{
All,
Letter,
Year
}

View File

@@ -0,0 +1,205 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Marechai.App.Helpers;
using Microsoft.Kiota.Abstractions.Serialization;
namespace Marechai.App.Services;
/// <summary>
/// Service for fetching and managing consoles from the Marechai API
/// </summary>
public class ConsolesService
{
private readonly ApiClient _apiClient;
private readonly ILogger<ConsolesService> _logger;
public ConsolesService(ApiClient apiClient, ILogger<ConsolesService> logger)
{
_apiClient = apiClient;
_logger = logger;
}
/// <summary>
/// Fetches the total count of consoles from the API
/// </summary>
/// <returns>Total number of consoles, or 0 if API call fails</returns>
public async Task<int> GetConsolesCountAsync()
{
try
{
_logger.LogInformation("Fetching consoles count from API");
UntypedNode result = await _apiClient.Consoles.Count.GetAsync();
// Extract integer value from UntypedNode
// UntypedNode wraps a JsonElement, we need to parse it
int count = UntypedNodeExtractor.ExtractInt(result);
_logger.LogInformation("Successfully fetched consoles count: {Count}", count);
return count;
}
catch(Exception ex)
{
_logger.LogError(ex, "Error fetching consoles count from API");
return 0;
}
}
/// <summary>
/// Fetches the minimum year of consoles from the API
/// </summary>
/// <returns>Minimum year, or 0 if API call fails</returns>
public async Task<int> GetMinimumYearAsync()
{
try
{
_logger.LogInformation("Fetching minimum year from API");
UntypedNode result = await _apiClient.Consoles.MinimumYear.GetAsync();
// Extract integer value from UntypedNode
int year = UntypedNodeExtractor.ExtractInt(result);
_logger.LogInformation("Successfully fetched minimum year: {Year}", year);
return year;
}
catch(Exception ex)
{
_logger.LogError(ex, "Error fetching minimum year from API");
return 0;
}
}
/// <summary>
/// Fetches the maximum year of consoles from the API
/// </summary>
/// <returns>Maximum year, or 0 if API call fails</returns>
public async Task<int> GetMaximumYearAsync()
{
try
{
_logger.LogInformation("Fetching maximum year from API");
UntypedNode result = await _apiClient.Consoles.MaximumYear.GetAsync();
// Extract integer value from UntypedNode
int year = UntypedNodeExtractor.ExtractInt(result);
_logger.LogInformation("Successfully fetched maximum year: {Year}", year);
return year;
}
catch(Exception ex)
{
_logger.LogError(ex, "Error fetching maximum year from API");
return 0;
}
}
/// <summary>
/// Fetches consoles filtered by starting letter from the API
/// </summary>
public async Task<List<MachineDto>> GetConsolesByLetterAsync(char letter)
{
try
{
_logger.LogInformation("Fetching consoles starting with '{Letter}' from API", letter);
List<MachineDto> consoles = await _apiClient.Consoles.ByLetter[letter.ToString()].GetAsync();
if(consoles == null) return [];
_logger.LogInformation("Successfully fetched {Count} consoles starting with '{Letter}'",
consoles.Count,
letter);
return consoles;
}
catch(Exception ex)
{
_logger.LogError(ex, "Error fetching consoles by letter '{Letter}' from API", letter);
return [];
}
}
/// <summary>
/// Fetches consoles filtered by year from the API
/// </summary>
public async Task<List<MachineDto>> GetConsolesByYearAsync(int year)
{
try
{
_logger.LogInformation("Fetching consoles from year {Year} from API", year);
List<MachineDto> consoles = await _apiClient.Consoles.ByYear[year].GetAsync();
if(consoles == null) return [];
_logger.LogInformation("Successfully fetched {Count} consoles from year {Year}", consoles.Count, year);
return consoles;
}
catch(Exception ex)
{
_logger.LogError(ex, "Error fetching consoles by year {Year} from API", year);
return [];
}
}
/// <summary>
/// Fetches all consoles from the API
/// </summary>
public async Task<List<MachineDto>> GetAllConsolesAsync()
{
try
{
_logger.LogInformation("Fetching all consoles from API");
List<MachineDto> consoles = await _apiClient.Consoles.GetAsync();
if(consoles == null) return [];
_logger.LogInformation("Successfully fetched {Count} total consoles", consoles.Count);
return consoles;
}
catch(Exception ex)
{
_logger.LogError(ex, "Error fetching all consoles from API");
return [];
}
}
/// <summary>
/// Fetches a single machine with full details by ID from the API
/// </summary>
public async Task<MachineDto?> GetMachineByIdAsync(int machineId)
{
try
{
_logger.LogInformation("Fetching machine {MachineId} from API", machineId);
MachineDto? machine = await _apiClient.Machines[machineId].Full.GetAsync();
if(machine == null)
{
_logger.LogWarning("Machine {MachineId} not found", machineId);
return null;
}
_logger.LogInformation("Successfully fetched machine {MachineId}: {MachineName}", machineId, machine.Name);
return machine;
}
catch(Exception ex)
{
_logger.LogError(ex, "Error fetching machine {MachineId} from API", machineId);
return null;
}
}
}

View File

@@ -30,13 +30,13 @@ public class NewsService
List<NewsDto> news = await _apiClient.News.Latest.GetAsync();
_logger.LogInformation("Successfully fetched {Count} news items", news?.Count ?? 0);
return news ?? new List<NewsDto>();
return news ?? [];
}
catch(Exception ex)
{
_logger.LogError(ex, "Error fetching latest news from API");
return new List<NewsDto>();
return [];
}
}
}

View File

@@ -1,64 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
@@ -120,4 +61,109 @@
<data name="ApplicationName" xml:space="preserve">
<value>Marechai.App-en</value>
</data>
<data name="News" xml:space="preserve">
<value>News</value>
</data>
<data name="Books" xml:space="preserve">
<value>Books</value>
</data>
<data name="Companies" xml:space="preserve">
<value>Companies</value>
</data>
<data name="Computers" xml:space="preserve">
<value>Computers</value>
</data>
<data name="Consoles" xml:space="preserve">
<value>Consoles</value>
</data>
<data name="Documents" xml:space="preserve">
<value>Documents</value>
</data>
<data name="Dumps" xml:space="preserve">
<value>Dumps</value>
</data>
<data name="GraphicalProcessingUnits" xml:space="preserve">
<value>Graphical Processing Units</value>
</data>
<data name="Magazines" xml:space="preserve">
<value>Magazines</value>
</data>
<data name="People" xml:space="preserve">
<value>People</value>
</data>
<data name="Processors" xml:space="preserve">
<value>Processors</value>
</data>
<data name="Software" xml:space="preserve">
<value>Software</value>
</data>
<data name="SoundSynthesizers" xml:space="preserve">
<value>Sound Synthesizers</value>
</data>
<data name="Settings" xml:space="preserve">
<value>Settings</value>
</data>
<data name="Login" xml:space="preserve">
<value>Login</value>
</data>
<data name="Logout" xml:space="preserve">
<value>Logout</value>
</data>
<data name="Company is active." xml:space="preserve">
<value>Company is active.</value>
</data>
<data name="Company sold to {0} on {1}." xml:space="preserve">
<value>Company sold to {0} on {1}.</value>
</data>
<data name="Company sold on {0} to an unknown company." xml:space="preserve">
<value>Company sold on {0} to an unknown company.</value>
</data>
<data name="Company sold to {0} on an unknown date." xml:space="preserve">
<value>Company sold to {0} on an unknown date.</value>
</data>
<data name="Company was sold to an unknown company on an unknown date." xml:space="preserve">
<value>Company was sold to an unknown company on an unknown date.</value>
</data>
<data name="Company merged on {0} to form {1}." xml:space="preserve">
<value>Company merged on {0} to form {1}.</value>
</data>
<data name="Company merged on {0} to form an unknown company." xml:space="preserve">
<value>Company merged on {0} to form an unknown company.</value>
</data>
<data name="Company merged on an unknown date to form {0}." xml:space="preserve">
<value>Company merged on an unknown date to form {0}.</value>
</data>
<data name="Company merged to form an unknown company on an unknown date." xml:space="preserve">
<value>Company merged to form an unknown company on an unknown date.</value>
</data>
<data name="Current company status is unknown." xml:space="preserve">
<value>Current company status is unknown.</value>
</data>
<data name="unknown date" xml:space="preserve">
<value>unknown date</value>
</data>
<data name="Company declared bankruptcy on {0}." xml:space="preserve">
<value>Company declared bankruptcy on {0}.</value>
</data>
<data name="Company declared bankruptcy on an unknown date." xml:space="preserve">
<value>Company declared bankruptcy on an unknown date.</value>
</data>
<data name="Company ceased operations on {0}." xml:space="preserve">
<value>Company ceased operations on {0}.</value>
</data>
<data name="Company ceased operations on an unknown date." xml:space="preserve">
<value>Company ceased operations on an unknown date.</value>
</data>
<data name="Company renamed to {0} on {1}." xml:space="preserve">
<value>Company renamed to {0} on {1}.</value>
</data>
<data name="Company was renamed on {0} to an unknown name." xml:space="preserve">
<value>Company was renamed on {0} to an unknown name.</value>
</data>
<data name="Company renamed to {0} on an unknown date." xml:space="preserve">
<value>Company renamed to {0} on an unknown date.</value>
</data>
<data name="Company was renamed on an unknown date to an unknown name." xml:space="preserve">
<value>Company was renamed on an unknown date to an unknown name.</value>
</data>
</root>

View File

@@ -1,64 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
@@ -120,4 +61,109 @@
<data name="ApplicationName" xml:space="preserve">
<value>Marechai.App-es</value>
</data>
<data name="News" xml:space="preserve">
<value>Noticias</value>
</data>
<data name="Books" xml:space="preserve">
<value>Libros</value>
</data>
<data name="Companies" xml:space="preserve">
<value>Empresas</value>
</data>
<data name="Computers" xml:space="preserve">
<value>Computadoras</value>
</data>
<data name="Consoles" xml:space="preserve">
<value>Consolas</value>
</data>
<data name="Documents" xml:space="preserve">
<value>Documentos</value>
</data>
<data name="Dumps" xml:space="preserve">
<value>Volcados</value>
</data>
<data name="GraphicalProcessingUnits" xml:space="preserve">
<value>Unidades de Procesamiento Gráfico</value>
</data>
<data name="Magazines" xml:space="preserve">
<value>Revistas</value>
</data>
<data name="People" xml:space="preserve">
<value>Personas</value>
</data>
<data name="Processors" xml:space="preserve">
<value>Procesadores</value>
</data>
<data name="Software" xml:space="preserve">
<value>Software</value>
</data>
<data name="SoundSynthesizers" xml:space="preserve">
<value>Sintetizadores de Sonido</value>
</data>
<data name="Settings" xml:space="preserve">
<value>Configuración</value>
</data>
<data name="Login" xml:space="preserve">
<value>Iniciar sesión</value>
</data>
<data name="Logout" xml:space="preserve">
<value>Cerrar sesión</value>
</data>
<data name="Company is active." xml:space="preserve">
<value>La empresa está activa.</value>
</data>
<data name="Company sold to {0} on {1}." xml:space="preserve">
<value>La empresa fue vendida a {0} el {1}.</value>
</data>
<data name="Company sold on {0} to an unknown company." xml:space="preserve">
<value>La empresa fue vendida el {0} a una empresa desconocida.</value>
</data>
<data name="Company sold to {0} on an unknown date." xml:space="preserve">
<value>La empresa fue vendida a {0} en una fecha desconocida.</value>
</data>
<data name="Company was sold to an unknown company on an unknown date." xml:space="preserve">
<value>La empresa fue vendida a una empresa desconocida en una fecha desconocida.</value>
</data>
<data name="Company merged on {0} to form {1}." xml:space="preserve">
<value>La empresa se fusionó el {0} para formar {1}.</value>
</data>
<data name="Company merged on {0} to form an unknown company." xml:space="preserve">
<value>La empresa se fusionó el {0} para formar una empresa desconocida.</value>
</data>
<data name="Company merged on an unknown date to form {0}." xml:space="preserve">
<value>La empresa se fusionó en una fecha desconocida para formar {0}.</value>
</data>
<data name="Company merged to form an unknown company on an unknown date." xml:space="preserve">
<value>La empresa se fusionó para formar una empresa desconocida en una fecha desconocida.</value>
</data>
<data name="Current company status is unknown." xml:space="preserve">
<value>El estado actual de la empresa es desconocido.</value>
</data>
<data name="unknown date" xml:space="preserve">
<value>fecha desconocida</value>
</data>
<data name="Company declared bankruptcy on {0}." xml:space="preserve">
<value>La empresa declaró quiebra el {0}.</value>
</data>
<data name="Company declared bankruptcy on an unknown date." xml:space="preserve">
<value>La empresa declaró quiebra en una fecha desconocida.</value>
</data>
<data name="Company ceased operations on {0}." xml:space="preserve">
<value>La empresa cesó operaciones el {0}.</value>
</data>
<data name="Company ceased operations on an unknown date." xml:space="preserve">
<value>La empresa cesó operaciones en una fecha desconocida.</value>
</data>
<data name="Company renamed to {0} on {1}." xml:space="preserve">
<value>La empresa fue renombrada a {0} el {1}.</value>
</data>
<data name="Company was renamed on {0} to an unknown name." xml:space="preserve">
<value>La empresa fue renombrada el {0} a un nombre desconocido.</value>
</data>
<data name="Company renamed to {0} on an unknown date." xml:space="preserve">
<value>La empresa fue renombrada a {0} en una fecha desconocida.</value>
</data>
<data name="Company was renamed on an unknown date to an unknown name." xml:space="preserve">
<value>La empresa fue renombrada en una fecha desconocida a un nombre desconocido.</value>
</data>
</root>

View File

@@ -1,64 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
@@ -120,4 +61,109 @@
<data name="ApplicationName" xml:space="preserve">
<value>Marechai.App-fr</value>
</data>
<data name="News" xml:space="preserve">
<value>Actualités</value>
</data>
<data name="Books" xml:space="preserve">
<value>Livres</value>
</data>
<data name="Companies" xml:space="preserve">
<value>Entreprises</value>
</data>
<data name="Computers" xml:space="preserve">
<value>Ordinateurs</value>
</data>
<data name="Consoles" xml:space="preserve">
<value>Consoles</value>
</data>
<data name="Documents" xml:space="preserve">
<value>Documents</value>
</data>
<data name="Dumps" xml:space="preserve">
<value>Vidages</value>
</data>
<data name="GraphicalProcessingUnits" xml:space="preserve">
<value>Processeurs Graphiques</value>
</data>
<data name="Magazines" xml:space="preserve">
<value>Magazines</value>
</data>
<data name="People" xml:space="preserve">
<value>Personnes</value>
</data>
<data name="Processors" xml:space="preserve">
<value>Processeurs</value>
</data>
<data name="Software" xml:space="preserve">
<value>Logiciels</value>
</data>
<data name="SoundSynthesizers" xml:space="preserve">
<value>Synthétiseurs de Son</value>
</data>
<data name="Settings" xml:space="preserve">
<value>Paramètres</value>
</data>
<data name="Login" xml:space="preserve">
<value>Connexion</value>
</data>
<data name="Logout" xml:space="preserve">
<value>Déconnexion</value>
</data>
<data name="Company is active." xml:space="preserve">
<value>L'entreprise est active.</value>
</data>
<data name="Company sold to {0} on {1}." xml:space="preserve">
<value>L'entreprise a été vendue à {0} le {1}.</value>
</data>
<data name="Company sold on {0} to an unknown company." xml:space="preserve">
<value>L'entreprise a été vendue le {0} à une entreprise inconnue.</value>
</data>
<data name="Company sold to {0} on an unknown date." xml:space="preserve">
<value>L'entreprise a été vendue à {0} à une date inconnue.</value>
</data>
<data name="Company was sold to an unknown company on an unknown date." xml:space="preserve">
<value>L'entreprise a été vendue à une entreprise inconnue à une date inconnue.</value>
</data>
<data name="Company merged on {0} to form {1}." xml:space="preserve">
<value>L'entreprise a fusionné le {0} pour former {1}.</value>
</data>
<data name="Company merged on {0} to form an unknown company." xml:space="preserve">
<value>L'entreprise a fusionné le {0} pour former une entreprise inconnue.</value>
</data>
<data name="Company merged on an unknown date to form {0}." xml:space="preserve">
<value>L'entreprise a fusionné à une date inconnue pour former {0}.</value>
</data>
<data name="Company merged to form an unknown company on an unknown date." xml:space="preserve">
<value>L'entreprise a fusionné pour former une entreprise inconnue à une date inconnue.</value>
</data>
<data name="Current company status is unknown." xml:space="preserve">
<value>L'état actuel de l'entreprise est inconnu.</value>
</data>
<data name="unknown date" xml:space="preserve">
<value>date inconnue</value>
</data>
<data name="Company declared bankruptcy on {0}." xml:space="preserve">
<value>L'entreprise a déclaré faillite le {0}.</value>
</data>
<data name="Company declared bankruptcy on an unknown date." xml:space="preserve">
<value>L'entreprise a déclaré faillite à une date inconnue.</value>
</data>
<data name="Company ceased operations on {0}." xml:space="preserve">
<value>L'entreprise a cessé ses activités le {0}.</value>
</data>
<data name="Company ceased operations on an unknown date." xml:space="preserve">
<value>L'entreprise a cessé ses activités à une date inconnue.</value>
</data>
<data name="Company renamed to {0} on {1}." xml:space="preserve">
<value>L'entreprise a été renommée en {0} le {1}.</value>
</data>
<data name="Company was renamed on {0} to an unknown name." xml:space="preserve">
<value>L'entreprise a été renommée le {0} en un nom inconnu.</value>
</data>
<data name="Company renamed to {0} on an unknown date." xml:space="preserve">
<value>L'entreprise a été renommée en {0} à une date inconnue.</value>
</data>
<data name="Company was renamed on an unknown date to an unknown name." xml:space="preserve">
<value>L'entreprise a été renommée à une date inconnue en un nom inconnu.</value>
</data>
</root>

View File

@@ -1,64 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
@@ -120,4 +61,109 @@
<data name="ApplicationName" xml:space="preserve">
<value>Marechai.App-pt-BR</value>
</data>
<data name="News" xml:space="preserve">
<value>Notícias</value>
</data>
<data name="Books" xml:space="preserve">
<value>Livros</value>
</data>
<data name="Companies" xml:space="preserve">
<value>Empresas</value>
</data>
<data name="Computers" xml:space="preserve">
<value>Computadores</value>
</data>
<data name="Consoles" xml:space="preserve">
<value>Consoles</value>
</data>
<data name="Documents" xml:space="preserve">
<value>Documentos</value>
</data>
<data name="Dumps" xml:space="preserve">
<value>Dumps</value>
</data>
<data name="GraphicalProcessingUnits" xml:space="preserve">
<value>Unidades de Processamento Gráfico</value>
</data>
<data name="Magazines" xml:space="preserve">
<value>Revistas</value>
</data>
<data name="People" xml:space="preserve">
<value>Pessoas</value>
</data>
<data name="Processors" xml:space="preserve">
<value>Processadores</value>
</data>
<data name="Software" xml:space="preserve">
<value>Software</value>
</data>
<data name="SoundSynthesizers" xml:space="preserve">
<value>Sintetizadores de Som</value>
</data>
<data name="Settings" xml:space="preserve">
<value>Configurações</value>
</data>
<data name="Login" xml:space="preserve">
<value>Fazer Login</value>
</data>
<data name="Logout" xml:space="preserve">
<value>Fazer Logout</value>
</data>
<data name="Company is active." xml:space="preserve">
<value>A empresa está ativa.</value>
</data>
<data name="Company sold to {0} on {1}." xml:space="preserve">
<value>A empresa foi vendida para {0} em {1}.</value>
</data>
<data name="Company sold on {0} to an unknown company." xml:space="preserve">
<value>A empresa foi vendida em {0} para uma empresa desconhecida.</value>
</data>
<data name="Company sold to {0} on an unknown date." xml:space="preserve">
<value>A empresa foi vendida para {0} em uma data desconhecida.</value>
</data>
<data name="Company was sold to an unknown company on an unknown date." xml:space="preserve">
<value>A empresa foi vendida para uma empresa desconhecida em uma data desconhecida.</value>
</data>
<data name="Company merged on {0} to form {1}." xml:space="preserve">
<value>A empresa se fundiu em {0} para formar {1}.</value>
</data>
<data name="Company merged on {0} to form an unknown company." xml:space="preserve">
<value>A empresa se fundiu em {0} para formar uma empresa desconhecida.</value>
</data>
<data name="Company merged on an unknown date to form {0}." xml:space="preserve">
<value>A empresa se fundiu em uma data desconhecida para formar {0}.</value>
</data>
<data name="Company merged to form an unknown company on an unknown date." xml:space="preserve">
<value>A empresa se fundiu para formar uma empresa desconhecida em uma data desconhecida.</value>
</data>
<data name="Current company status is unknown." xml:space="preserve">
<value>O status atual da empresa é desconhecido.</value>
</data>
<data name="unknown date" xml:space="preserve">
<value>data desconhecida</value>
</data>
<data name="Company declared bankruptcy on {0}." xml:space="preserve">
<value>A empresa declarou falência em {0}.</value>
</data>
<data name="Company declared bankruptcy on an unknown date." xml:space="preserve">
<value>A empresa declarou falência em uma data desconhecida.</value>
</data>
<data name="Company ceased operations on {0}." xml:space="preserve">
<value>A empresa cessou operações em {0}.</value>
</data>
<data name="Company ceased operations on an unknown date." xml:space="preserve">
<value>A empresa cessou operações em uma data desconhecida.</value>
</data>
<data name="Company renamed to {0} on {1}." xml:space="preserve">
<value>A empresa foi renomeada para {0} em {1}.</value>
</data>
<data name="Company was renamed on {0} to an unknown name." xml:space="preserve">
<value>A empresa foi renomeada em {0} para um nome desconhecido.</value>
</data>
<data name="Company renamed to {0} on an unknown date." xml:space="preserve">
<value>A empresa foi renomeada para {0} em uma data desconhecida.</value>
</data>
<data name="Company was renamed on an unknown date to an unknown name." xml:space="preserve">
<value>A empresa foi renomeada em uma data desconhecida para um nome desconhecido.</value>
</data>
</root>

View File

@@ -1,8 +1,9 @@
{
"AppConfig": {
"AppConfig": {
"Environment": "Production"
},
"ApiClient": {
"ApiClient": {
"Url": "http://localhost:5023",
"UseNativeHandler": true
},
"LocalizationConfiguration": {

View File

@@ -52,19 +52,14 @@ public class MachineDto : BaseDto<int>
[JsonPropertyName("family_name")]
public string? FamilyName { get; set; }
[JsonIgnore]
public List<GpuDto>? Gpus { get; set; }
[JsonIgnore]
public List<MemoryDto>? Memory { get; set; }
[JsonIgnore]
public List<ProcessorDto>? Processors { get; set; }
[JsonIgnore]
public List<SoundSynthDto>? SoundSynthesizers { get; set; }
[JsonIgnore]
public List<StorageDto>? Storage { get; set; }
[JsonPropertyName("company")]

View File

@@ -40,13 +40,13 @@ namespace Marechai.Server.Controllers;
[ApiController]
public class ComputersController(MarechaiContext context) : ControllerBase
{
[HttpGet("/count")]
[HttpGet("count")]
[AllowAnonymous]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public Task<int> GetComputersCountAsync() => context.Machines.CountAsync(c => c.Type == MachineType.Computer);
[HttpGet("/minimum-year")]
[HttpGet("minimum-year")]
[AllowAnonymous]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
@@ -56,7 +56,7 @@ public class ComputersController(MarechaiContext context) : ControllerBase
t.Introduced.Value.Year > 1000)
.MinAsync(t => t.Introduced.Value.Year);
[HttpGet("/maximum-year")]
[HttpGet("maximum-year")]
[AllowAnonymous]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
@@ -66,7 +66,7 @@ public class ComputersController(MarechaiContext context) : ControllerBase
t.Introduced.Value.Year > 1000)
.MaxAsync(t => t.Introduced.Value.Year);
[HttpGet("/by-letter/{c}")]
[HttpGet("by-letter/{c}")]
[AllowAnonymous]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
@@ -78,13 +78,14 @@ public class ComputersController(MarechaiContext context) : ControllerBase
.ThenBy(m => m.Name)
.Select(m => new MachineDto
{
Id = m.Id,
Name = m.Name,
Company = m.Company.Name
Id = m.Id,
Name = m.Name,
Company = m.Company.Name,
Introduced = m.Introduced
})
.ToListAsync();
[HttpGet("/by-year/{year:int}")]
[HttpGet("by-year/{year:int}")]
[AllowAnonymous]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
@@ -97,9 +98,10 @@ public class ComputersController(MarechaiContext context) : ControllerBase
.ThenBy(m => m.Name)
.Select(m => new MachineDto
{
Id = m.Id,
Name = m.Name,
Company = m.Company.Name
Id = m.Id,
Name = m.Name,
Company = m.Company.Name,
Introduced = m.Introduced
})
.ToListAsync();
@@ -113,9 +115,10 @@ public class ComputersController(MarechaiContext context) : ControllerBase
.ThenBy(m => m.Name)
.Select(m => new MachineDto
{
Id = m.Id,
Name = m.Name,
Company = m.Company.Name
Id = m.Id,
Name = m.Name,
Company = m.Company.Name,
Introduced = m.Introduced
})
.ToListAsync();
}

View File

@@ -40,13 +40,13 @@ namespace Marechai.Server.Controllers;
[ApiController]
public class ConsolesController(MarechaiContext context) : ControllerBase
{
[HttpGet("/count")]
[HttpGet("count")]
[AllowAnonymous]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public Task<int> GetConsolesCountAsync() => context.Machines.CountAsync(c => c.Type == MachineType.Console);
[HttpGet("/minimum-year")]
[HttpGet("minimum-year")]
[AllowAnonymous]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
@@ -56,7 +56,7 @@ public class ConsolesController(MarechaiContext context) : ControllerBase
t.Introduced.Value.Year > 1000)
.MinAsync(t => t.Introduced.Value.Year);
[HttpGet("/maximum-year")]
[HttpGet("maximum-year")]
[AllowAnonymous]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
@@ -66,7 +66,7 @@ public class ConsolesController(MarechaiContext context) : ControllerBase
t.Introduced.Value.Year > 1000)
.MaxAsync(t => t.Introduced.Value.Year);
[HttpGet("/by-letter/{c}")]
[HttpGet("by-letter/{c}")]
[AllowAnonymous]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
@@ -78,13 +78,14 @@ public class ConsolesController(MarechaiContext context) : ControllerBase
.ThenBy(m => m.Name)
.Select(m => new MachineDto
{
Id = m.Id,
Name = m.Name,
Company = m.Company.Name
Id = m.Id,
Name = m.Name,
Company = m.Company.Name,
Introduced = m.Introduced
})
.ToListAsync();
[HttpGet("/by-year/{year:int}")]
[HttpGet("by-year/{year:int}")]
[AllowAnonymous]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
@@ -97,9 +98,10 @@ public class ConsolesController(MarechaiContext context) : ControllerBase
.ThenBy(m => m.Name)
.Select(m => new MachineDto
{
Id = m.Id,
Name = m.Name,
Company = m.Company.Name
Id = m.Id,
Name = m.Name,
Company = m.Company.Name,
Introduced = m.Introduced
})
.ToListAsync();
@@ -113,9 +115,10 @@ public class ConsolesController(MarechaiContext context) : ControllerBase
.ThenBy(m => m.Name)
.Select(m => new MachineDto
{
Id = m.Id,
Name = m.Name,
Company = m.Company.Name
Id = m.Id,
Name = m.Name,
Company = m.Company.Name,
Introduced = m.Introduced
})
.ToListAsync();
}

View File

@@ -40,13 +40,7 @@ namespace Marechai.Server.Controllers;
[Route("/machines")]
[ApiController]
public class MachinesController
(
MarechaiContext context,
GpusController gpusController,
ProcessorsController processorsController,
SoundSynthsController soundSynthsController
) : ControllerBase
public class MachinesController(MarechaiContext context) : ControllerBase
{
[HttpGet]
[AllowAnonymous]
@@ -232,7 +226,25 @@ public class MachinesController
model.FamilyId = family.Id;
}
model.Gpus = await gpusController.GetByMachineAsync(machine.Id);
model.Gpus = await context.GpusByMachine.Where(g => g.MachineId == machine.Id)
.Select(g => g.Gpu)
.OrderBy(g => g.Company.Name)
.ThenBy(g => g.Name)
.Select(g => new GpuDto
{
Id = g.Id,
Name = g.Name,
Company = g.Company.Name,
CompanyId = g.Company.Id,
ModelCode = g.ModelCode,
Introduced = g.Introduced,
Package = g.Package,
Process = g.Process,
ProcessNm = g.ProcessNm,
DieSize = g.DieSize,
Transistors = g.Transistors
})
.ToListAsync();
model.Memory = await context.MemoryByMachine.Where(m => m.MachineId == machine.Id)
.Select(m => new MemoryDto
@@ -244,9 +256,63 @@ public class MachinesController
})
.ToListAsync();
model.Processors = await processorsController.GetByMachineAsync(machine.Id);
model.Processors = await context.ProcessorsByMachine.Where(p => p.MachineId == machine.Id)
.Select(p => new ProcessorDto
{
Name = p.Processor.Name,
CompanyName = p.Processor.Company.Name,
CompanyId = p.Processor.Company.Id,
ModelCode = p.Processor.ModelCode,
Introduced = p.Processor.Introduced,
Speed = p.Speed,
Package = p.Processor.Package,
Gprs = p.Processor.Gprs,
GprSize = p.Processor.GprSize,
Fprs = p.Processor.Fprs,
FprSize = p.Processor.FprSize,
Cores = p.Processor.Cores,
ThreadsPerCore = p.Processor.ThreadsPerCore,
Process = p.Processor.Process,
ProcessNm = p.Processor.ProcessNm,
DieSize = p.Processor.DieSize,
Transistors = p.Processor.Transistors,
DataBus = p.Processor.DataBus,
AddrBus = p.Processor.AddrBus,
SimdRegisters = p.Processor.SimdRegisters,
SimdSize = p.Processor.SimdSize,
L1Instruction = p.Processor.L1Instruction,
L1Data = p.Processor.L1Data,
L2 = p.Processor.L2,
L3 = p.Processor.L3,
InstructionSet = p.Processor.InstructionSet.Name,
Id = p.Processor.Id,
InstructionSetExtensions = p.Processor.InstructionSetExtensions
.Select(e => e.Extension.Extension)
.ToList()
})
.ToListAsync();
model.SoundSynthesizers = await soundSynthsController.GetByMachineAsync(machine.Id);
model.SoundSynthesizers = await context.SoundByMachine.Where(s => s.MachineId == machine.Id)
.Select(s => s.SoundSynth)
.OrderBy(s => s.Company.Name)
.ThenBy(s => s.Name)
.ThenBy(s => s.ModelCode)
.Select(s => new SoundSynthDto
{
Id = s.Id,
Name = s.Name,
CompanyId = s.Company.Id,
CompanyName = s.Company.Name,
ModelCode = s.ModelCode,
Introduced = s.Introduced,
Voices = s.Voices,
Frequency = s.Frequency,
Depth = s.Depth,
SquareWave = s.SquareWave,
WhiteNoise = s.WhiteNoise,
Type = s.Type
})
.ToListAsync();
model.Storage = await context.StorageByMachine.Where(s => s.MachineId == machine.Id)
.Select(s => new StorageDto

View File

@@ -210,24 +210,19 @@ file class Program
builder.Services.AddScoped<TokenService, TokenService>();
// Read allowed CORS origins from configuration
string[] allowedOrigins = builder.Configuration.GetSection("CORS:AllowedOrigins").Get<string[]>();
string[] allowedOrigins = builder.Configuration.GetSection("CORS:AllowedOrigins").Get<string[]>() ??
Array.Empty<string>();
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowFrontend",
policy =>
{
switch(allowedOrigins)
{
case ["*"]:
policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod();
break;
case { Length: > 0 }:
policy.WithOrigins(allowedOrigins).AllowAnyHeader().AllowAnyMethod();
break;
}
// Check if wildcard is in the allowed origins
if(allowedOrigins.Contains("*"))
policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod();
else if(allowedOrigins.Length > 0)
policy.WithOrigins(allowedOrigins).AllowAnyHeader().AllowAnyMethod();
});
});