mirror of
https://github.com/FalloutCollaborationProject/FCP-Mod-Updater.git
synced 2026-07-27 17:04:05 -07:00
Version 1.0
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,174 @@
|
||||
using FCPModUpdater.Models;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace FCPModUpdater.UI;
|
||||
|
||||
public static class ModTableRenderer
|
||||
{
|
||||
public static void RenderModTable(IReadOnlyList<InstalledMod> mods, int? rateLimit = null,
|
||||
DateTimeOffset? rateLimitReset = null)
|
||||
{
|
||||
if (mods.Count == 0)
|
||||
{
|
||||
AnsiConsole.MarkupLine("[yellow]No FCP mods found in the specified directory.[/]");
|
||||
return;
|
||||
}
|
||||
|
||||
var table = new Table()
|
||||
.Border(TableBorder.Rounded)
|
||||
.BorderColor(Color.Grey)
|
||||
.Title("[bold blue]FCP Mod Status[/]")
|
||||
.AddColumn(new TableColumn("[bold]Mod Name[/]").NoWrap())
|
||||
.AddColumn(new TableColumn("[bold]Source[/]").Centered())
|
||||
.AddColumn(new TableColumn("[bold]Branch[/]").Centered())
|
||||
.AddColumn(new TableColumn("[bold]Commit[/]").Centered())
|
||||
.AddColumn(new TableColumn("[bold]Status[/]").Centered());
|
||||
|
||||
foreach (var mod in mods)
|
||||
{
|
||||
table.AddRow(
|
||||
FormatModName(mod),
|
||||
FormatSource(mod.Source),
|
||||
FormatBranch(mod.Branch),
|
||||
FormatCommit(mod.CurrentCommit),
|
||||
FormatStatus(mod)
|
||||
);
|
||||
}
|
||||
|
||||
AnsiConsole.Write(table);
|
||||
|
||||
// Status summary
|
||||
var gitMods = mods.Where(m => m.Source == ModSource.Git).ToList();
|
||||
var upToDate = gitMods.Count(m => m.Status == ModStatus.UpToDate);
|
||||
var behind = gitMods.Count(m => m.Status == ModStatus.Behind);
|
||||
var localChanges = gitMods.Count(m => m.Status == ModStatus.LocalChanges);
|
||||
var nonGit = mods.Count(m => m.Source != ModSource.Git);
|
||||
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.MarkupLine(
|
||||
$"[grey]Summary:[/] [green]{upToDate} up to date[/] | [yellow]{behind} updates available[/] | [cyan]{localChanges} with local changes[/] | [grey]{nonGit} non-git[/]");
|
||||
|
||||
if (rateLimit.HasValue)
|
||||
{
|
||||
var resetTime = rateLimitReset.HasValue
|
||||
? $" (resets {rateLimitReset.Value.ToLocalTime():HH:mm})"
|
||||
: "";
|
||||
var color = rateLimit.Value < 10 ? "yellow" : "grey";
|
||||
AnsiConsole.MarkupLine($"[{color}]GitHub API: {rateLimit.Value} requests remaining{resetTime}[/]");
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatModName(InstalledMod mod)
|
||||
{
|
||||
var name = mod.Name;
|
||||
if (mod.HasLocalChanges)
|
||||
{
|
||||
name += " [yellow]*[/]";
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
private static string FormatSource(ModSource source)
|
||||
{
|
||||
return source switch
|
||||
{
|
||||
ModSource.Git => "[green]Git[/]",
|
||||
ModSource.Local => "[grey]Local[/]",
|
||||
ModSource.Workshop => "[blue]Workshop[/]",
|
||||
_ => "[grey]?[/]"
|
||||
};
|
||||
}
|
||||
|
||||
private static string FormatBranch(string? branch)
|
||||
{
|
||||
if (string.IsNullOrEmpty(branch))
|
||||
return "[grey]-[/]";
|
||||
|
||||
return branch == "main" || branch == "master"
|
||||
? $"[green]{branch}[/]"
|
||||
: $"[yellow]{branch}[/]";
|
||||
}
|
||||
|
||||
private static string FormatCommit(GitCommitInfo? commit)
|
||||
{
|
||||
if (commit == null)
|
||||
return "[grey]-[/]";
|
||||
|
||||
return $"[grey]{commit.ShortHash}[/]";
|
||||
}
|
||||
|
||||
private static string FormatStatus(InstalledMod mod)
|
||||
{
|
||||
return mod.Status switch
|
||||
{
|
||||
ModStatus.UpToDate => "[green]✓ Up to date[/]",
|
||||
ModStatus.Behind => $"[yellow]↓ {mod.CommitsBehind} behind[/]",
|
||||
ModStatus.Ahead => $"[cyan]↑ {mod.CommitsAhead} ahead[/]",
|
||||
ModStatus.Diverged => $"[red]⇅ Diverged ({mod.CommitsBehind}↓ {mod.CommitsAhead}↑)[/]",
|
||||
ModStatus.LocalChanges => "[cyan]~ Modified[/]",
|
||||
ModStatus.NonGit => "[grey]— Not Git[/]",
|
||||
ModStatus.Error => $"[red]✗ Error[/]",
|
||||
ModStatus.Unknown => "[grey]? Unknown[/]",
|
||||
_ => "[grey]?[/]"
|
||||
};
|
||||
}
|
||||
|
||||
public static void RenderUpdateSummary(IReadOnlyList<(string Name, bool Success, string? Error)> results)
|
||||
{
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
var table = new Table()
|
||||
.Border(TableBorder.Rounded)
|
||||
.BorderColor(Color.Grey)
|
||||
.Title("[bold]Update Results[/]")
|
||||
.AddColumn("[bold]Mod[/]")
|
||||
.AddColumn("[bold]Result[/]");
|
||||
|
||||
foreach (var (name, success, error) in results)
|
||||
{
|
||||
var result = success
|
||||
? "[green]✓ Updated[/]"
|
||||
: $"[red]✗ Failed: {Markup.Escape(error ?? "Unknown error")}[/]";
|
||||
|
||||
table.AddRow(name, result);
|
||||
}
|
||||
|
||||
AnsiConsole.Write(table);
|
||||
|
||||
var successCount = results.Count(r => r.Success);
|
||||
var failCount = results.Count(r => !r.Success);
|
||||
|
||||
AnsiConsole.WriteLine();
|
||||
if (failCount == 0)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Successfully updated {successCount} mod(s).[/]");
|
||||
}
|
||||
else
|
||||
{
|
||||
AnsiConsole.MarkupLine(
|
||||
$"[yellow]Updated {successCount} mod(s), {failCount} failed.[/]");
|
||||
}
|
||||
}
|
||||
|
||||
public static void RenderIncomingCommits(InstalledMod mod, IReadOnlyList<GitCommitInfo> commits)
|
||||
{
|
||||
if (commits.Count == 0)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[grey]No incoming commits for {mod.Name}[/]");
|
||||
return;
|
||||
}
|
||||
|
||||
var tree = new Tree($"[bold]{mod.Name}[/] [grey]({commits.Count} incoming commits)[/]");
|
||||
|
||||
foreach (var commit in commits)
|
||||
{
|
||||
var dateStr = commit.Date.ToLocalTime().ToString("yyyy-MM-dd HH:mm");
|
||||
tree.AddNode(
|
||||
$"[yellow]{commit.ShortHash}[/] [grey]{dateStr}[/] {Markup.Escape(commit.Message)} [grey]— {Markup.Escape(commit.Author)}[/]");
|
||||
}
|
||||
|
||||
AnsiConsole.Write(tree);
|
||||
AnsiConsole.WriteLine();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
using Spectre.Console;
|
||||
|
||||
namespace FCPModUpdater.UI;
|
||||
|
||||
public static class ProgressReporter
|
||||
{
|
||||
public static async Task<T> WithStatusAsync<T>(string status, Func<Task<T>> action)
|
||||
{
|
||||
return await AnsiConsole.Status()
|
||||
.Spinner(Spinner.Known.Dots)
|
||||
.SpinnerStyle(Style.Parse("blue"))
|
||||
.StartAsync(status, async _ => await action());
|
||||
}
|
||||
|
||||
public static async Task WithStatusAsync(string status, Func<Task> action)
|
||||
{
|
||||
await AnsiConsole.Status()
|
||||
.Spinner(Spinner.Known.Dots)
|
||||
.SpinnerStyle(Style.Parse("blue"))
|
||||
.StartAsync(status, async _ => await action());
|
||||
}
|
||||
|
||||
public static async Task WithProgressAsync(
|
||||
string description,
|
||||
IEnumerable<(string Name, Func<ProgressTask, Task> Action)> tasks)
|
||||
{
|
||||
await AnsiConsole.Progress()
|
||||
.Columns(
|
||||
new TaskDescriptionColumn(),
|
||||
new ProgressBarColumn(),
|
||||
new PercentageColumn(),
|
||||
new SpinnerColumn())
|
||||
.StartAsync(async ctx =>
|
||||
{
|
||||
var taskList = tasks.ToList();
|
||||
var progressTasks = taskList
|
||||
.Select(t => (Task: ctx.AddTask(t.Name), t.Action))
|
||||
.ToList();
|
||||
|
||||
foreach ((ProgressTask task, var action) in progressTasks)
|
||||
{
|
||||
await action(task);
|
||||
task.Value = 100;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static async Task<IReadOnlyList<(string Name, bool Success, string? Error)>> WithBatchProgressAsync<T>(
|
||||
string description,
|
||||
IReadOnlyList<T> items,
|
||||
Func<T, string> nameSelector,
|
||||
Func<T, IProgress<double>, Task<(bool Success, string? Error)>> action)
|
||||
{
|
||||
var results = new List<(string Name, bool Success, string? Error)>();
|
||||
|
||||
await AnsiConsole.Progress()
|
||||
.Columns(
|
||||
new TaskDescriptionColumn(),
|
||||
new ProgressBarColumn(),
|
||||
new PercentageColumn(),
|
||||
new SpinnerColumn())
|
||||
.StartAsync(async ctx =>
|
||||
{
|
||||
var overallTask = ctx.AddTask($"[bold]{description}[/]", maxValue: items.Count);
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
var name = nameSelector(item);
|
||||
var itemTask = ctx.AddTask($" {name}");
|
||||
|
||||
var progress = new Progress<double>(p => itemTask.Value = p);
|
||||
|
||||
try
|
||||
{
|
||||
var (success, error) = await action(item, progress);
|
||||
results.Add((name, success, error));
|
||||
itemTask.Value = 100;
|
||||
|
||||
if (!success)
|
||||
{
|
||||
itemTask.Description = $" [red]{name}[/]";
|
||||
}
|
||||
else
|
||||
{
|
||||
itemTask.Description = $" [green]{name}[/]";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
results.Add((name, false, ex.Message));
|
||||
itemTask.Value = 100;
|
||||
itemTask.Description = $" [red]{name}[/]";
|
||||
}
|
||||
|
||||
overallTask.Increment(1);
|
||||
}
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user