This commit is contained in:
eelke 2024-12-25 12:44:46 +01:00
parent d803cd8003
commit 1173bfe81e
26 changed files with 410 additions and 131 deletions

View file

@ -7,6 +7,7 @@
<!-- Avalonia packages -->
<!-- Important: keep version in sync! -->
<PackageVersion Include="Avalonia" Version="11.2.1" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.2.1" />
<PackageVersion Include="Avalonia.Themes.Fluent" Version="11.2.1" />
<PackageVersion Include="Avalonia.Fonts.Inter" Version="11.2.1" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.2.1" />
@ -15,11 +16,15 @@
<PackageVersion Include="Avalonia.Browser" Version="11.2.1" />
<PackageVersion Include="Avalonia.Android" Version="11.2.1" />
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.2.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageVersion>
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
<PackageVersion Include="Npgsql" Version="8.0.5" />
<PackageVersion Include="Pure.DI" Version="2.1.38" />
<PackageVersion Include="Xamarin.AndroidX.Core.SplashScreen" Version="1.0.1.1" />
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageVersion Include="xunit" Version="2.9.2" />

View file

@ -13,9 +13,7 @@
<Application.Styles>
<FluentTheme />
<StyleInclude Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml" />
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
</Application.Styles>
<Application.Resources>
<app:Composition x:Key="Composition" />
</Application.Resources>
</Application>

View file

@ -1,6 +1,9 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using pgLabII.Infra;
using pgLabII.ViewModels;
using pgLabII.Views;
@ -15,27 +18,37 @@ public partial class App : Application
public override void OnFrameworkInitializationCompleted()
{
if (Resources[nameof(Composition)] is Composition composition)
{
switch (ApplicationLifetime)
{
case IClassicDesktopStyleApplicationLifetime desktop:
desktop.MainWindow = composition.MainWindow;
break;
// If you use CommunityToolkit, line below is needed to remove Avalonia data validation.
// Without this line you will get duplicate validations from both Avalonia and CT
// BindingPlugins.DataValidators.RemoveAt(0);
case ISingleViewApplicationLifetime singleViewPlatform:
singleViewPlatform.MainView = composition.MainWindow;
break;
}
// Register all the services needed for the application to run
var collection = new ServiceCollection();
collection.AddCommonServices();
// Handles disposables
if (ApplicationLifetime is IControlledApplicationLifetime controlledApplicationLifetime)
// Creates a ServiceProvider containing services from the provided IServiceCollection
var services = collection.BuildServiceProvider();
MigrateLocalDb(services);
MainWindow win = services.GetRequiredService<MainWindow>();
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
//controlledApplicationLifetime.Exit += (_, _) => composition.Dispose();
desktop.MainWindow = win;
}
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
{
singleViewPlatform.MainView = win;
}
base.OnFrameworkInitializationCompleted();
}
private void MigrateLocalDb(ServiceProvider services)
{
using var scope = services.CreateScope();
var db = services.GetRequiredService<LocalDb>();
//db.Database.Migrate();
}
}

View file

@ -1,20 +0,0 @@
using pgLabII.ViewModels;
using pgLabII.Views;
using Pure.DI;
using static Pure.DI.Lifetime;
namespace pgLabII;
internal partial class Composition
{
public void Setup() => DI.Setup()
.Root<MainWindow>(nameof(MainWindow))
// .Root<ServerListView>(nameof(ServerListView))
.Root<MainViewModel>(nameof(MainViewModel))
// .Bind().As(Singleton).To<MainViewModel>()
.Root<ServerListViewModel>(nameof(ServerListViewModel))
// .Bind().As(Singleton).To<ServerListViewModel>()
;
}

View file

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace pgLabII.ConnectionManager;
internal class ConnectionRepository
{
}

View file

@ -0,0 +1,13 @@
using Avalonia.Media;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace pgLabII.Infra;
public class ColorConverter : ValueConverter<Color, uint>
{
public ColorConverter()
: base(
v => v.ToUInt32(),
v => Color.FromUInt32(v))
{ }
}

58
pgLabII/Infra/LocalDb.cs Normal file
View file

