From 353ef8c5db8b6ecdf6d1dc86e904680f070b484b Mon Sep 17 00:00:00 2001 From: Chris Pulman Date: Sat, 26 Aug 2023 23:25:18 +0100 Subject: [PATCH] Update ReadMe and Licence --- LICENSE | 8 ++ README.md | 221 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 228 insertions(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 88ac044..5d94e67 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,13 @@ MIT License +A large portion of this code is based on the work of the following people: +Copyright (c) 2020 Patrik Svensson, Phil Scott, Nils Andresen + +The original code can be found at https://github.com/spectreconsole/spectre.console + +The derivitive work is as follows: +Added support for Reactive Extensions (Rx) with a scheduler to make things work as expected. + Copyright (c) 2023 Chris Pulman Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/README.md b/README.md index 3e995a2..be1d2c9 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,221 @@ +![Alt](https://repobeats.axiom.co/api/embed/a6232388fa6d7a8d647834b31d9d87a88d046ef9.svg "Repobeats analytics image") + # Spectre.Console.Rx -A reactive extension of Spectre.Console Live, Progress, and Status +A reactive version of Spectre.Console Live, Progress, and Status + +visit https://spectreconsole.net/ for more information on Spectre.Console +visit https://github.com/spectreconsole/spectre.console for the original source code of Spectre.Console + +## Usage + +An example of using Spectre.Console.Rx to animate some status, a table and a progress bar. + +```csharp +using System.Reactive.Concurrency; +using System.Reactive.Linq; +using ProgressDemo; +using Spectre.Console.Rx; + +// NOTE: This code executes synchronously, only one instance of the console context can be executing at a time. +// This is because the console context is not thread safe. +// Remember to call IsFinished() on the console context when you're done with it. +var table = new Table().LeftAligned(); + +AnsiConsoleRx + .Status( + "[yellow]Initializing warp drive[/]", + p => p.AutoRefresh(true).Spinner(Spinner.Known.Default)) + .ObserveOn(AnsiConsoleRx.Scheduler) + .Subscribe( + ctx => ctx.Schedule(async scheduler => + { + // Initialize + await scheduler.Sleep(TimeSpan.FromMilliseconds(3000)); + WriteLogMessage("Starting gravimetric field displacement manifold"); + await scheduler.Sleep(TimeSpan.FromMilliseconds(1000)); + WriteLogMessage("Warming up deuterium chamber"); + await scheduler.Sleep(TimeSpan.FromMilliseconds(2000)); + WriteLogMessage("Generating antideuterium"); + + // Warp nacelles + await scheduler.Sleep(TimeSpan.FromMilliseconds(3000)); + ctx.Spinner(Spinner.Known.BouncingBar); + ctx.Status("[bold blue]Unfolding warp nacelles[/]"); + WriteLogMessage("Unfolding left warp nacelle"); + await scheduler.Sleep(TimeSpan.FromMilliseconds(2000)); + WriteLogMessage("Left warp nacelle [green]online[/]"); + WriteLogMessage("Unfolding right warp nacelle"); + await scheduler.Sleep(TimeSpan.FromMilliseconds(1000)); + WriteLogMessage("Right warp nacelle [green]online[/]"); + + // Warp bubble + await scheduler.Sleep(TimeSpan.FromMilliseconds(3000)); + ctx.Spinner(Spinner.Known.Star2); + ctx.Status("[bold blue]Generating warp bubble[/]"); + await scheduler.Sleep(TimeSpan.FromMilliseconds(3000)); + ctx.Spinner(Spinner.Known.Star); + ctx.Status("[bold blue]Stabilizing warp bubble[/]"); + + // Safety + ctx.Spinner(Spinner.Known.Monkey); + ctx.Status("[bold blue]Performing safety checks[/]"); + WriteLogMessage("Enabling interior dampening"); + await scheduler.Sleep(TimeSpan.FromMilliseconds(2000)); + WriteLogMessage("Interior dampening [green]enabled[/]"); + + // Warp! + await scheduler.Sleep(TimeSpan.FromMilliseconds(3000)); + ctx.Spinner(Spinner.Known.Moon); + WriteLogMessage("Preparing for warp"); + await scheduler.Sleep(TimeSpan.FromMilliseconds(1000)); + for (var warp = 1; warp < 10; warp++) + { + ctx.Status($"[bold blue]Warp {warp}[/]"); + await scheduler.Sleep(TimeSpan.FromMilliseconds(500)); + } + + // Done + WriteLogMessage("[bold green]Crusing at Warp 9[/]"); + + // Finish animation and allow next animation to start + ctx.IsFinished(); + })); + +// Animate +AnsiConsoleRx.Live(table, ld => ld.AutoClear(false).Overflow(VerticalOverflow.Ellipsis).Cropping(VerticalOverflowCropping.Top)) + .ObserveOn(AnsiConsoleRx.Scheduler) + .Subscribe( + ctx => + + // Columns + ctx.Update(230, () => table.AddColumn("Release date")) + .Update(230, () => table.AddColumn("Title")) + .Update(230, () => table.AddColumn("Budget")) + .Update(230, () => table.AddColumn("Opening Weekend")) + .Update(230, () => table.AddColumn("Box office")) + + // Rows + .Update(70, () => table.AddRow("May 25, 1977", "[yellow]Star Wars[/] [grey]Ep.[/] [u]IV[/]", "$11,000,000", "$1,554,475", "$775,398,007")) + .Update(70, () => table.AddRow("May 21, 1980", "[yellow]Star Wars[/] [grey]Ep.[/] [u]V[/]", "$18,000,000", "$4,910,483", "$547,969,004")) + .Update(70, () => table.AddRow("May 25, 1983", "[yellow]Star Wars[/] [grey]Ep.[/] [u]VI[/]", "$32,500,000", "$23,019,618", "$475,106,177")) + .Update(70, () => table.AddRow("May 19, 1999", "[yellow]Star Wars[/] [grey]Ep.[/] [u]I[/]", "$115,000,000", "$64,810,870", "$1,027,044,677")) + .Update(70, () => table.AddRow("May 16, 2002", "[yellow]Star Wars[/] [grey]Ep.[/] [u]II[/]", "$115,000,000", "$80,027,814", "$649,436,358")) + .Update(70, () => table.AddRow("May 19, 2005", "[yellow]Star Wars[/] [grey]Ep.[/] [u]III[/]", "$113,000,000", "$108,435,841", "$850,035,635")) + .Update(70, () => table.AddRow("Dec 18, 2015", "[yellow]Star Wars[/] [grey]Ep.[/] [u]VII[/]", "$245,000,000", "$247,966,675", "$2,068,223,624")) + .Update(70, () => table.AddRow("Dec 15, 2017", "[yellow]Star Wars[/] [grey]Ep.[/] [u]VIII[/]", "$317,000,000", "$220,009,584", "$1,333,539,889")) + .Update(70, () => table.AddRow("Dec 20, 2019", "[yellow]Star Wars[/] [grey]Ep.[/] [u]IX[/]", "$245,000,000", "$177,383,864", "$1,074,114,248")) + + // Column footer + .Update(230, () => table.Columns[2].Footer("$1,633,000,000")) + .Update(230, () => table.Columns[3].Footer("$928,119,224")) + .Update(400, () => table.Columns[4].Footer("$10,318,030,576")) + + // Column alignment + .Update(230, () => table.Columns[2].RightAligned()) + .Update(230, () => table.Columns[3].RightAligned()) + .Update(400, () => table.Columns[4].RightAligned()) + + // Column titles + .Update(70, () => table.Columns[0].Header("[bold]Release date[/]")) + .Update(70, () => table.Columns[1].Header("[bold]Title[/]")) + .Update(70, () => table.Columns[2].Header("[red bold]Budget[/]")) + .Update(70, () => table.Columns[3].Header("[green bold]Opening Weekend[/]")) + .Update(400, () => table.Columns[4].Header("[blue bold]Box office[/]")) + + // Footers + .Update(70, () => table.Columns[2].Footer("[red bold]$1,633,000,000[/]")) + .Update(70, () => table.Columns[3].Footer("[green bold]$928,119,224[/]")) + .Update(400, () => table.Columns[4].Footer("[blue bold]$10,318,030,576[/]")) + + // Title + .Update(500, () => table.Title("Star Wars Movies")) + .Update(400, () => table.Title("[[ [yellow]Star Wars Movies[/] ]]")) + + // Borders + .Update(230, () => table.BorderColor(Color.Yellow)) + .Update(230, () => table.MinimalBorder()) + .Update(230, () => table.SimpleBorder()) + .Update(230, () => table.SimpleHeavyBorder()) + + // Caption + .Update(400, () => table.Caption("[[ [blue]THE END[/] ]]")) + + // Finish animation and allow next animation to start + .IsFinished()); + +AnsiConsole.MarkupLine("[yellow]Initializing warp drive[/]..."); + +// Show progress +AnsiConsoleRx.Progress(p => + p.AutoClear(false) + .Columns(new ProgressColumn[] + { + new TaskDescriptionColumn(), // Task description + new ProgressBarColumn(), // Progress bar + new PercentageColumn(), // Percentage + new RemainingTimeColumn(), // Remaining time + new SpinnerColumn(), // Spinner + })) + .ObserveOn(AnsiConsoleRx.Scheduler) + .Subscribe(async ctx => + { + var random = new Random(DateTime.Now.Millisecond); + + // Create some tasks + var tasks = CreateTasks(ctx, random); + var warpTask = ctx.AddTask("Going to warp", autoStart: false).IsIndeterminate(); + + await ctx.Schedule( + TimeSpan.FromMilliseconds(100), + () => ctx.IsFinished, + () => + { + // Increment progress + foreach (var (task, increment) in tasks) + { + task.Increment(random.NextDouble() * increment); + } + + // Write some random things to the terminal + if (random.NextDouble() < 0.1) + { + GenerateLogMessage(); + } + }); + + // Now start the "warp" task + warpTask.StartTask().IsIndeterminate(false); + await ctx.Schedule( + TimeSpan.FromMilliseconds(100), + () => warpTask.Increment(12 * random.NextDouble())); + + // Done + await ctx.Schedule(_ => WriteLogMessage("[green]Done![/]")); + + // Progress is finished and will automatically exit + }); + +WriteLogMessage("[red]Press ctrl+c to exit[/]"); + +static List<(ProgressTask Task, int Delay)> CreateTasks(ProgressContext context, Random random) +{ + var tasks = new List<(ProgressTask, int)>(); + while (tasks.Count < 5) + { + if (DescriptionGenerator.TryGenerate(out var name)) + { + tasks.Add((context.AddTask(name), random.Next(2, 10))); + } + } + + return tasks; +} + +static void GenerateLogMessage() => AnsiConsole.MarkupLine( + "[grey]LOG:[/] " + + DescriptionGenerator.Generate() + + "[grey]...[/]"); + +static void WriteLogMessage(string message) => AnsiConsole.MarkupLine($"[grey]LOG:[/] {message}[grey]...[/]"); + +```