Initial commit

This commit is contained in:
github-classroom[bot] 2025-04-24 07:02:41 +00:00 committed by GitHub
commit 1be1863b20
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
50 changed files with 6449 additions and 0 deletions

View file

@ -0,0 +1,18 @@
using MeetTheTeacher.Model;
namespace MeetTheTeacher.Export;
/// <summary>
/// Allows to export data of <see cref="ICsvRepresentable"/> type to a CSV file
/// </summary>
/// <inheritdoc cref="FileExporterBase" />
/// <inheritdoc cref="IDataExporter" />
public sealed class CsvDataExporter : FileExporterBase, IDataExporter
{
public void Export(IEnumerable<ICsvRepresentable> items, string? fileName)
{
// TODO
}
protected override string FileExtension => ".csv";
}

View file

@ -0,0 +1,12 @@
using MeetTheTeacher.Model;
namespace MeetTheTeacher.Export;
/// <summary>
/// Default implementation of <see cref="IExporterProvider"/> which returns the following exporters:
/// <see cref="CsvDataExporter"/> and <see cref="HtmlDataExporter"/>
/// </summary>
public sealed class DefaultExporterProvider : IExporterProvider
{
public IDataExporter GetExporter(ExportFormat exportFormat) => null!; // TODO
}

View file

@ -0,0 +1,37 @@
using MeetTheTeacher.Model;
using MeetTheTeacher.Model.Comparison;
namespace MeetTheTeacher.Export;
/// <summary>
/// A utility class which allows to export data in a given format and order
/// </summary>
public sealed class Exporter
{
// TODO
//private readonly IComparer<Teacher> _comparer;
private readonly IDataExporter _exporter;
/// <summary>
/// Creates a new instance of <see cref="Exporter" /> initialized with the provided export options
/// </summary>
/// <param name="sortOrder">Order in which data should be sorted before export</param>
/// <param name="exportFormat">Desired export format</param>
/// <param name="exporterProvider">Export provider which supports exporting in the desired format</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown if you manage to pass an unexpected <see cref="SortOrder" /> value</exception>
public Exporter(SortOrder sortOrder, ExportFormat exportFormat, IExporterProvider exporterProvider)
{
_exporter = exporterProvider.GetExporter(exportFormat);
//_comparer = ... // TODO
}
/// <summary>
/// First sorts the provided data based on the previous settings, then performs the actual export to a file
/// </summary>
/// <param name="fileName">Name of the file to export to; without extension</param>
/// <param name="teachers">Data to export - has to be list due to multiple enumeration</param>
public void Export(string fileName, List<Teacher> teachers)
{
// TODO
}
}

View file

@ -0,0 +1,36 @@
namespace MeetTheTeacher.Export;
/// <summary>
/// Base class for exporters which write data to a file
/// </summary>
public abstract class FileExporterBase
{
/// <summary>
/// File extension of the exported file including the dot
/// </summary>
protected abstract string FileExtension { get; }
/// <summary>
/// Exports the given content to a file with the given name.
/// If the file already exists, it will be overwritten.
/// </summary>
/// <param name="fileName">Name of the file to export without the extension</param>
/// <param name="content">Content to write into the file</param>
protected void Export(string fileName, string content)
{
// TODO (don't forget to delete)
}
/// <summary>
/// Joins several lines into one text, separated by line breaks
/// </summary>
/// <param name="lines">Lines to merge</param>
/// <returns>A text containing all lines</returns>
protected static string LinesToText(IEnumerable<string> lines) => null!; // TODO (remember to use proper line break)
private string GetFilePath(string fileName)
{
// TODO (remember to use the current directory)
return null!;
}
}

View file

@ -0,0 +1,27 @@
using MeetTheTeacher.Model;
namespace MeetTheTeacher.Export;
/// <summary>
/// Allows to export data of <see cref="ICsvRepresentable"/> type to a file in HTML table syntax.
/// Be careful: does not create valid HTML page, just a fragment!
/// </summary>
/// <inheritdoc cref="FileExporterBase" />
/// <inheritdoc cref="IDataExporter" />
public sealed class HtmlDataExporter : FileExporterBase, IDataExporter
{
public void Export(IEnumerable<ICsvRepresentable> items, string? fileName)
{
// TODO (use a raw string literal with interpolation!)
}
private static string CreateTableRow(IEnumerable<string> values, bool isHeader = false)
{
// TODO
return null!;
}
private static string WrapInTag(string content, string tag) => $"<{tag}>{content}</{tag}>";
protected override string FileExtension => ".html";
}

