Console Binary file preview
This commit is contained in:
@@ -22,7 +22,6 @@ public class CommandPaletteViewModel : FuzzyPanelViewModel<ICommandPaletteEntryV
|
|||||||
IUserCommandHandlerService userCommandHandlerService,
|
IUserCommandHandlerService userCommandHandlerService,
|
||||||
ICommandKeysHelperService commandKeysHelperService,
|
ICommandKeysHelperService commandKeysHelperService,
|
||||||
ILogger<CommandPaletteViewModel> logger)
|
ILogger<CommandPaletteViewModel> logger)
|
||||||
: base((a, b) => a.Identifier == b.Identifier)
|
|
||||||
{
|
{
|
||||||
_commandPaletteService = commandPaletteService;
|
_commandPaletteService = commandPaletteService;
|
||||||
_identifiableUserCommandService = identifiableUserCommandService;
|
_identifiableUserCommandService = identifiableUserCommandService;
|
||||||
|
|||||||
@@ -6,5 +6,6 @@ public interface IElementPreviewViewModel : IItemPreviewViewModel
|
|||||||
{
|
{
|
||||||
ItemPreviewMode Mode { get; }
|
ItemPreviewMode Mode { get; }
|
||||||
string TextContent { get; }
|
string TextContent { get; }
|
||||||
|
byte[] BinaryContent { get; }
|
||||||
string TextEncoding { get; }
|
string TextEncoding { get; }
|
||||||
}
|
}
|
||||||
@@ -26,6 +26,7 @@ public partial class ElementPreviewViewModel : IElementPreviewViewModel, IAsyncI
|
|||||||
public ItemPreviewMode Mode { get; private set; }
|
public ItemPreviewMode Mode { get; private set; }
|
||||||
|
|
||||||
[Property] private string? _textContent;
|
[Property] private string? _textContent;
|
||||||
|
[Property] private byte[]? _binaryContent;
|
||||||
[Property] private string? _textEncoding;
|
[Property] private string? _textEncoding;
|
||||||
|
|
||||||
public string Name => PreviewName;
|
public string Name => PreviewName;
|
||||||
@@ -35,6 +36,7 @@ public partial class ElementPreviewViewModel : IElementPreviewViewModel, IAsyncI
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var content = await element.Provider.GetContentAsync(element, MaxTextPreviewSize);
|
var content = await element.Provider.GetContentAsync(element, MaxTextPreviewSize);
|
||||||
|
BinaryContent = content;
|
||||||
|
|
||||||
if (content is null)
|
if (content is null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,13 +29,20 @@ public abstract partial class FuzzyPanelViewModel<TItem> : IFuzzyPanelViewModel<
|
|||||||
_searchText = value;
|
_searchText = value;
|
||||||
OnPropertyChanged(new PropertyChangedEventArgs(nameof(SearchText)));
|
OnPropertyChanged(new PropertyChangedEventArgs(nameof(SearchText)));
|
||||||
|
|
||||||
UpdateFilteredMatchesInternal();
|
UpdateFilteredMatches();
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
SelectedItem = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UpdateSelectedItem();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateFilteredMatchesInternal()
|
private void UpdateSelectedItem()
|
||||||
{
|
{
|
||||||
UpdateFilteredMatches();
|
|
||||||
if (SelectedItem != null && FilteredMatches.Contains(SelectedItem)) return;
|
if (SelectedItem != null && FilteredMatches.Contains(SelectedItem)) return;
|
||||||
|
|
||||||
SelectedItem = FilteredMatches.Count > 0
|
SelectedItem = FilteredMatches.Count > 0
|
||||||
|
|||||||
@@ -4,4 +4,6 @@ public class ConsoleApplicationConfiguration
|
|||||||
{
|
{
|
||||||
public string? ConsoleDriver { get; set; }
|
public string? ConsoleDriver { get; set; }
|
||||||
public bool DisableUtf8 { get; set; }
|
public bool DisableUtf8 { get; set; }
|
||||||
|
public string? ClipboardSingleIcon { get; set; }
|
||||||
|
public string? ClipboardMultipleIcon { get; set; }
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using FileTime.App.Core.ViewModels;
|
using FileTime.App.Core.ViewModels;
|
||||||
|
using FileTime.ConsoleUI.App.Preview;
|
||||||
|
|
||||||
namespace FileTime.ConsoleUI.App;
|
namespace FileTime.ConsoleUI.App;
|
||||||
|
|
||||||
@@ -7,4 +8,5 @@ public interface IConsoleAppState : IAppState
|
|||||||
{
|
{
|
||||||
string ErrorText { get; set; }
|
string ErrorText { get; set; }
|
||||||
ObservableCollection<string> PopupTexts { get; }
|
ObservableCollection<string> PopupTexts { get; }
|
||||||
|
ItemPreviewType? PreviewType { get; set; }
|
||||||
}
|
}
|
||||||
@@ -22,5 +22,6 @@ public interface IRootViewModel
|
|||||||
IDeclarativeProperty<VolumeSizeInfo?> VolumeSizeInfo { get; }
|
IDeclarativeProperty<VolumeSizeInfo?> VolumeSizeInfo { get; }
|
||||||
IFrequencyNavigationViewModel FrequencyNavigation { get; }
|
IFrequencyNavigationViewModel FrequencyNavigation { get; }
|
||||||
IItemPreviewService ItemPreviewService { get; }
|
IItemPreviewService ItemPreviewService { get; }
|
||||||
|
IClipboardService ClipboardService { get; }
|
||||||
event Action<IInputElement>? FocusReadInputElement;
|
event Action<IInputElement>? FocusReadInputElement;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace FileTime.ConsoleUI.App.Preview;
|
||||||
|
|
||||||
|
public enum ItemPreviewType
|
||||||
|
{
|
||||||
|
Text,
|
||||||
|
Binary
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using FileTime.App.Core.UserCommand;
|
||||||
|
|
||||||
|
namespace FileTime.ConsoleUI.App.UserCommand;
|
||||||
|
|
||||||
|
public class NextPreviewUserCommand : IIdentifiableUserCommand
|
||||||
|
{
|
||||||
|
public const string CommandId = "console_next_preview";
|
||||||
|
|
||||||
|
public static NextPreviewUserCommand Instance = new();
|
||||||
|
|
||||||
|
private NextPreviewUserCommand()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public string UserCommandID => CommandId;
|
||||||
|
public string Title => "Next preview";
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using FileTime.App.Core.UserCommand;
|
||||||
|
|
||||||
|
namespace FileTime.ConsoleUI.App.UserCommand;
|
||||||
|
|
||||||
|
public class PreviousPreviewUserCommand : IIdentifiableUserCommand
|
||||||
|
{
|
||||||
|
public const string CommandId = "console_previous_preview";
|
||||||
|
|
||||||
|
public static PreviousPreviewUserCommand Instance = new();
|
||||||
|
|
||||||
|
private PreviousPreviewUserCommand()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public string UserCommandID => CommandId;
|
||||||
|
public string Title => "Previous preview";
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using FileTime.App.Core.ViewModels;
|
using FileTime.App.Core.ViewModels;
|
||||||
|
using FileTime.ConsoleUI.App.Preview;
|
||||||
using PropertyChanged.SourceGenerator;
|
using PropertyChanged.SourceGenerator;
|
||||||
|
|
||||||
namespace FileTime.ConsoleUI.App;
|
namespace FileTime.ConsoleUI.App;
|
||||||
@@ -9,4 +10,6 @@ public partial class ConsoleAppState : AppStateBase, IConsoleAppState
|
|||||||
[Notify] private string? _errorText;
|
[Notify] private string? _errorText;
|
||||||
//TODO: make it thread safe
|
//TODO: make it thread safe
|
||||||
public ObservableCollection<string> PopupTexts { get; } = new();
|
public ObservableCollection<string> PopupTexts { get; } = new();
|
||||||
|
|
||||||
|
[Notify] private ItemPreviewType? _previewType = ItemPreviewType.Binary;
|
||||||
}
|
}
|
||||||
@@ -105,13 +105,13 @@ public class CommandPalette
|
|||||||
|
|
||||||
item.Bind(
|
item.Bind(
|
||||||
item.Parent,
|
item.Parent,
|
||||||
d => d.CommandPalette.SelectedItem == item.DataContext ? _theme.ListViewItemTheme.SelectedBackgroundColor : null,
|
d => d.CommandPalette.SelectedItem.Identifier == item.DataContext.Identifier ? _theme.ListViewItemTheme.SelectedBackgroundColor : null,
|
||||||
t => t.Background
|
t => t.Background
|
||||||
);
|
);
|
||||||
|
|
||||||
item.Bind(
|
item.Bind(
|
||||||
item.Parent,
|
item.Parent,
|
||||||
d => d.CommandPalette.SelectedItem == item.DataContext ? _theme.ListViewItemTheme.SelectedForegroundColor : null,
|
d => d.CommandPalette.SelectedItem.Identifier == item.DataContext.Identifier ? _theme.ListViewItemTheme.SelectedForegroundColor : null,
|
||||||
t => t.Foreground
|
t => t.Foreground
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using FileTime.App.Core.Models;
|
using FileTime.App.Core.Models;
|
||||||
using FileTime.App.Core.ViewModels.ItemPreview;
|
using FileTime.App.Core.ViewModels.ItemPreview;
|
||||||
|
using FileTime.ConsoleUI.App.Preview;
|
||||||
using FileTime.ConsoleUI.App.Styling;
|
using FileTime.ConsoleUI.App.Styling;
|
||||||
using TerminalUI.Controls;
|
using TerminalUI.Controls;
|
||||||
using TerminalUI.Extensions;
|
using TerminalUI.Extensions;
|
||||||
@@ -11,10 +12,12 @@ namespace FileTime.ConsoleUI.App.Controls;
|
|||||||
public class ItemPreviews
|
public class ItemPreviews
|
||||||
{
|
{
|
||||||
private readonly ITheme _theme;
|
private readonly ITheme _theme;
|
||||||
|
private readonly IConsoleAppState _appState;
|
||||||
|
|
||||||
public ItemPreviews(ITheme theme)
|
public ItemPreviews(ITheme theme, IConsoleAppState appState)
|
||||||
{
|
{
|
||||||
_theme = theme;
|
_theme = theme;
|
||||||
|
_appState = appState;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IView<IRootViewModel> View()
|
public IView<IRootViewModel> View()
|
||||||
@@ -75,11 +78,34 @@ public class ItemPreviews
|
|||||||
ChildInitializer =
|
ChildInitializer =
|
||||||
{
|
{
|
||||||
new TextBlock<IElementPreviewViewModel>()
|
new TextBlock<IElementPreviewViewModel>()
|
||||||
.Setup(t => t.Bind(
|
.Setup(t =>
|
||||||
|
{
|
||||||
|
t.Bind(
|
||||||
t,
|
t,
|
||||||
dc => dc.TextContent,
|
dc => dc.TextContent,
|
||||||
t => t.Text,
|
t => t.Text,
|
||||||
fallbackValue: string.Empty)),
|
fallbackValue: string.Empty);
|
||||||
|
|
||||||
|
t.Bind(
|
||||||
|
t,
|
||||||
|
dc => _appState.PreviewType,
|
||||||
|
t => t.IsVisible,
|
||||||
|
v => v is null or ItemPreviewType.Text);
|
||||||
|
}),
|
||||||
|
new BinaryView<IElementPreviewViewModel>()
|
||||||
|
.Setup(b =>
|
||||||
|
{
|
||||||
|
b.Bind(
|
||||||
|
b,
|
||||||
|
dc => dc.BinaryContent,
|
||||||
|
b => b.Data);
|
||||||
|
|
||||||
|
b.Bind(
|
||||||
|
b,
|
||||||
|
dc => _appState.PreviewType,
|
||||||
|
t => t.IsVisible,
|
||||||
|
v => v == ItemPreviewType.Binary);
|
||||||
|
}),
|
||||||
new TextBlock<IElementPreviewViewModel>
|
new TextBlock<IElementPreviewViewModel>
|
||||||
{
|
{
|
||||||
Margin = "0 1 0 0",
|
Margin = "0 1 0 0",
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using FileTime.App.Core.Models.Enums;
|
using FileTime.App.Core.Models.Enums;
|
||||||
using FileTime.App.Core.ViewModels;
|
using FileTime.App.Core.ViewModels;
|
||||||
|
using FileTime.ConsoleUI.App.Configuration;
|
||||||
using FileTime.ConsoleUI.App.Controls;
|
using FileTime.ConsoleUI.App.Controls;
|
||||||
using FileTime.ConsoleUI.App.Styling;
|
using FileTime.ConsoleUI.App.Styling;
|
||||||
using FileTime.Core.Enums;
|
using FileTime.Core.Enums;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
using Humanizer.Bytes;
|
using Humanizer.Bytes;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using TerminalUI;
|
using TerminalUI;
|
||||||
using TerminalUI.Color;
|
using TerminalUI.Color;
|
||||||
using TerminalUI.Controls;
|
using TerminalUI.Controls;
|
||||||
@@ -36,6 +38,7 @@ public class MainWindow
|
|||||||
private readonly Dialogs _dialogs;
|
private readonly Dialogs _dialogs;
|
||||||
private readonly Timeline _timeline;
|
private readonly Timeline _timeline;
|
||||||
private readonly ItemPreviews _itemPreviews;
|
private readonly ItemPreviews _itemPreviews;
|
||||||
|
private readonly IOptions<ConsoleApplicationConfiguration> _consoleApplicationConfiguration;
|
||||||
private readonly Lazy<IView> _root;
|
private readonly Lazy<IView> _root;
|
||||||
|
|
||||||
|
|
||||||
@@ -47,7 +50,8 @@ public class MainWindow
|
|||||||
FrequencyNavigation frequencyNavigation,
|
FrequencyNavigation frequencyNavigation,
|
||||||
Dialogs dialogs,
|
Dialogs dialogs,
|
||||||
Timeline timeline,
|
Timeline timeline,
|
||||||
ItemPreviews itemPreviews)
|
ItemPreviews itemPreviews,
|
||||||
|
IOptions<ConsoleApplicationConfiguration> consoleApplicationConfiguration)
|
||||||
{
|
{
|
||||||
_rootViewModel = rootViewModel;
|
_rootViewModel = rootViewModel;
|
||||||
_applicationContext = applicationContext;
|
_applicationContext = applicationContext;
|
||||||
@@ -57,6 +61,7 @@ public class MainWindow
|
|||||||
_dialogs = dialogs;
|
_dialogs = dialogs;
|
||||||
_timeline = timeline;
|
_timeline = timeline;
|
||||||
_itemPreviews = itemPreviews;
|
_itemPreviews = itemPreviews;
|
||||||
|
_consoleApplicationConfiguration = consoleApplicationConfiguration;
|
||||||
_root = new Lazy<IView>(Initialize);
|
_root = new Lazy<IView>(Initialize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +97,7 @@ public class MainWindow
|
|||||||
{
|
{
|
||||||
new Grid<IRootViewModel>
|
new Grid<IRootViewModel>
|
||||||
{
|
{
|
||||||
ColumnDefinitionsObject = "Auto * Auto",
|
ColumnDefinitionsObject = "Auto * Auto Auto",
|
||||||
ChildInitializer =
|
ChildInitializer =
|
||||||
{
|
{
|
||||||
new StackPanel<IRootViewModel>
|
new StackPanel<IRootViewModel>
|
||||||
@@ -130,8 +135,32 @@ public class MainWindow
|
|||||||
root => root.AppState.SelectedTab.Value.CurrentLocation.Value.FullName.Path,
|
root => root.AppState.SelectedTab.Value.CurrentLocation.Value.FullName.Path,
|
||||||
tb => tb.Text
|
tb => tb.Text
|
||||||
)),
|
)),
|
||||||
|
new StackPanel<IRootViewModel>
|
||||||
|
{
|
||||||
|
Margin = "2 0 0 0",
|
||||||
|
Extensions = {new GridPositionExtension(2, 0)},
|
||||||
|
ChildInitializer =
|
||||||
|
{
|
||||||
|
new TextBlock<IRootViewModel>
|
||||||
|
{
|
||||||
|
Text = _consoleApplicationConfiguration.Value.ClipboardSingleIcon ?? "C",
|
||||||
|
AsciiOnly = false
|
||||||
|
}.Setup(t => t.Bind(
|
||||||
|
t,
|
||||||
|
dc => dc.ClipboardService.Content.Count == 1,
|
||||||
|
t => t.IsVisible)),
|
||||||
|
new TextBlock<IRootViewModel>
|
||||||
|
{
|
||||||
|
Text = _consoleApplicationConfiguration.Value.ClipboardMultipleIcon ?? "CC",
|
||||||
|
AsciiOnly = false
|
||||||
|
}.Setup(t => t.Bind(
|
||||||
|
t,
|
||||||
|
dc => dc.ClipboardService.Content.Count > 1,
|
||||||
|
t => t.IsVisible))
|
||||||
|
}
|
||||||
|
},
|
||||||
TabControl()
|
TabControl()
|
||||||
.WithExtension(new GridPositionExtension(2, 0))
|
.WithExtension(new GridPositionExtension(3, 0))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new Grid<IRootViewModel>
|
new Grid<IRootViewModel>
|
||||||
@@ -375,6 +404,7 @@ public class MainWindow
|
|||||||
{
|
{
|
||||||
var tabList = new ListView<IRootViewModel, ITabViewModel>
|
var tabList = new ListView<IRootViewModel, ITabViewModel>
|
||||||
{
|
{
|
||||||
|
Margin = "1 0 0 0",
|
||||||
Orientation = Orientation.Horizontal,
|
Orientation = Orientation.Horizontal,
|
||||||
ItemTemplate = item =>
|
ItemTemplate = item =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,13 +4,15 @@ using FileTime.App.Core.Services;
|
|||||||
using FileTime.App.Core.ViewModels;
|
using FileTime.App.Core.ViewModels;
|
||||||
using FileTime.App.Core.ViewModels.Timeline;
|
using FileTime.App.Core.ViewModels.Timeline;
|
||||||
using FileTime.App.FrequencyNavigation.ViewModels;
|
using FileTime.App.FrequencyNavigation.ViewModels;
|
||||||
|
using FileTime.ConsoleUI.App.Preview;
|
||||||
using FileTime.ConsoleUI.App.Services;
|
using FileTime.ConsoleUI.App.Services;
|
||||||
using FileTime.Core.Interactions;
|
using FileTime.Core.Interactions;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
|
using PropertyChanged.SourceGenerator;
|
||||||
|
|
||||||
namespace FileTime.ConsoleUI.App;
|
namespace FileTime.ConsoleUI.App;
|
||||||
|
|
||||||
public class RootViewModel : IRootViewModel
|
public partial class RootViewModel : IRootViewModel
|
||||||
{
|
{
|
||||||
public string UserName => Environment.UserName;
|
public string UserName => Environment.UserName;
|
||||||
public string MachineName => Environment.MachineName;
|
public string MachineName => Environment.MachineName;
|
||||||
@@ -19,6 +21,7 @@ public class RootViewModel : IRootViewModel
|
|||||||
public ICommandPaletteViewModel CommandPalette { get; }
|
public ICommandPaletteViewModel CommandPalette { get; }
|
||||||
public IFrequencyNavigationViewModel FrequencyNavigation { get; }
|
public IFrequencyNavigationViewModel FrequencyNavigation { get; }
|
||||||
public IItemPreviewService ItemPreviewService { get; }
|
public IItemPreviewService ItemPreviewService { get; }
|
||||||
|
public IClipboardService ClipboardService { get; }
|
||||||
public IDialogService DialogService { get; }
|
public IDialogService DialogService { get; }
|
||||||
public ITimelineViewModel TimelineViewModel { get; }
|
public ITimelineViewModel TimelineViewModel { get; }
|
||||||
public IDeclarativeProperty<VolumeSizeInfo?> VolumeSizeInfo { get;}
|
public IDeclarativeProperty<VolumeSizeInfo?> VolumeSizeInfo { get;}
|
||||||
@@ -32,7 +35,8 @@ public class RootViewModel : IRootViewModel
|
|||||||
IDialogService dialogService,
|
IDialogService dialogService,
|
||||||
ITimelineViewModel timelineViewModel,
|
ITimelineViewModel timelineViewModel,
|
||||||
IFrequencyNavigationViewModel frequencyNavigation,
|
IFrequencyNavigationViewModel frequencyNavigation,
|
||||||
IItemPreviewService itemPreviewService)
|
IItemPreviewService itemPreviewService,
|
||||||
|
IClipboardService clipboardService)
|
||||||
{
|
{
|
||||||
AppState = appState;
|
AppState = appState;
|
||||||
PossibleCommands = possibleCommands;
|
PossibleCommands = possibleCommands;
|
||||||
@@ -41,6 +45,7 @@ public class RootViewModel : IRootViewModel
|
|||||||
TimelineViewModel = timelineViewModel;
|
TimelineViewModel = timelineViewModel;
|
||||||
FrequencyNavigation = frequencyNavigation;
|
FrequencyNavigation = frequencyNavigation;
|
||||||
ItemPreviewService = itemPreviewService;
|
ItemPreviewService = itemPreviewService;
|
||||||
|
ClipboardService = clipboardService;
|
||||||
|
|
||||||
DialogService.ReadInput.PropertyChanged += (o, e) =>
|
DialogService.ReadInput.PropertyChanged += (o, e) =>
|
||||||
{
|
{
|
||||||
@@ -53,6 +58,14 @@ public class RootViewModel : IRootViewModel
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
itemPreviewService.ItemPreview.PropertyChanged += (o, e) =>
|
||||||
|
{
|
||||||
|
if (e.PropertyName == nameof(itemPreviewService.ItemPreview.Value))
|
||||||
|
{
|
||||||
|
appState.PreviewType = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
VolumeSizeInfo = appState.SelectedTab
|
VolumeSizeInfo = appState.SelectedTab
|
||||||
.Map(t => t?.CurrentLocation)
|
.Map(t => t?.CurrentLocation)
|
||||||
.Switch()
|
.Switch()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using FileTime.ConsoleUI.App.Configuration;
|
|||||||
using FileTime.ConsoleUI.App.Controls;
|
using FileTime.ConsoleUI.App.Controls;
|
||||||
using FileTime.ConsoleUI.App.KeyInputHandling;
|
using FileTime.ConsoleUI.App.KeyInputHandling;
|
||||||
using FileTime.ConsoleUI.App.Services;
|
using FileTime.ConsoleUI.App.Services;
|
||||||
|
using FileTime.ConsoleUI.App.UserCommand;
|
||||||
using FileTime.Core.Interactions;
|
using FileTime.Core.Interactions;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@@ -12,6 +13,16 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
|
|||||||
|
|
||||||
namespace FileTime.ConsoleUI.App;
|
namespace FileTime.ConsoleUI.App;
|
||||||
|
|
||||||
|
public class StartupHandler : IStartupHandler
|
||||||
|
{
|
||||||
|
public StartupHandler(IIdentifiableUserCommandService identifiableUserCommandService)
|
||||||
|
{
|
||||||
|
identifiableUserCommandService.AddIdentifiableUserCommand(NextPreviewUserCommand.Instance);
|
||||||
|
identifiableUserCommandService.AddIdentifiableUserCommand(PreviousPreviewUserCommand.Instance);
|
||||||
|
}
|
||||||
|
public Task InitAsync() => Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
public static class Startup
|
public static class Startup
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddConsoleServices(this IServiceCollection services, IConfigurationRoot configuration)
|
public static IServiceCollection AddConsoleServices(this IServiceCollection services, IConfigurationRoot configuration)
|
||||||
@@ -27,6 +38,8 @@ public static class Startup
|
|||||||
services.TryAddSingleton<IRootViewModel, RootViewModel>();
|
services.TryAddSingleton<IRootViewModel, RootViewModel>();
|
||||||
services.TryAddSingleton<IDialogService, DialogService>();
|
services.TryAddSingleton<IDialogService, DialogService>();
|
||||||
services.TryAddSingleton<IUserCommunicationService>(sp => sp.GetRequiredService<IDialogService>());
|
services.TryAddSingleton<IUserCommunicationService>(sp => sp.GetRequiredService<IDialogService>());
|
||||||
|
services.AddSingleton<IUserCommandHandler, ConsoleUserCommandHandler>();
|
||||||
|
services.AddSingleton<IStartupHandler, StartupHandler>();
|
||||||
|
|
||||||
services.Configure<ConsoleApplicationConfiguration>(configuration);
|
services.Configure<ConsoleApplicationConfiguration>(configuration);
|
||||||
return services;
|
return services;
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
using FileTime.App.Core.Services;
|
||||||
|
using FileTime.App.Core.Services.UserCommandHandler;
|
||||||
|
using FileTime.App.Core.ViewModels.ItemPreview;
|
||||||
|
using FileTime.ConsoleUI.App.Preview;
|
||||||
|
|
||||||
|
namespace FileTime.ConsoleUI.App.UserCommand;
|
||||||
|
|
||||||
|
public class ConsoleUserCommandHandler : AggregatedUserCommandHandler
|
||||||
|
{
|
||||||
|
private static readonly ItemPreviewType[] ElementPreviewOrder = {ItemPreviewType.Text, ItemPreviewType.Binary};
|
||||||
|
|
||||||
|
private readonly IConsoleAppState _consoleAppState;
|
||||||
|
private readonly IItemPreviewService _itemPreviewService;
|
||||||
|
|
||||||
|
public ConsoleUserCommandHandler(
|
||||||
|
IConsoleAppState consoleAppState,
|
||||||
|
IItemPreviewService itemPreviewService
|
||||||
|
)
|
||||||
|
{
|
||||||
|
_consoleAppState = consoleAppState;
|
||||||
|
_itemPreviewService = itemPreviewService;
|
||||||
|
AddCommandHandler(new IUserCommandHandler[]
|
||||||
|
{
|
||||||
|
new TypeUserCommandHandler<NextPreviewUserCommand>(NextPreview),
|
||||||
|
new TypeUserCommandHandler<PreviousPreviewUserCommand>(PreviousPreview),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task NextPreview()
|
||||||
|
{
|
||||||
|
if (_itemPreviewService.ItemPreview.Value is not IElementPreviewViewModel) return Task.CompletedTask;
|
||||||
|
|
||||||
|
var previewOrder = ElementPreviewOrder;
|
||||||
|
if (previewOrder.Length < 2) return Task.CompletedTask;
|
||||||
|
|
||||||
|
if (_consoleAppState.PreviewType == null)
|
||||||
|
{
|
||||||
|
_consoleAppState.PreviewType = previewOrder.Length > 1 ? previewOrder[1] : null;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentPreviewType = _consoleAppState.PreviewType.Value;
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < previewOrder.Length; i++)
|
||||||
|
{
|
||||||
|
if (previewOrder[i] == currentPreviewType) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
|
||||||
|
_consoleAppState.PreviewType = i >= previewOrder.Length ? previewOrder[0] : previewOrder[i];
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task PreviousPreview()
|
||||||
|
{
|
||||||
|
if (_itemPreviewService.ItemPreview.Value is not IElementPreviewViewModel) return Task.CompletedTask;
|
||||||
|
|
||||||
|
var previewOrder = ElementPreviewOrder;
|
||||||
|
if (previewOrder.Length < 2) return Task.CompletedTask;
|
||||||
|
|
||||||
|
if (_consoleAppState.PreviewType == null)
|
||||||
|
{
|
||||||
|
_consoleAppState.PreviewType = previewOrder.Length > 1 ? previewOrder[^1] : null;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentPreviewType = _consoleAppState.PreviewType.Value;
|
||||||
|
int i;
|
||||||
|
for (i = previewOrder.Length - 1; i > -1; i--)
|
||||||
|
{
|
||||||
|
if (previewOrder[i] == currentPreviewType) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
i--;
|
||||||
|
|
||||||
|
_consoleAppState.PreviewType = i > -1 ? previewOrder[^1] : previewOrder[i];
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,4 +35,8 @@
|
|||||||
<PackageReference Include="Terminal.Gui" Version="1.13.5" />
|
<PackageReference Include="Terminal.Gui" Version="1.13.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
|
||||||
|
<Content Include="appsettings.json" CopyToOutputDirectory="PreserveNewest" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
4
src/ConsoleApp/FileTime.ConsoleUI/appsettings.json
Normal file
4
src/ConsoleApp/FileTime.ConsoleUI/appsettings.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"ClipboardSingleIcon": "\udb80\udd4c",
|
||||||
|
"ClipboardMultipleIcon": "\udb84\ude68"
|
||||||
|
}
|
||||||
@@ -8,6 +8,10 @@ public class MockRenderEngine : IRenderEngine
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void VisibilityChanged(IView view, bool newVisibility)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public void VisibilityChanged(IView view)
|
public void VisibilityChanged(IView view)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
126
src/Library/TerminalUI/Controls/BinaryView.cs
Normal file
126
src/Library/TerminalUI/Controls/BinaryView.cs
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
using System.Reflection.Metadata;
|
||||||
|
using PropertyChanged.SourceGenerator;
|
||||||
|
using TerminalUI.Color;
|
||||||
|
using TerminalUI.Models;
|
||||||
|
using TerminalUI.Traits;
|
||||||
|
|
||||||
|
namespace TerminalUI.Controls;
|
||||||
|
|
||||||
|
public partial class BinaryView<T> : View<BinaryView<T>, T>, IDisplayView
|
||||||
|
{
|
||||||
|
private record RenderState(
|
||||||
|
byte[]? Data,
|
||||||
|
int BytesPerLine,
|
||||||
|
Position Position,
|
||||||
|
Size Size,
|
||||||
|
IColor? Foreground,
|
||||||
|
IColor? Background);
|
||||||
|
|
||||||
|
private RenderState? _lastRenderState;
|
||||||
|
|
||||||
|
[Notify] private byte[]? _data;
|
||||||
|
[Notify] private int _bytesPerLine = 16;
|
||||||
|
|
||||||
|
public BinaryView()
|
||||||
|
{
|
||||||
|
RerenderProperties.Add(nameof(Data));
|
||||||
|
RerenderProperties.Add(nameof(BytesPerLine));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Size CalculateSize()
|
||||||
|
{
|
||||||
|
if (_data is null) return new(0, 0);
|
||||||
|
if (_data.Length < _bytesPerLine) return new Size(_data.Length, 1);
|
||||||
|
|
||||||
|
var completeLines = (int) Math.Floor((double) _data.Length / _bytesPerLine);
|
||||||
|
var remaining = completeLines * _bytesPerLine - completeLines;
|
||||||
|
|
||||||
|
var lines = remaining > 0 ? completeLines + 1 : completeLines;
|
||||||
|
|
||||||
|
return new Size(_bytesPerLine * 3 - 1, lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool DefaultRenderer(in RenderContext renderContext, Position position, Size size)
|
||||||
|
{
|
||||||
|
if (size.Width < 2 || size.Height == 0) return false;
|
||||||
|
|
||||||
|
var data = _data;
|
||||||
|
var bytesPerLine = _bytesPerLine;
|
||||||
|
if (size.Width < _bytesPerLine * 3 - 1)
|
||||||
|
{
|
||||||
|
bytesPerLine = (int) Math.Floor((double) (size.Width + 1) / 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
var foreground = Foreground ?? renderContext.Foreground;
|
||||||
|
var background = Background ?? renderContext.Background;
|
||||||
|
|
||||||
|
var renderState = new RenderState(
|
||||||
|
data,
|
||||||
|
bytesPerLine,
|
||||||
|
position,
|
||||||
|
size,
|
||||||
|
foreground,
|
||||||
|
background);
|
||||||
|
|
||||||
|
var skipRender = !renderContext.ForceRerender && !NeedsRerender(renderState);
|
||||||
|
_lastRenderState = renderState;
|
||||||
|
|
||||||
|
if (data is null) return false;
|
||||||
|
|
||||||
|
var lineI = 0;
|
||||||
|
var textSize = size with {Height = 1};
|
||||||
|
for (var i = 0; i < data.Length; i += bytesPerLine, lineI++)
|
||||||
|
{
|
||||||
|
if (lineI > size.Height) break;
|
||||||
|
RenderLine(
|
||||||
|
renderContext,
|
||||||
|
data,
|
||||||
|
i,
|
||||||
|
i + bytesPerLine,
|
||||||
|
position with {Y = position.Y + lineI},
|
||||||
|
textSize,
|
||||||
|
skipRender);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderLine(
|
||||||
|
in RenderContext renderContext,
|
||||||
|
byte[] data,
|
||||||
|
int startIndex,
|
||||||
|
int maxEndIndex,
|
||||||
|
Position position,
|
||||||
|
Size size,
|
||||||
|
bool updateCellsOnly
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Span<char> text = stackalloc char[(maxEndIndex - startIndex) * 3 - 1];
|
||||||
|
var textI = 0;
|
||||||
|
for (var i = startIndex; i < maxEndIndex && i < data.Length; i++, textI += 3)
|
||||||
|
{
|
||||||
|
var b = data[i];
|
||||||
|
var b1 = (byte) (b >> 4);
|
||||||
|
var b2 = (byte) (b & 0x0F);
|
||||||
|
|
||||||
|
var c1 = b1 < 10 ? (char) (b1 + '0') : (char) (b1 - 10 + 'A');
|
||||||
|
var c2 = b2 < 10 ? (char) (b2 + '0') : (char) (b2 - 10 + 'A');
|
||||||
|
text[textI] = c1;
|
||||||
|
text[textI + 1] = c2;
|
||||||
|
|
||||||
|
if (textI + 2 < text.Length)
|
||||||
|
text[textI + 2] = ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderText(
|
||||||
|
text,
|
||||||
|
renderContext,
|
||||||
|
position,
|
||||||
|
size,
|
||||||
|
updateCellsOnly
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool NeedsRerender(RenderState renderState)
|
||||||
|
=> _lastRenderState is null || _lastRenderState != renderState;
|
||||||
|
}
|
||||||
@@ -17,7 +17,8 @@ public sealed partial class TextBlock<T> : View<TextBlock<T>, T>, IDisplayView
|
|||||||
string? Text,
|
string? Text,
|
||||||
IColor? Foreground,
|
IColor? Foreground,
|
||||||
IColor? Background,
|
IColor? Background,
|
||||||
ITextFormat? TextFormat);
|
ITextFormat? TextFormat,
|
||||||
|
bool AsciiOnly);
|
||||||
|
|
||||||
private RenderState? _lastRenderState;
|
private RenderState? _lastRenderState;
|
||||||
private string[]? _textLines;
|
private string[]? _textLines;
|
||||||
@@ -26,6 +27,7 @@ public sealed partial class TextBlock<T> : View<TextBlock<T>, T>, IDisplayView
|
|||||||
[Notify] private TextAlignment _textAlignment = TextAlignment.Left;
|
[Notify] private TextAlignment _textAlignment = TextAlignment.Left;
|
||||||
[Notify] private ITextFormat? _textFormat;
|
[Notify] private ITextFormat? _textFormat;
|
||||||
[Notify] private int _textStartIndex;
|
[Notify] private int _textStartIndex;
|
||||||
|
[Notify] private bool _asciiOnly = true;
|
||||||
|
|
||||||
public TextBlock()
|
public TextBlock()
|
||||||
{
|
{
|
||||||
@@ -33,6 +35,7 @@ public sealed partial class TextBlock<T> : View<TextBlock<T>, T>, IDisplayView
|
|||||||
RerenderProperties.Add(nameof(TextAlignment));
|
RerenderProperties.Add(nameof(TextAlignment));
|
||||||
RerenderProperties.Add(nameof(TextFormat));
|
RerenderProperties.Add(nameof(TextFormat));
|
||||||
RerenderProperties.Add(nameof(TextStartIndex));
|
RerenderProperties.Add(nameof(TextStartIndex));
|
||||||
|
RerenderProperties.Add(nameof(AsciiOnly));
|
||||||
|
|
||||||
((INotifyPropertyChanged) this).PropertyChanged += (o, e) =>
|
((INotifyPropertyChanged) this).PropertyChanged += (o, e) =>
|
||||||
{
|
{
|
||||||
@@ -51,19 +54,23 @@ public sealed partial class TextBlock<T> : View<TextBlock<T>, T>, IDisplayView
|
|||||||
|
|
||||||
var foreground = Foreground ?? renderContext.Foreground;
|
var foreground = Foreground ?? renderContext.Foreground;
|
||||||
var background = Background ?? renderContext.Background;
|
var background = Background ?? renderContext.Background;
|
||||||
|
var text = Text;
|
||||||
|
var textLines = _textLines;
|
||||||
|
var asciiOnly = _asciiOnly;
|
||||||
|
|
||||||
var renderState = new RenderState(
|
var renderState = new RenderState(
|
||||||
position,
|
position,
|
||||||
size,
|
size,
|
||||||
Text,
|
text,
|
||||||
foreground,
|
foreground,
|
||||||
background,
|
background,
|
||||||
_textFormat);
|
_textFormat,
|
||||||
|
asciiOnly);
|
||||||
|
|
||||||
var skipRender = !renderContext.ForceRerender && !NeedsRerender(renderState);
|
var skipRender = !renderContext.ForceRerender && !NeedsRerender(renderState);
|
||||||
|
|
||||||
_lastRenderState = renderState;
|
_lastRenderState = renderState;
|
||||||
|
|
||||||
var textLines = _textLines;
|
|
||||||
var textStartIndex = _textStartIndex;
|
var textStartIndex = _textStartIndex;
|
||||||
if (textLines is null)
|
if (textLines is null)
|
||||||
{
|
{
|
||||||
@@ -81,7 +88,7 @@ public sealed partial class TextBlock<T> : View<TextBlock<T>, T>, IDisplayView
|
|||||||
_textStartIndex = textLines.Length - size.Height;
|
_textStartIndex = textLines.Length - size.Height;
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderText(textLines, renderContext, position, size, skipRender, TransformText);
|
RenderText(textLines, renderContext, position, size, skipRender, TransformText, asciiOnly);
|
||||||
|
|
||||||
return !skipRender;
|
return !skipRender;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
@@ -220,10 +220,13 @@ public abstract partial class View<TConcrete, T> : IView<T> where TConcrete : Vi
|
|||||||
|
|
||||||
private static void UpdateCells(bool[,] renderContextUpdatedCells, Position position, int sizeWidth, int sizeHeight)
|
private static void UpdateCells(bool[,] renderContextUpdatedCells, Position position, int sizeWidth, int sizeHeight)
|
||||||
{
|
{
|
||||||
|
var xLen = renderContextUpdatedCells.GetLength(0);
|
||||||
|
var yLen = renderContextUpdatedCells.GetLength(1);
|
||||||
for (var x = 0; x < sizeWidth; x++)
|
for (var x = 0; x < sizeWidth; x++)
|
||||||
{
|
{
|
||||||
for (var y = 0; y < sizeHeight; y++)
|
for (var y = 0; y < sizeHeight; y++)
|
||||||
{
|
{
|
||||||
|
if (position.X + x >= xLen || position.Y + y >= yLen) continue;
|
||||||
renderContextUpdatedCells[position.X + x, position.Y + y] = true;
|
renderContextUpdatedCells[position.X + x, position.Y + y] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user