Notebook layout and episodes page

This commit is contained in:
2020-02-29 20:14:54 +01:00
parent a982b15cbe
commit 96a65b24bd
5 changed files with 381 additions and 77 deletions

View File

@@ -14,12 +14,22 @@ namespace EstusShots.Gtk
internal class MainWindow : Window internal class MainWindow : Window
{ {
private const string ApiUrl = "http://localhost:5000/api/"; private const string ApiUrl = "http://localhost:5000/api/";
[UI] private readonly Label _infoLabel = null; [UI] private readonly Label _infoLabel = null;
[UI] public readonly Button LoadButton = null; [UI] public readonly Button LoadButton = null;
[UI] public readonly Box LoadingSpinner = null; [UI] public readonly Box LoadingSpinner = null;
[UI] public readonly Button NewSeasonButton = null; [UI] public readonly Button NewSeasonButton = null;
[UI] public readonly Button AddEpisodeButton = null;
[UI] public readonly Overlay SeasonsOverlay = null; [UI] public readonly Overlay SeasonsOverlay = null;
[UI] public readonly Overlay EpisodesOverlay = null;
[UI] public readonly TreeView SeasonsView = null; [UI] public readonly TreeView SeasonsView = null;
[UI] public readonly TreeView EpisodesTreeView = null;
[UI] public readonly Notebook Navigation = null;
[UI] public readonly Box EpisodesPage = null;
private EstusShotsClient Client { get; }
private BindableListControl<Season> SeasonsControl { get; set; }
private BindableListControl<Episode> EpisodesControl { get; set; }
public MainWindow() : this(new Builder("MainWindow.glade")) public MainWindow() : this(new Builder("MainWindow.glade"))
{ {
@@ -28,40 +38,82 @@ namespace EstusShots.Gtk
private MainWindow(Builder builder) : base(builder.GetObject("MainWindow").Handle) private MainWindow(Builder builder) : base(builder.GetObject("MainWindow").Handle)
{ {
builder.Autoconnect(this); builder.Autoconnect(this);
Client = new EstusShotsClient("http://localhost:5000/api/"); Client = new EstusShotsClient(ApiUrl);
DeleteEvent += Window_DeleteEvent; DeleteEvent += Window_DeleteEvent;
LoadButton.Clicked += LoadButtonClicked; LoadButton.Clicked += LoadButtonClicked;
NewSeasonButton.Clicked += NewSeasonButtonOnClicked; NewSeasonButton.Clicked += NewSeasonButtonOnClicked;
AddEpisodeButton.Clicked += AddEpisodeButtonOnClicked;
InitSeasonsControl();
InitEpisodesControl();
// The episodes page is not shown, as long as no season is selected
EpisodesPage.Hide();
var seasonsColumns = new List<DataColumn>
{
new DataColumn(nameof(Season.DisplayName)) {Title = "Name"},
new DataColumn(nameof(Season.Description)) {Title = "Description"},
new DataColumn(nameof(Season.Start))
{
Title = "Start",
Format = date => (date as DateTime?)?.ToString("dd.MM.yyyy hh:mm")
}
};
SeasonsControl = new BindableListControl<Season>(seasonsColumns, nameof(Season.SeasonId), SeasonsView);
SeasonsControl.OnSelectionChanged += SeasonsViewOnOnSelectionChanged;
Info("Application Started"); Info("Application Started");
// No need to wait for the loading to finnish // No need to wait for the loading to finnish
var _ = ReloadSeasons(); var _ = ReloadSeasons();
} }
private EstusShotsClient Client { get; } private void InitSeasonsControl()
private BindableListControl<Season> SeasonsControl { get; } {
var columns = new List<DataColumn>
{
new DataColumn(nameof(Season.DisplayName)) {Title = "Name"},
new DataColumn(nameof(Season.Description)) {Title = "Description"},
new DataColumn(nameof(Season.Start))
{
Title = "Start",
Format = date => (date as DateTime?)?.ToString("dd.MM.yyyy")
},
new DataColumn(nameof(Season.End))
{
Title = "End",
Format = date => (date as DateTime?)?.ToString("dd.MM.yyyy") ?? "Ongoing"
}
};
SeasonsControl = new BindableListControl<Season>(columns, nameof(Season.SeasonId), SeasonsView);
SeasonsControl.OnSelectionChanged += SeasonsViewOnOnSelectionChanged;
}
private void InitEpisodesControl()
{
var columns = new List<DataColumn>
{
new DataColumn(nameof(Episode.DisplayName)) {Title = "Name"},
new DataColumn(nameof(Episode.Title)) {Title = "Title"},
new DataColumn(nameof(Episode.Date))
{
Title = "Date",
Format = d => (d as DateTime?)?.ToString("dd.MM.yyyy")
},
new DataColumn(nameof(Episode.Start))
{
Title = "Start",
Format = d => (d as DateTime?)?.ToString("HH:mm")
},
new DataColumn(nameof(Episode.End))
{
Title = "End",
Format = d => (d as DateTime?)?.ToString("HH:mm") ?? "Ongoing"
}
};
EpisodesControl = new BindableListControl<Episode>(columns, nameof(Episode.EpisodeId), EpisodesTreeView);
}
private async void SeasonsViewOnOnSelectionChanged(object sender, SelectionChangedEventArgs e) private async void SeasonsViewOnOnSelectionChanged(object sender, SelectionChangedEventArgs e)
{ {
if (!(e.Selection is Season season)) return; if (!(e.Selection is Season season)) return;
using var _ = new LoadingMode(this);
// TODO this is test code EpisodesPage.Show();
var parameter = new GetEpisodesParameter(season.SeasonId); var parameter = new GetEpisodesParameter(season.SeasonId);
var res = await Client.Episodes.GetEpisodes(parameter); var res = await Client.Episodes.GetEpisodes(parameter);
EpisodesControl.Items = res.Data.Episodes;
EpisodesControl.DataBind();
Info($"{season.DisplayName}: {res.Data.Episodes.Count} episodes"); Info($"{season.DisplayName}: {res.Data.Episodes.Count} episodes");
} }
@@ -87,6 +139,47 @@ namespace EstusShots.Gtk
Info("Created new Season"); Info("Created new Season");
} }
private async void AddEpisodeButtonOnClicked(object sender, EventArgs e)
{
if (SeasonsControl.SelectedItem == null) return;
using var _ = new LoadingMode(this);
var season = SeasonsControl.SelectedItem;
// Some random test data
var rand = new Random();
var start = new DateTime(
DateTime.Today.Year,
DateTime.Today.Month,
DateTime.Today.Day,
DateTime.Today.Hour + rand.Next(0, 12),
DateTime.Today.Minute + rand.Next(0, 59),
DateTime.Today.Second
);
var end = start.AddHours(rand.Next(2, 4));
var ep = new Episode
{
SeasonId = season.SeasonId,
Number = EpisodesControl.Items.Any() ? EpisodesControl.Items.Max(x => x.Number) + 1 : 1,
Title = $"An Episode in season '{season.Game}'",
Date = DateTime.Today,
Start = start,
End = end
};
var parameter = new SaveEpisodeParameter(ep);
var res = await Client.Episodes.SaveEpisode(parameter);
if (!res.OperationResult.Success)
{
Info($"Cannot add episode: {res.OperationResult.ShortMessage}");
return;
}
await ReloadEpisodes();
}
private async void LoadButtonClicked(object sender, EventArgs a) private async void LoadButtonClicked(object sender, EventArgs a)
{ {
using var _ = new LoadingMode(this); using var _ = new LoadingMode(this);
@@ -106,7 +199,23 @@ namespace EstusShots.Gtk
SeasonsControl.Items = res.Data.Seasons; SeasonsControl.Items = res.Data.Seasons;
SeasonsControl.DataBind(); SeasonsControl.DataBind();
Info("List Refreshed"); Info("Seasons Refreshed");
}
private async Task ReloadEpisodes()
{
var seasonId = SeasonsControl.SelectedItem.SeasonId;
var res = await Task.Factory.StartNew(
() => Client.Episodes.GetEpisodes(new GetEpisodesParameter(seasonId)).Result);
if (!res.OperationResult.Success)
{
_infoLabel.Text = $"Refresh Failed: {res.OperationResult.ShortMessage}";
return;
}
EpisodesControl.Items = res.Data.Episodes;
EpisodesControl.DataBind();
Info("Episodes Refreshed");
} }
private void Window_DeleteEvent(object sender, DeleteEventArgs a) private void Window_DeleteEvent(object sender, DeleteEventArgs a)
@@ -118,6 +227,5 @@ namespace EstusShots.Gtk
{ {
_infoLabel.Text = message; _infoLabel.Text = message;
} }
} }
} }