View file

@ -0,0 +1,17 @@
using MeetTheTeacher.Model;
namespace MeetTheTeacher.Export;
/// <summary>
/// Allows to export data of <see cref="ICsvRepresentable"/> type
/// </summary>
public interface IDataExporter
{
/// <summary>
/// Exports the given data.
/// File based exporters will use the provided file name as the name of the file they export.
/// </summary>
/// <param name="data">Data to export</param>
/// <param name="fileName">File name to be used; without the extension</param>
void Export(IEnumerable<ICsvRepresentable> data, string? fileName);
}

View file

@ -0,0 +1,16 @@
using MeetTheTeacher.Model;
namespace MeetTheTeacher.Export;
/// <summary>
/// Provides an <see cref="IDataExporter"/> for the given <see cref="ExportFormat"/>
/// </summary>
public interface IExporterProvider
{
/// <summary>
/// Returns an <see cref="IDataExporter"/> for the given <see cref="ExportFormat"/>
/// </summary>
/// <param name="exportFormat">Requested export format</param>
/// <returns><see cref="IDataExporter"/> which supports exporting in the desired format</returns>
IDataExporter GetExporter(ExportFormat exportFormat);
}

View file

@ -0,0 +1,51 @@
using MeetTheTeacher.Model;
using Spectre.Console;
namespace MeetTheTeacher.Export;
/// <summary>
/// Allows to export data of <see cref="ICsvRepresentable" /> type to a <see cref="Table" /> in-memory data structure
/// </summary>
/// <inheritdoc cref="IDataExporter" />
public sealed class SpectreTableExporter : IDataExporter
{
/// <summary>
/// Creates a new instance of the exporter with an empty <see cref="Table" />
/// </summary>
public SpectreTableExporter()
{
Table = new Table();
}
/// <summary>
/// Gets the table containing the exported data
/// </summary>
public Table Table { get; private set; }
/// <summary>
/// Exports the given data to a <see cref="Table" />.
/// Does not write a file.
/// </summary>
/// <param name="data">Data to export</param>
/// <param name="fileName">Unused</param>
public void Export(IEnumerable<ICsvRepresentable> data, string? fileName)
{
var dataRows = new List<CsvData>();
foreach (var csvRepresentable in data)
{
dataRows.Add(csvRepresentable.ToCsvData());
}
var table = new Table();
// TODO
Table = table;
}
private static IEnumerable<string> FindMaxHeaderColumns(IEnumerable<CsvData> rows)
{
// TODO
return null!;
}
}

View file

@ -0,0 +1,16 @@
using MeetTheTeacher.Model;
namespace MeetTheTeacher.Import;
/// <summary>
/// Allows to import data of <see cref="Teacher" /> type
/// </summary>
public interface ITeacherDataImporter
{
/// <summary>
/// Loads the data from various sources.
/// Mind that instances of <see cref="Teacher"/> and <see cref="TeacherWithBusinessCard"/> can be returned.
/// </summary>
/// <returns>A collection of <see cref="Teacher"/> data</returns>
IEnumerable<Teacher> LoadTeacherData();
}

View file

@ -0,0 +1,54 @@
using MeetTheTeacher.Model;
namespace MeetTheTeacher.Import;
/// <summary>
/// Imports <see cref="Teacher" /> data from CSV files
/// </summary>
/// <inheritdoc cref="ITeacherDataImporter" />
public sealed class TeacherDataCsvImporter : ITeacherDataImporter
{
private readonly string _businessCardDataFilePath;
private readonly string _generalDataFilePath;
/// <summary>
/// Creates a new instance of <see cref="TeacherDataCsvImporter" /> configured to read from the given files
/// </summary>
/// <param name="generalDataFilePath">Path to file containing general <see cref="Teacher" /> data for all teachers</param>
/// <param name="businessCardDataFilePath">
/// Path to file containing additional data for <see cref="TeacherWithBusinessCard" /> teachers
/// </param>
public TeacherDataCsvImporter(string generalDataFilePath, string businessCardDataFilePath)
{
_generalDataFilePath = generalDataFilePath;
_businessCardDataFilePath = businessCardDataFilePath;
}
public IEnumerable<Teacher> LoadTeacherData()
{
// TODO
return null!;
}
private static Dictionary<string, Teacher> CombineData(Dictionary<string, Teacher> generalData,
IEnumerable<(string name, int id)> businessCardData)
{
// Note: this method is designed specifically to force you to deal with the dictionary and
// different teacher classes as value. It could be done in different ways as well, of course.
// TODO
return null!;
}
private IEnumerable<(string name, int id)>? ReadTeacherBusinessCardData()
{
// TODO - CSV import (mind the return type: ValueTuple!)
return null!;
}
private Dictionary<string, Teacher>? ReadTeacherData()
{
// TODO - CSV import
return null!;
}
}