@ -0,0 +1,58 @@
using System;
using Avalonia.Media;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using pgLabII.Model;
namespace pgLabII.Infra;
internal class LocalDb : DbContext
{
public DbSet<ServerConfiguration> ServerConfigurations => Set<ServerConfiguration>();
public string DbPath { get; }
public LocalDb()
{
var folder = Environment.SpecialFolder.LocalApplicationData;
var path = Environment.GetFolderPath(folder);
DbPath = System.IO.Path.Join(path, "local.db");
}
// The following configures EF to create a Sqlite database file in the
// special "local" folder for your platform.
protected override void OnConfiguring(DbContextOptionsBuilder options)
=> options.UseSqlite($"Data Source={DbPath}");
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
new ServerConfigurationEntityConfiguration().Configure(modelBuilder.Entity<ServerConfiguration>());
}
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
base.ConfigureConventions(configurationBuilder);
configurationBuilder
.Properties<Color>()
.HaveConversion<ColorConverter>();
}
}
public class ServerConfigurationEntityConfiguration : IEntityTypeConfiguration<ServerConfiguration>
{
public void Configure(EntityTypeBuilder<ServerConfiguration> b)
{
b.HasKey(e => e.Id);
}
}
public class ServerUserEntityConfiguration : IEntityTypeConfiguration<ServerUser>
{
public void Configure(EntityTypeBuilder<ServerUser> b)
{
b.HasKey(e => e.Id);
}
}

View file

@ -0,0 +1,9 @@
namespace pgLabII.Model;
public class Database
{
public int Oid { get; set; }
public string Name { get; set; } = "";
public int OwnerId { get; set; }
}

View file

@ -7,18 +7,19 @@ using Npgsql;
using pgLabII.Views;
using ReactiveUI;
namespace pgLabII.ViewModels;
namespace pgLabII.Model;
public class ServerConfiguration : ReactiveObject
{
private Color? _color;
private Color _color;
private bool _colorEnabled = true;
public Guid Id { get; set; } = Guid.NewGuid();
/// <summary>
/// For the user to help him identify the item
/// </summary>
public string Name { get; set; } = "";
public Color? Color
public Color Color
{
get => _color;
set
@ -32,13 +33,27 @@ public class ServerConfiguration : ReactiveObject
}
}
public bool ColorEnabled
{
get => _colorEnabled;
set
{
if (_colorEnabled != value)
{
_colorEnabled = value;
this.RaisePropertyChanged();
this.RaisePropertyChanged(propertyName: nameof(BackgroundBrush));
}
}
}
public string Host { get; set; } = "";
public ushort Port { get; set; } = 5432;
public string InitialDatabase { get; set; } = "";
public SslMode DefaultSslMode { get; set; } = SslMode.Prefer;
public IBrush? BackgroundBrush => Color.HasValue ? new SolidColorBrush(Color.Value) : null;
public IBrush? BackgroundBrush => ColorEnabled ? new SolidColorBrush(Color) : null;
public ObservableCollection<ServerUser> Users { get; } = [];
public ServerUser User { get; set; } = new();
public ReactiveCommand<Unit, Unit> EditCommand { get; }
@ -46,9 +61,22 @@ public class ServerConfiguration : ReactiveObject
{
EditCommand = ReactiveCommand.Create(() =>
{
EditServerConfigurationWindow window = new() { DataContext = this };
EditServerConfigurationWindow window = new() { DataContext = this, New = false };
window.Show();
});
}
public ServerConfiguration(ServerConfiguration src)
: this()
{
Color = src.Color;
ColorEnabled = src.ColorEnabled;
Id = src.Id;
Name = src.Name;
Port = src.Port;
InitialDatabase = src.InitialDatabase;
DefaultSslMode = src.DefaultSslMode;
User = src.User;
}
}

View file

@ -1,11 +1,11 @@
using Npgsql;
using pgLabII.ViewModels;
using System;
namespace pgLabII.ViewModels;
namespace pgLabII.Model;
public class ServerUser : ViewModelBase
{
public Guid Id { get; set; }
public Guid ServerConfigurationId { get; set; }
public string Name { get; set; } = "";
public string Password { get; set; } = "";
public SslMode? SslModeOverride { get; set; }
}

View file

@ -0,0 +1,31 @@
using Microsoft.Extensions.DependencyInjection;
using pgLabII.Infra;
using pgLabII.ViewModels;
using pgLabII.Views;
namespace pgLabII;
internal static class ServiceCollectionExtensions
{
public static void AddCommonServices(this IServiceCollection collection)
{
collection.AddTransient<MainWindow>();
collection.AddTransient<MainViewModel>();
collection.AddTransient<ServerListViewModel>();
collection.AddScoped<LocalDb>();
}
}
/*
* .Root<MainWindow>(nameof(MainWindow))
// .Root<ServerListView>(nameof(ServerListView))
.Root<MainViewModel>(nameof(MainViewModel))
// .Bind().As(Singleton).To<MainViewModel>()
.Root<ServerListViewModel>(nameof(ServerListViewModel))
.Bind().As(Scoped).To<LocalDb>()
.Root<LocalDb>(nameof(LocalDb))
// .Bind().As(Singleton).To<ServerListViewModel>()
;
*/