View File

@@ -32,8 +32,11 @@
<property name="position">1</property> <property name="position">1</property>
</packing> </packing>
</child> </child>
<style>
<class name="bg-black"/>
</style>
</object> </object>
<object class="GtkWindow" id="MainWindow"> <object class="GtkApplicationWindow" id="MainWindow">
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="title" translatable="yes">Estus Shots</property> <property name="title" translatable="yes">Estus Shots</property>
<property name="default_width">800</property> <property name="default_width">800</property>
@@ -50,6 +53,15 @@
<property name="margin_top">5</property> <property name="margin_top">5</property>
<property name="margin_bottom">2</property> <property name="margin_bottom">2</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<child>
<object class="GtkNotebook" id="Navigation">
<property name="visible">True</property>
<property name="can_focus">True</property>
<child>
<object class="GtkBox" id="SeasonsPage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child> <child>
<object class="GtkOverlay" id="SeasonsOverlay"> <object class="GtkOverlay" id="SeasonsOverlay">
<property name="visible">True</property> <property name="visible">True</property>
@@ -120,9 +132,189 @@
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="padding">2</property> <property name="padding">2</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
<child type="tab">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Seasons</property>
</object>
<packing>
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<object class="GtkBox" id="EpisodesPage">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkOverlay" id="EpisodesOverlay">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkScrolledWindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="EpisodesTreeView">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="enable_grid_lines">horizontal</property>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
</object>
</child>
</object>
<packing>
<property name="index">-1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButtonBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="AddEpisodeButton">
<property name="label" translatable="yes">Add Episode</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property> <property name="position">1</property>
</packing> </packing>
</child> </child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child type="tab">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Episodes</property>
</object>
<packing>
<property name="position">1</property>
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<placeholder/>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Enemies go here</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="position">2</property>
</packing>
</child>
<child type="tab">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Enemies</property>
</object>
<packing>
<property name="position">2</property>
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<placeholder/>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Players go here</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="position">3</property>
</packing>
</child>
<child type="tab">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Players</property>
</object>
<packing>
<property name="position">3</property>
<property name="tab_fill">False</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child> <child>
<object class="GtkBox"> <object class="GtkBox">
<property name="visible">True</property> <property name="visible">True</property>
@@ -157,7 +349,7 @@
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">2</property> <property name="position">3</property>
</packing> </packing>
</child> </child>
</object> </object>

