Nice Exception Dialog

This commit is contained in:
2020-03-01 23:37:43 +01:00
parent aa24f80fe2
commit 8953ac1808
10 changed files with 273 additions and 10 deletions

View File

@@ -4,6 +4,7 @@ using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using EstusShots.Client.Routes; using EstusShots.Client.Routes;
using EstusShots.Shared.Dto;
using EstusShots.Shared.Interfaces; using EstusShots.Shared.Interfaces;
using EstusShots.Shared.Models; using EstusShots.Shared.Models;
@@ -22,6 +23,7 @@ namespace EstusShots.Client
// API Routes // API Routes
public Seasons Seasons { get; } public Seasons Seasons { get; }
public Episodes Episodes { get; } public Episodes Episodes { get; }
public Players Players { get; }
/// <summary> /// <summary>
/// Creates a new instance of <see cref="EstusShotsClient"/> /// Creates a new instance of <see cref="EstusShotsClient"/>
@@ -34,6 +36,7 @@ namespace EstusShots.Client
Seasons = new Seasons(this); Seasons = new Seasons(this);
Episodes = new Episodes(this); Episodes = new Episodes(this);
Players = new Players(this);
} }
/// <summary> /// <summary>

View File

@@ -0,0 +1,40 @@
using EstusShots.Shared.Models;
using Gtk;
using UI = Gtk.Builder.ObjectAttribute;
namespace EstusShots.Gtk.Dialogs
{
public class ErrorHandler
{
[UI] private readonly Dialog ShowErrorDialog = null;
[UI] private readonly Label ErrorShortTextLabel = null;
[UI] private readonly Label ErrorDetailTextLabel = null;
[UI] private readonly TextView StackTraceTextView = null;
[UI] private readonly Button ErrorDialogCloseButton = null;
public void ShowFor(Window window, OperationResult error)
{
ShowErrorDialog.TransientFor = window;
ErrorDialogCloseButton.Clicked += (sender, args) => { ShowErrorDialog.Dispose();};
ErrorShortTextLabel.Text = error.ShortMessage;
ErrorDetailTextLabel.Visible = error.DetailedMessage != null;
ErrorDetailTextLabel.Text = error.DetailedMessage;
StackTraceTextView.Visible = error.StackTrace != null;
var buff = new TextBuffer(new TextTagTable()) {Text = error.StackTrace};
StackTraceTextView.Buffer = buff;
ShowErrorDialog.Show();
}
}
public static class ErrorDialog
{
public static Window MainWindow;
public static void Show(OperationResult error)
{
var handler = new ErrorHandler();
var builder = new Builder("MainWindow.glade");
builder.Autoconnect(handler);
handler.ShowFor(MainWindow, error);
}
}
}

View File

@@ -43,8 +43,8 @@ namespace EstusShots.Gtk.Dialogs
SavePlayerButton.Clicked += SavePlayerButtonOnClicked; SavePlayerButton.Clicked += SavePlayerButtonOnClicked;
PlayerEditorDialog.Parent = parent;
PlayerEditorDialog.TransientFor = parent; PlayerEditorDialog.TransientFor = parent;
PlayerEditorDialog.Show();
ReadFromModel(); ReadFromModel();
} }
@@ -55,6 +55,7 @@ namespace EstusShots.Gtk.Dialogs
{ {
ReadToModel(); ReadToModel();
OnDialogClosed?.Invoke(this, new DialogClosedEventArgs(true, _player)); OnDialogClosed?.Invoke(this, new DialogClosedEventArgs(true, _player));
PlayerEditorDialog.Dispose();
} }
// Private Methods // Private Methods

View File

@@ -1,8 +1,14 @@
using System;
using System.Threading.Tasks;
using EstusShots.Client; using EstusShots.Client;
using EstusShots.Gtk.Controls; using EstusShots.Gtk.Controls;
using EstusShots.Gtk.Dialogs;
using EstusShots.Shared.Dto; using EstusShots.Shared.Dto;
using EstusShots.Shared.Models;
using Gdk; using Gdk;
using GLib;
using Gtk; using Gtk;
using Application = Gtk.Application;
using UI = Gtk.Builder.ObjectAttribute; using UI = Gtk.Builder.ObjectAttribute;
using Window = Gtk.Window; using Window = Gtk.Window;
@@ -19,6 +25,8 @@ namespace EstusShots.Gtk
[UI] public readonly Box LoadingSpinner = null; [UI] public readonly Box LoadingSpinner = null;
[UI] public readonly Notebook Navigation = null; [UI] public readonly Notebook Navigation = null;
private EstusShotsClient Client { get; } private EstusShotsClient Client { get; }
private BindableListControl<Episode> EpisodesControl { get; set; } private BindableListControl<Episode> EpisodesControl { get; set; }
@@ -31,6 +39,8 @@ namespace EstusShots.Gtk
builder.Autoconnect(this); builder.Autoconnect(this);
Client = new EstusShotsClient(ApiUrl); Client = new EstusShotsClient(ApiUrl);
ErrorDialog.MainWindow = this;
ExceptionManager.UnhandledException += ExceptionManagerOnUnhandledException;
DeleteEvent += Window_DeleteEvent; DeleteEvent += Window_DeleteEvent;
Icon = Pixbuf.LoadFromResource("icon.png"); Icon = Pixbuf.LoadFromResource("icon.png");
@@ -49,6 +59,12 @@ namespace EstusShots.Gtk
UpdateTitle(); UpdateTitle();
} }
private void ExceptionManagerOnUnhandledException(UnhandledExceptionArgs args)
{
Console.WriteLine(args.ExceptionObject);
args.ExitApplication = false;
}
private void Window_DeleteEvent(object sender, DeleteEventArgs a) private void Window_DeleteEvent(object sender, DeleteEventArgs a)
{ {
Application.Quit(); Application.Quit();

View File

@@ -439,4 +439,158 @@
</object> </object>
</child> </child>
</object> </object>
<object class="GtkDialog" id="ShowErrorDialog">
<property name="can_focus">False</property>
<property name="title" translatable="yes">An Error Occured</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="window_position">center-on-parent</property>
<property name="default_width">500</property>
<property name="default_height">350</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">5</property>
<property name="margin_bottom">5</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<placeholder/>
</child>
<child>
<object class="GtkButton" id="ErrorDialogCloseButton">
<property name="label">gtk-close</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">5</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-no</property>
<property name="icon_size">6</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Error:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="ErrorShortTextLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Unknown Error</property>
<property name="wrap">True</property>
<property name="wrap_mode">char</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="ErrorDetailTextLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="margin_left">10</property>
<property name="margin_right">10</property>
<property name="wrap">True</property>
<property name="wrap_mode">char</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTextView" id="StackTraceTextView">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">False</property>
<property name="cursor_visible">False</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface> </interface>

View File

@@ -1,6 +1,8 @@
using System; using System;
using System.Threading.Tasks;
using EstusShots.Gtk.Dialogs; using EstusShots.Gtk.Dialogs;
using EstusShots.Shared.Dto; using EstusShots.Shared.Dto;
using EstusShots.Shared.Models.Parameters;
using Gtk; using Gtk;
using UI = Gtk.Builder.ObjectAttribute; using UI = Gtk.Builder.ObjectAttribute;
@@ -28,11 +30,38 @@ namespace EstusShots.Gtk
dialog.OnDialogClosed += PlayerEditorClosed; dialog.OnDialogClosed += PlayerEditorClosed;
} }
private void PlayerEditorClosed(object o, DialogClosedEventArgs args) private async void PlayerEditorClosed(object o, DialogClosedEventArgs args)
{ {
if (!args.Ok || !(args.Model is Player player)) return; if (!args.Ok || !(args.Model is Player player)) return;
var res = await Task.Factory.StartNew(()
=> Client.Players.SavePlayer(new SavePlayerParameter(player)).Result);
if (!res.OperationResult.Success)
{
Info($"Unable to save: {res.OperationResult.ShortMessage}");
ErrorDialog.Show(res.OperationResult);
return;
}
// ReloadPlayers();
}
// Private Methods
private async void ReloadPlayers()
{
var res = await Task.Factory.StartNew(()
=> Client.Players.GetPlayers(new GetPlayersParameter()).Result);
if (!res.OperationResult.Success)
{
InfoLabel.Text = $"Refresh failed: {res.OperationResult.ShortMessage}";
ErrorDialog.Show(res.OperationResult);
return;
}
// TODO // TODO
// Client.Players.SavePlayer(); // SeasonsControl.Items = res.Data.Seasons;
// SeasonsControl.DataBind();
// Info("Player list refreshed");
} }
} }
} }

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using EstusShots.Gtk.Controls; using EstusShots.Gtk.Controls;
using EstusShots.Gtk.Dialogs;
using EstusShots.Shared.Dto; using EstusShots.Shared.Dto;
using EstusShots.Shared.Models.Parameters; using EstusShots.Shared.Models.Parameters;
using Gtk; using Gtk;
@@ -55,6 +56,7 @@ namespace EstusShots.Gtk
if (!res.OperationResult.Success) if (!res.OperationResult.Success)
{ {
InfoLabel.Text = $"Error while creating Season: {res.OperationResult.ShortMessage}"; InfoLabel.Text = $"Error while creating Season: {res.OperationResult.ShortMessage}";
ErrorDialog.Show(res.OperationResult);
return; return;
} }
@@ -88,6 +90,7 @@ namespace EstusShots.Gtk
if (!res.OperationResult.Success) if (!res.OperationResult.Success)
{ {
InfoLabel.Text = $"Refresh Failed: {res.OperationResult.ShortMessage}"; InfoLabel.Text = $"Refresh Failed: {res.OperationResult.ShortMessage}";
ErrorDialog.Show(res.OperationResult);
return; return;
} }

View File

@@ -20,6 +20,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.2" /> <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.2" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.0.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="5.0.0" />
<PackageReference Include="Z.ExtensionMethods" Version="2.1.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -1,4 +1,6 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using AutoMapper;
using EstusShots.Server.Models;
using EstusShots.Shared.Interfaces; using EstusShots.Shared.Interfaces;
using EstusShots.Shared.Models; using EstusShots.Shared.Models;
using EstusShots.Shared.Models.Parameters; using EstusShots.Shared.Models.Parameters;
@@ -9,28 +11,41 @@ namespace EstusShots.Server.Services
public class PlayersService : IPlayersController public class PlayersService : IPlayersController
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly EstusShotsContext _context;
private readonly IMapper _mapper;
public PlayersService(ILogger<PlayersService> logger) public PlayersService(ILogger<PlayersService> logger, EstusShotsContext context, IMapper mapper)
{ {
_logger = logger; _logger = logger;
_context = context;
_mapper = mapper;
} }
public Task<ApiResponse<GetPlayersResponse>> GetPlayers(GetPlayersParameter parameter) public async Task<ApiResponse<GetPlayersResponse>> GetPlayers(GetPlayersParameter parameter)
{ {
throw new System.NotImplementedException(); throw new System.NotImplementedException();
} }
public Task<ApiResponse<GetPlayerDetailsResponse>> GetPlayerDetails(GetPlayerDetailsParameter parameter) public async Task<ApiResponse<GetPlayerDetailsResponse>> GetPlayerDetails(GetPlayerDetailsParameter parameter)
{ {
throw new System.NotImplementedException(); throw new System.NotImplementedException();
} }
public Task<ApiResponse<SavePlayerResponse>> SavePlayer(SavePlayerParameter parameter) public async Task<ApiResponse<SavePlayerResponse>> SavePlayer(SavePlayerParameter parameter)
{ {
throw new System.NotImplementedException(); var player = _mapper.Map<Player>(parameter.Player);
if (player.PlayerId.IsEmpty())
{
_context.Players.Add(player);
var count = await _context.SaveChangesAsync();
_logger.LogInformation($"Created {count} rows");
return new ApiResponse<SavePlayerResponse>(new SavePlayerResponse(player.PlayerId));
}
// TODO Update Player
return new ApiResponse<SavePlayerResponse>(new OperationResult(false, "NotImplemented"));
} }
public Task<ApiResponse<DeletePlayerResponse>> DeletePlayers(DeletePlayerParameter parameter) public async Task<ApiResponse<DeletePlayerResponse>> DeletePlayers(DeletePlayerParameter parameter)
{ {
throw new System.NotImplementedException(); throw new System.NotImplementedException();
} }

View File

@@ -47,6 +47,7 @@ namespace EstusShots.Server
// Register business logic services // Register business logic services
services.AddScoped<SeasonsService>(); services.AddScoped<SeasonsService>();
services.AddScoped<EpisodesService>(); services.AddScoped<EpisodesService>();
services.AddScoped<PlayersService>();
} }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.