View file

@ -0,0 +1,33 @@
using System.Reactive;
using pgLabII.Model;
using ReactiveUI;
namespace pgLabII.ViewModels;
public class EditServerConfigurationViewModel : ViewModelBase
{
public ServerConfiguration Configuration { get; set; }
public ReactiveCommand<Unit, Unit> SaveAndCloseCommand { get; }
public ReactiveCommand<Unit, Unit> CloseCommand { get; }
public EditServerConfigurationViewModel()
{
Configuration = new();
SaveAndCloseCommand = ReactiveCommand.Create(() =>
{
});
CloseCommand = ReactiveCommand.Create(() =>
{
});
}
public EditServerConfigurationViewModel(ServerConfiguration configuration)
: this()
{
Configuration = configuration;
}
}

View file

@ -1,4 +1,7 @@

using System;
using Microsoft.Extensions.DependencyInjection;
namespace pgLabII.ViewModels;
public class MainViewModel : ViewModelBase

View file

@ -1,10 +1,9 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Collections.ObjectModel;
using ReactiveUI;
using System.Reactive;
using Avalonia.Media;
using DynamicData;
using pgLabII.Views;
using pgLabII.Model;
namespace pgLabII.ViewModels;
@ -14,20 +13,29 @@ public class ServerListViewModel : ViewModelBase
[
new ServerConfiguration()
{
Name = "Foo",
Name = "Local pg15",
Color = Colors.Aquamarine,
Host = "db.host.nl"
ColorEnabled = true,
Host = "localhost",
Port = 5434,
User = new ()
{
Name = "postgres",
Password = "admin",
},
},
new ServerConfiguration()
{
Name = "Bar",
Color = Colors.Bisque,
ColorEnabled = false,
Host = "db.host.nl"
}
];
public ReactiveCommand<ServerConfiguration, Unit> RemoveServerCommand { get; }
public ReactiveCommand<Unit, Unit> AddServerCommand { get; }
public ServerListViewModel()
{
RemoveServerCommand = ReactiveCommand.Create<ServerConfiguration, Unit>((sc) =>
@ -35,5 +43,12 @@ public class ServerListViewModel : ViewModelBase
ServerConfigurations.Remove(sc);
return Unit.Default;
});
AddServerCommand = ReactiveCommand.Create(() =>
{
ServerConfiguration sc = new();
EditServerConfigurationWindow window = new() { DataContext = sc, New = true };
window.Show();
});
}
}

View file

@ -0,0 +1,9 @@
namespace pgLabII.ViewModels;
/// <summary>
/// Toplevel viewmodel for the global resources of a postgresql installation.
/// </summary>
public class ServerViewModel : ViewModelBase
{
}

View file

@ -0,0 +1,8 @@
<UserControl xmlns="https://github.com/avaloniaui"
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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="pgLabII.Views.DatabaseListView">
Welcome to Avalonia!
</UserControl>

View file

@ -0,0 +1,14 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace pgLabII.Views;
public partial class DatabaseListView : UserControl
{
public DatabaseListView()
{
InitializeComponent();
}
}

View file

@ -4,26 +4,28 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
xmlns:vm="clr-namespace:pgLabII.ViewModels"
x:DataType="vm:ServerConfiguration"
x:DataType="vm:EditServerConfigurationViewModel"
x:Class="pgLabII.Views.EditServerConfigurationWindow"
Title="EditServerConfiguration">
<Design.DataContext>
<!-- This only sets the DataContext for the previewer in an IDE,
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
<vm:ServerConfiguration />
<vm:EditServerConfigurationViewModel />
</Design.DataContext>
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Top">
<TextBlock>Name:</TextBlock>
<TextBox Text="{Binding Name}"/>
<TextBox Text="{Binding Configuration.Name}"/>
<TextBlock>Color:</TextBlock>
<StackPanel Orientation="Horizontal">
<ColorPicker Color="{Binding Color}"/>
<CheckBox IsChecked="{Binding Configuration.ColorEnabled}"/>
<ColorPicker Color="{Binding Configuration.Color}"
/>
</StackPanel>
<TextBlock>Host:</TextBlock>
<TextBox Text="{Binding Host}"/>
<TextBox Text="{Binding Configuration.Host}"/>
</StackPanel>
</Window>

