Closed webmaster442 closed 2 months ago
using Spectre.Console; namespace Browser; internal sealed class Item { public required string Name { get; init; } public required string FullPath { get; init; } public required string Icon { get; init; } } internal interface IBrowserMenuProvider { IEnumerable<Item> ProvideItems(string currentPath); bool SelectionCanExit(Item selectedItem); } internal sealed class FileSystemBrowserMenuProvider : IBrowserMenuProvider { private readonly HashSet<string> _extensionsToShow; public FileSystemBrowserMenuProvider(IEnumerable<string>? extensions = null) { if (extensions == null) _extensionsToShow = new HashSet<string>(StringComparer.OrdinalIgnoreCase); else _extensionsToShow = new HashSet<string>(extensions, StringComparer.OrdinalIgnoreCase); } public IEnumerable<Item> ProvideItems(string currentPath) { if (string.IsNullOrEmpty(currentPath)) { return GetDrives(); } return GetFileSystemItems(currentPath); } private IEnumerable<Item> GetFileSystemItems(string currentPath) { var directoryInfo = new DirectoryInfo(currentPath); yield return new Item { Name = "..", FullPath = GetPreviousPath(currentPath), Icon = "" }; foreach (var directory in directoryInfo.GetDirectories().OrderBy(x => x.Name)) { if (!directory.Attributes.HasFlag(FileAttributes.Hidden) && !directory.Attributes.HasFlag(FileAttributes.System)) { yield return CreateItem(directory); } } foreach (var file in directoryInfo.GetFiles().OrderBy(x => x.Name)) { if (!file.Attributes.HasFlag(FileAttributes.Hidden) && !file.Attributes.HasFlag(FileAttributes.System)) { if (_extensionsToShow.Count == 0) yield return CreateItem(file); else if (_extensionsToShow.Contains(file.Extension)) yield return CreateItem(file); } } } private static string GetPreviousPath(string currentPath) { var parts = currentPath.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries); return string.Join(Path.DirectorySeparatorChar, parts.Take(parts.Length - 1)); } private static Item CreateItem(FileSystemInfo item) { return new Item { FullPath = item.FullName, Name = item.Name, Icon = item is DirectoryInfo ? Emoji.Known.FileFolder : Emoji.Known.Memo }; } private static IEnumerable<Item> GetDrives() { static string GetIcon(DriveType driveType) { return driveType switch { DriveType.Fixed => Emoji.Known.ComputerDisk, DriveType.Ram => Emoji.Known.DesktopComputer, DriveType.Removable => Emoji.Known.FloppyDisk, DriveType.CDRom => Emoji.Known.OpticalDisk, DriveType.NoRootDirectory => Emoji.Known.FileFolder, DriveType.Network => Emoji.Known.GlobeWithMeridians, _ => Emoji.Known.WhiteQuestionMark, }; } foreach (var drive in DriveInfo.GetDrives()) { yield return new Item { FullPath = drive.RootDirectory.FullName, Name = $"{drive.Name} - {drive.VolumeLabel}", Icon = GetIcon(drive.DriveType) }; } } public bool SelectionCanExit(Item selectedItem) => File.Exists(selectedItem.FullPath); } internal sealed class BrowserMenu { private string _currentPath; private readonly IBrowserMenuProvider _menuProvider; private readonly string _title; public BrowserMenu(IBrowserMenuProvider menuProvider, string title) { _currentPath = string.Empty; _menuProvider = menuProvider; _title = title; } public async Task<Item> DoSelection() { while (true) { var selection = new SelectionPrompt<Item>() .PageSize(Console.WindowHeight - 5) .AddChoices(_menuProvider.ProvideItems(_currentPath)) .Mode(SelectionMode.Independent) .Title($"{_title} - [green]{_currentPath}[/]") .UseConverter(Render); var item = await selection.ShowAsync(AnsiConsole.Console, CancellationToken.None); if (_menuProvider.SelectionCanExit(item)) { return item; } else { _currentPath = item.FullPath; } } } private string Render(Item item) => $"{item.Icon} {item.Name}"; }