View file

@ -0,0 +1,57 @@
using MeetTheTeacher.Model;
namespace MeetTheTeacher.Import;
/// <summary>
/// Fake implementation of <see cref="ITeacherDataImporter" /> which returns a fixed set of data.
/// </summary>
/// <inheritdoc cref="ITeacherDataImporter" />
public sealed class TeacherDataFakeImporter : ITeacherDataImporter
{
public IEnumerable<Teacher> LoadTeacherData() =>
new List<Teacher>
{
new("John Doe")
{
ConsultingHour = new TimeFrame(new TimeOnly(8, 0), new TimeOnly(8, 50)),
ConsultingHourUnit = SchoolUnit.UE01,
ConsultingHourWeekDay = DayOfWeek.Monday,
Room = "A101"
},
new("Jane Smith")
{
ConsultingHour = new TimeFrame(new TimeOnly(14, 35), new TimeOnly(15, 25)),
ConsultingHourUnit = SchoolUnit.UE08,
ConsultingHourWeekDay = DayOfWeek.Tuesday,
Room = "A102"
},
new("Alice Johnson")
{
ConsultingHour = new TimeFrame(new TimeOnly(10, 0), new TimeOnly(10, 50)),
ConsultingHourUnit = SchoolUnit.UE03,
ConsultingHourWeekDay = DayOfWeek.Wednesday,
Room = "A102"
},
new("Bob Brown")
{
ConsultingHour = new TimeFrame(new TimeOnly(10, 55), new TimeOnly(11, 45)),
ConsultingHourUnit = SchoolUnit.UE04,
ConsultingHourWeekDay = DayOfWeek.Friday,
Room = "A104"
},
new TeacherWithBusinessCard("Emily Davis", 1001)
{
ConsultingHour = new TimeFrame(new TimeOnly(12, 40), new TimeOnly(13, 35)),
ConsultingHourUnit = SchoolUnit.UE06,
ConsultingHourWeekDay = DayOfWeek.Thursday,
Room = "A101"
},
new TeacherWithBusinessCard("Tom Wilson", 1002)
{
ConsultingHour = new TimeFrame(new TimeOnly(8, 50), new TimeOnly(9, 45)),
ConsultingHourUnit = SchoolUnit.UE02,
ConsultingHourWeekDay = DayOfWeek.Monday,
Room = "A102"
}
};
}

View file

@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Spectre.Console" Version="0.49.2-preview.0.77" />
<PackageReference Include="Spectre.Console.ImageSharp" Version="0.49.2-preview.0.77" />
</ItemGroup>
<ItemGroup>
<Content Include="..\Data\*.csv" Link="Data\%(Filename)%(Extension)">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<None Update="happy-dog.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View file

@ -0,0 +1,22 @@
namespace MeetTheTeacher.Model.Comparison;
/// <summary>
/// Compares <see cref="Teacher" /> instances by their consulting hour, sorting in ascending order.
/// First compares by <see cref="DayOfWeek" />, then by <see cref="SchoolUnit" />.
/// </summary>
/// <inheritdoc cref="ComparerBase" />
/// <inheritdoc cref="IComparer{Teacher}" />
public sealed class ByHourComparer : ComparerBase, IComparer<Teacher>
{
/// <summary>
/// Creates a new instance of <see cref="ByHourComparer" /> configured to sort in ascending or descending order
/// </summary>
/// <param name="ascending">Flag indicating if items should be sorted in ascending order; descending if false</param>
public ByHourComparer(bool ascending) : base(ascending) { }
public int Compare(Teacher? x, Teacher? y)
{
// TODO
return -1;
}
}

View file