View file

@ -1,6 +1,7 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using pgLabII.ViewModels;
namespace pgLabII.Views;
@ -9,6 +10,11 @@ public partial class EditServerConfigurationWindow : Window
public EditServerConfigurationWindow()
{
InitializeComponent();
}
DataContext = new EditServerConfigurationViewModel(
new());
}
public bool New { get; set; }
}

View file

@ -7,9 +7,19 @@
xmlns:app="clr-namespace:pgLabII"
DataContext="{StaticResource Composition}"
x:Class="pgLabII.Views.MainView"
x:DataType="app:Composition">
x:DataType="vm:MainViewModel">
<Design.DataContext>
<!-- This only sets the DataContext for the previewer in an IDE,
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
<vm:MainViewModel />
</Design.DataContext>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" DataContext="{Binding MainViewModel}">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<DataGrid>
<DataGrid.Columns >
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</UserControl>

View file

@ -1,11 +1,13 @@
using Avalonia.Controls;
using Avalonia.Controls;
using pgLabII.ViewModels;
namespace pgLabII.Views;
public partial class MainWindow : Window
{
public MainWindow()
public MainWindow(ServerListViewModel model)
{
DataContext = model;
InitializeComponent();
}
}

View file

@ -6,9 +6,18 @@
xmlns:vm="clr-namespace:pgLabII.ViewModels"
xmlns:app="clr-namespace:pgLabII"
DataContext="{StaticResource Composition}"
x:DataType="app:Composition"
x:DataType="vm:ServerListViewModel"
x:Class="pgLabII.Views.ServerListView">
<StackPanel x:Name="ServerList" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" DataContext="{Binding ServerListViewModel}">
<Design.DataContext>
<!-- This only sets the DataContext for the previewer in an IDE,
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
<vm:ServerListViewModel />
</Design.DataContext>
<StackPanel x:Name="ServerList" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid ColumnDefinitions="Auto,*,Auto">
<Button Grid.Column="2" Content="+" Command="{Binding AddServerCommand}"/>
</Grid>
<ListBox x:Name="Servers" ItemsSource="{Binding ServerConfigurations}">
<ListBox.ItemTemplate>
<DataTemplate>
@ -18,8 +27,7 @@
Orientation="Vertical"
HorizontalAlignment="Stretch"
Grid.Row="0"
Grid.Column="0"
>
Grid.Column="0">
<TextBlock Text="{Binding Name}" FontSize="18" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Host}" />
@ -33,8 +41,7 @@
Orientation="Horizontal"
HorizontalAlignment="Right"
Grid.Row="0"
Grid.Column="1"
>
Grid.Column="1">
<Button>DB</Button>
<Button>Server</Button>
<Button Content="...">
@ -45,8 +52,7 @@
<MenuItem Header="Remove"
Command="{Binding #ServerList.((vm:ServerListViewModel)DataContext).RemoveServerCommand}"
CommandParameter="{Binding .}"
Foreground="Crimson"
/>
Foreground="Crimson" />
</MenuFlyout>
</Button.Flyout>
</Button>

View file

@ -1,6 +1,5 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Controls;
using pgLabII.ViewModels;
namespace pgLabII.Views;
@ -8,6 +7,8 @@ public partial class ServerListView : UserControl
{
public ServerListView()
{
//DataContext = new ServerListViewModel();
InitializeComponent();
}
}

View file

@ -0,0 +1,10 @@
<Window xmlns="https://github.com/avaloniaui"
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:views="clr-namespace:pgLabII.Views"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="pgLabII.Views.ServerWindow"
Title="ServerWindow">
<views:DatabaseListView />
</Window>

View file

@ -0,0 +1,14 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace pgLabII.Views;
public partial class ServerWindow : Window
{
public ServerWindow()
{
InitializeComponent();
}
}

View file

@ -12,6 +12,7 @@
<ItemGroup>
<PackageReference Include="Avalonia" />
<PackageReference Include="Avalonia.Controls.DataGrid" />
<PackageReference Include="Avalonia.Themes.Fluent" />
<PackageReference Include="Avalonia.Fonts.Inter" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
@ -20,16 +21,16 @@
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="Avalonia.ReactiveUI" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
<PackageReference Include="Npgsql" />
<PackageReference Include="Pure.DI">
<PrivateAssets>all</PrivateAssets>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Npgsql" />
</ItemGroup>
<ItemGroup>
<Folder Include="ConnectionManager\" />
<Folder Include="Contracts\" />
</ItemGroup>
</Project>