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.Threading.Tasks;
using EstusShots.Client.Routes;
using EstusShots.Shared.Dto;
using EstusShots.Shared.Interfaces;
using EstusShots.Shared.Models;
@@ -22,6 +23,7 @@ namespace EstusShots.Client
// API Routes
public Seasons Seasons { get; }
public Episodes Episodes { get; }
public Players Players { get; }
/// <summary>
/// Creates a new instance of <see cref="EstusShotsClient"/>
@@ -34,6 +36,7 @@ namespace EstusShots.Client
Seasons = new Seasons(this);
Episodes = new Episodes(this);
Players = new Players(this);
}
/// <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;
PlayerEditorDialog.Parent = parent;
PlayerEditorDialog.TransientFor = parent;
PlayerEditorDialog.Show();
ReadFromModel();
}
@@ -55,6 +55,7 @@ namespace EstusShots.Gtk.Dialogs
{
ReadToModel();
OnDialogClosed?.Invoke(this, new DialogClosedEventArgs(true, _player));
PlayerEditorDialog.Dispose();
}
// Private Methods

View File

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

View File

@@ -439,4 +439,158 @@
</object>
</child>
</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>

View File

@@ -1,6 +1,8 @@
using System;
using System.Threading.Tasks;
using EstusShots.Gtk.Dialogs;
using EstusShots.Shared.Dto;
using EstusShots.Shared.Models.Parameters;
using Gtk;
using UI = Gtk.Builder.ObjectAttribute;
@@ -28,11 +30,38 @@ namespace EstusShots.Gtk
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;
// TODO
// Client.Players.SavePlayer();
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
// 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.Threading.Tasks;
using EstusShots.Gtk.Controls;
using EstusShots.Gtk.Dialogs;
using EstusShots.Shared.Dto;
using EstusShots.Shared.Models.Parameters;
using Gtk;
@@ -55,6 +56,7 @@ namespace EstusShots.Gtk
if (!res.OperationResult.Success)
{
InfoLabel.Text = $"Error while creating Season: {res.OperationResult.ShortMessage}";
ErrorDialog.Show(res.OperationResult);
return;
}
@@ -88,6 +90,7 @@ namespace EstusShots.Gtk
if (!res.OperationResult.Success)
{
InfoLabel.Text = $"Refresh Failed: {res.OperationResult.ShortMessage}";
ErrorDialog.Show(res.OperationResult);
return;
}

View File

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

View File

@@ -1,4 +1,6 @@
using System.Threading.Tasks;
using AutoMapper;
using EstusShots.Server.Models;
using EstusShots.Shared.Interfaces;
using EstusShots.Shared.Models;
using EstusShots.Shared.Models.Parameters;
@@ -9,28 +11,41 @@ namespace EstusShots.Server.Services
public class PlayersService : IPlayersController
{
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;
_context = context;
_mapper = mapper;
}
public Task<ApiResponse<GetPlayersResponse>> GetPlayers(GetPlayersParameter parameter)
public async Task<ApiResponse<GetPlayersResponse>> GetPlayers(GetPlayersParameter parameter)
{
throw new System.NotImplementedException();
}
public Task<ApiResponse<GetPlayerDetailsResponse>> GetPlayerDetails(GetPlayerDetailsParameter parameter)
public async Task<ApiResponse<GetPlayerDetailsResponse>> GetPlayerDetails(GetPlayerDetailsParameter parameter)
{
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();
}

View File

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