@ -0,0 +1,22 @@
namespace MeetTheTeacher.Model.Comparison;
/// <summary>
/// A comparer which allows sorting <see cref="Teacher" /> instances by their name in either ascending or descending
/// order
/// </summary>
/// <inheritdoc cref="ComparerBase" />
/// <inheritdoc cref="IComparer{Teacher}"/>
public sealed class ByNameComparer : ComparerBase, IComparer<Teacher>
{
/// <summary>
/// Creates a new instance of <see cref="ByNameComparer" /> configured to sort in ascending or descending order
/// </summary>
/// <param name="ascending">Flag indicating if items should be sorted in ascending order; descending if false</param>
public ByNameComparer(bool ascending) : base(ascending) { }
public int Compare(Teacher? x, Teacher? y)
{
// TODO
return -1;
}
}

View file

@ -0,0 +1,14 @@
namespace MeetTheTeacher.Model.Comparison;
/// <summary>
/// Compares <see cref="Teacher"/> instances by their room, sorting in ascending order
/// </summary>
/// <inheritdoc cref="IComparer{Teacher}"/>
public sealed class ByRoomComparer : IComparer<Teacher>
{
public int Compare(Teacher? x, Teacher? y)
{
// TODO
return -1;
}
}

View file

@ -0,0 +1,24 @@
namespace MeetTheTeacher.Model.Comparison;
/// <summary>
/// Base class for comparers which allow switching between ascending and descending mode
/// </summary>
public abstract class ComparerBase
{
/// <summary>
/// Creates a new instance of <see cref="ComparerBase" /> configured to sort in ascending or descending order
/// </summary>
/// <param name="ascending">Flag indicating if items should be sorted in ascending order; descending if false</param>
protected ComparerBase(bool ascending)
{
Ascending = ascending;
}
/// <summary>
/// Gets if the comparer is configured to sort in ascending order; descending if false
/// </summary>
protected bool Ascending { get; }
// TODO: consider moving the basic RefEquals code of CompareTo from the subclasses here
// maybe also rename this to SortOrderComparerBase or something and introduce another, common base class for all three?
}

View file

@ -0,0 +1,57 @@
namespace MeetTheTeacher.Model;
/// <summary>
/// Represents a single entity as a CSV processable object.
/// </summary>
public sealed class CsvData
{
/// <summary>
/// Separator used in CSV files
/// </summary>
public const char Separator = ';';
/// <summary>
/// Creates a new instance of <see cref="CsvData" />
/// </summary>
/// <param name="headerNames">Header column names for the entity which has its data represented by this object</param>
/// <param name="data">Data column values for the entity which has its data represented by this object; stringly typed</param>
public CsvData(IReadOnlyList<string> headerNames, IReadOnlyList<string> data)
{
HeaderNames = headerNames;
Data = data;
}
/// <summary>
/// Gets the header column names for the entity which has its data represented by this object
/// </summary>
public IReadOnlyList<string> HeaderNames { get; }
/// <summary>
/// Gets the data column values for the entity which has its data represented by this object; stringly typed
/// </summary>
public IReadOnlyList<string> Data { get; }
/// <summary>
/// Gets a CSV header based on <see cref="HeaderNames" />
/// </summary>
/// <returns>A CSV header line</returns>
public string GetHeader() => null!; // TODO
/// <summary>
/// Gets a CSV data line based on <see cref="Data" />
/// </summary>
/// <returns>A CSV data line</returns>
public string GetData() => null!; // TODO
/// <summary>
/// Allows to deconstruct the object into its header and data parts
/// </summary>
/// <param name="headerNames">Set to <see cref="HeaderNames" /></param>
/// <param name="data">Set to <see cref="Data" /></param>
public void Deconstruct(out IReadOnlyList<string> headerNames, out IReadOnlyList<string> data)
{
// TODO
headerNames = null!;
data = null!;
}
}

View file

@ -0,0 +1,13 @@
namespace MeetTheTeacher.Model;
/// <summary>
/// Allows to represent the entity in a CSV compatible way
/// </summary>
public interface ICsvRepresentable
{
/// <summary>
/// Converts the entity to a <see cref="CsvData" /> instance
/// </summary>
/// <returns>A <see cref="CsvData" /> instance representing this entity</returns>
CsvData ToCsvData();
}

View file