View File

@@ -11,7 +11,7 @@ namespace EstusShots.Server.Models
[MaxLength(50)] public string Title { get; set; } = default!; [MaxLength(50)] public string Title { get; set; } = default!;
public DateTime DateTime { get; set; } public DateTime Date { get; set; }
public DateTime Start { get; set; } public DateTime Start { get; set; }

View File

@@ -30,6 +30,7 @@ namespace EstusShots.Server.Services
{ {
var episodes = await _context.Episodes var episodes = await _context.Episodes
.Where(x => x.SeasonId == parameter.SeasonId) .Where(x => x.SeasonId == parameter.SeasonId)
.Include(x => x.Season)
.ToListAsync(); .ToListAsync();
var dtos = _mapper.Map<List<Dto.Episode>>(episodes); var dtos = _mapper.Map<List<Dto.Episode>>(episodes);
_logger.LogInformation($"{dtos.Count} episodes loaded for season '{parameter.SeasonId}'"); _logger.LogInformation($"{dtos.Count} episodes loaded for season '{parameter.SeasonId}'");
@@ -38,7 +39,9 @@ namespace EstusShots.Server.Services
public async Task<ApiResponse<GetEpisodeResponse>> GetEpisode(GetEpisodeParameter parameter) public async Task<ApiResponse<GetEpisodeResponse>> GetEpisode(GetEpisodeParameter parameter)
{ {
var episode = await _context.Seasons.FindAsync(parameter.EpisodeId); var episode = await _context.Episodes
.Include(x => x.Season)
.FirstOrDefaultAsync(x => x.EpisodeId == parameter.EpisodeId);
if (episode == null) if (episode == null)
{ {
_logger.LogWarning($"Episode with ID {parameter.EpisodeId} was not found in database"); _logger.LogWarning($"Episode with ID {parameter.EpisodeId} was not found in database");

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Text.Json.Serialization;
namespace EstusShots.Shared.Dto namespace EstusShots.Shared.Dto
{ {
@@ -10,7 +11,7 @@ namespace EstusShots.Shared.Dto
public string Title { get; set; } public string Title { get; set; }
public DateTime DateTime { get; set; } public DateTime Date { get; set; }
public DateTime Start { get; set; } public DateTime Start { get; set; }
@@ -20,6 +21,6 @@ namespace EstusShots.Shared.Dto
public Season Season { get; set; } public Season Season { get; set; }
public string Displayname => $"S{Season.Number:D2}E{Number:D2}"; public string DisplayName => $"S{Season?.Number ?? 0:D2}E{Number:D2}";
} }
} }