@ -0,0 +1,54 @@
namespace MeetTheTeacher.Model;
/// <summary>
/// Represents a teacher at a vocational college
/// </summary>
/// <inheritdoc cref="ICsvRepresentable" />
public class Teacher : ICsvRepresentable
{
/// <summary>
/// Creates a new instance of <see cref="Teacher" />.
/// The name is the only required property.
/// </summary>
/// <param name="name">Name of the teacher</param>
public Teacher(string name)
{
Name = name;
}
/// <summary>
/// Gets the name of the teacher
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the time frame in which the teacher is available for consulting
/// </summary>
public TimeFrame? ConsultingHour { get; init; }
/// <summary>
/// Gets the school unit in which the teacher is available for consulting
/// </summary>
public SchoolUnit? ConsultingHourUnit { get; init; }
/// <summary>
/// Gets the day of the week the teacher is available for consulting
/// </summary>
public DayOfWeek? ConsultingHourWeekDay { get; init; }
/// <summary>
/// Gets the room in which the teacher is available for consulting
/// </summary>
public string? Room { get; init; }
private string? ConsultingHourTime
{
get
{
// TODO
return null!;
}
}
public virtual CsvData ToCsvData() => null!; // TODO
}

View file

@ -0,0 +1,32 @@
namespace MeetTheTeacher.Model;
/// <summary>
/// Represents a teacher with a business card which means they have an id for their picture available as well
/// </summary>
/// <inheritdoc cref="Teacher" />
public sealed class TeacherWithBusinessCard : Teacher
{
private const string BaseUrl = "https://www.htl-leonding.at/media/teacher-avatar";
/// <summary>
/// Creates a new instance of <see cref="TeacherWithBusinessCard" />.
/// In addition to the name, the id of the picture is required as well.
/// </summary>
/// <param name="name">Name of the teacher</param>
/// <param name="id">Id of the teacher's picture</param>
public TeacherWithBusinessCard(string name, int id) : base(name)
{
Id = id;
}
/// <summary>
/// Gets the picture id of the teacher
/// </summary>
public int Id { get; }
public override CsvData ToCsvData()
{
// TODO (remember to maybe use the base implementation...)
return null!;
}
}

View file

@ -0,0 +1,38 @@
using System.Globalization;
namespace MeetTheTeacher.Model;
/// <summary>
/// Represents a time frame between a start and end time.
/// Start time is always before end time.
/// </summary>
/// <param name="Start">Start time</param>
/// <param name="End">End time</param>
public readonly record struct TimeFrame(TimeOnly Start, TimeOnly End)
{
private static readonly CultureInfo culture = new ("de-AT");
/// <summary>
/// Tries to parse a time frame from a string in format HH:MM-HH:MM.
/// Additional whitespace is trimmed and ignored.
/// </summary>
/// <param name="timeFrame">Timeframe string to parse</param>
/// <param name="result">Set to the parsed time frame if possible</param>
/// <returns>True if time frame could be parsed successfully; false otherwise</returns>
public static bool TryParse(string timeFrame, out TimeFrame? result)
{
// TODO
result = null;
return false;
}
/// <summary>
/// Returns a string representation of the time frame in format HH:MM-HH:MM
/// </summary>
/// <returns></returns>
public override string ToString()
{
// TODO
return null!;
}
}

View file

@ -0,0 +1,39 @@
namespace MeetTheTeacher.Model;
/// <summary>
/// Represents school units, each taking 50 minutes
/// </summary>
public enum SchoolUnit
{
UE01 = 01,
UE02 = 02,
UE03 = 03,
UE04 = 04,
UE05 = 05,
UE06 = 06,
UE07 = 07,
UE08 = 08,
UE09 = 09,
UE10 = 10
}
/// <summary>
/// Represents possible data export formats
/// </summary>
public enum ExportFormat
{
Csv,
Html
}
/// <summary>
/// Represents possible sort orders for the teacher data
/// </summary>
public enum SortOrder
{
ByNameAsc,
ByNameDesc,
ByHourAsc,
ByHourDesc,
Room
}

154
MeetTheTeacher/Program.cs Normal file
View file

@ -0,0 +1,154 @@
using System.Text;
using MeetTheTeacher.Export;
using MeetTheTeacher.Import;
using MeetTheTeacher.Model;
using Spectre.Console;
Console.OutputEncoding = Encoding.UTF8;
Clear();
AnsiConsole.Markup("[orange3]Press any key to load HTL-Leonding teacher data[/] ");
Console.ReadKey();
// data loading
bool loadRealData = AskForDataToUse();
// wrapping IEnumerable in List, because we will iterate multiple times
var data = new List<Teacher>(LoadData(loadRealData));
var tableGenerator = new SpectreTableExporter();
tableGenerator.Export(data, string.Empty);
// fake loading
Clear();
await DisplayFakeProgress();
Clear();
AnsiConsole.Write(tableGenerator.Table);
PrintStatistics(data);
// user input
WriteRule("Sort Order");
var order = AskForSortOrder();
WriteRule("Export Format");
var format = AskForExportFormat();
WriteRule("File Name");
string fileName = AnsiConsole.Ask<string>("Which [blue]file name[/] should be used?");
// export
var exporter = new Exporter(order, format, new DefaultExporterProvider());
exporter.Export(fileName, data);
// done
WriteRule(string.Empty);
AnsiConsole.Write(new FigletText("All Done!").Centered().Color(Color.Green));
DrawDog();
return;
static void Clear()
{
Console.Clear();
AnsiConsole.Write(new FigletText("Teacher Data").Centered().Color(Color.Blue));
}
static void WriteRule(string text)
{
AnsiConsole.Write(new Rule($"[blue]{text}[/]").LeftJustified());
}
static IEnumerable<Teacher> LoadData(bool realData)
{
// TODO
// ITeacherDataImporter importer ...
// files: "Data/teachers.csv" & "Data/teachers-with-business-card.csv"
return null!;
}
static Task DisplayFakeProgress()
{
const double Increase = 4.5;
const double ReducedIncrease = Increase * 0.8D;
return AnsiConsole.Progress()
.StartAsync(async ctx =>
{
// we are actually already done, but want to fake some complex operation
// to have the user appreciate the hard work our application is doing :)
var task1 = ctx.AddTask("[green]Loading Data[/]");
var task2 = ctx.AddTask("[green]Processing Data[/]");
while (!ctx.IsFinished)
{
task1.Increment(Increase);
task2.Increment(ReducedIncrease);
await Task.Delay(TimeSpan.FromMilliseconds(125));
}
});
}
static void PrintStatistics(IReadOnlyCollection<Teacher> teachers)
{
var hasConsultingHours = 0;
var hasBusinessCard = 0;
foreach (var teacher in teachers)
{
// TODO
}
double total = teachers.Count;
var cPercentage = (int) (hasConsultingHours / total * 100);
int ncPercentage = 100 - cPercentage;
var bPercentage = (int) (hasBusinessCard / total * 100);
AnsiConsole.Write(new BarChart()
.Width(60)
.Label($"[blue underline]Statistics ({total:F0} teachers)[/]")
.CenterLabel()
.AddItem("Has consulting hour", cPercentage, Color.Green)
.AddItem("Has no consulting hour", ncPercentage, Color.Red)
.AddItem("Has business card", bPercentage, Color.Purple));
}
static SortOrder AskForSortOrder()
{
var options = new Dictionary<string, SortOrder>
{
["Name Asc."] = SortOrder.ByNameAsc,
["Name Desc."] = SortOrder.ByNameDesc,
["Hour Asc."] = SortOrder.ByHourAsc,
["Hour Desc."] = SortOrder.ByHourDesc,
["Room"] = SortOrder.Room
};
string order = AnsiConsole.Prompt(new SelectionPrompt<string>()
.Title("How do you wish to [blue]sort[/] the data?")
.PageSize(3)
.MoreChoicesText("[grey](Move up and down to reveal more options)[/]")
.AddChoices(options.Keys));
AnsiConsole.MarkupLine($"Selected: [green]{order}[/]");
return options[order];
}
static ExportFormat AskForExportFormat()
{
// TODO (similar to AskForDataToUse)
return default;
}
static bool AskForDataToUse()
{
const string Real = "Real";
string dataToUse = AnsiConsole.Prompt(new SelectionPrompt<string>()
.Title("Do you want to load [blue]real[/] or [blue]sample[/] data?")
.AddChoices(Real, "Sample"));
AnsiConsole.MarkupLine($"Selected: [green]{dataToUse}[/]");
return dataToUse == Real;
}
static void DrawDog()
{
var image = new CanvasImage("happy-dog.png");
image.MaxWidth(36);
AnsiConsole.Write(image);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB