More project base

This commit is contained in:
2022-04-01 20:38:43 +02:00
parent d0edf85e98
commit 8e09bf41bb
76 changed files with 3589 additions and 78 deletions

View File

@@ -0,0 +1,66 @@
namespace FileTime.App.Core.Command
{
public enum Commands
{
None,
AutoRefresh,
ChangeTimelineMode,
CloseTab,
Compress,
Copy,
CopyHash,
CopyPath,
CreateContainer,
CreateElement,
Cut,
Edit,
EnterRapidTravel,
FindByName,
FindByNameRegex,
GoToHome,
GoToPath,
GoToProvider,
GoToRoot,
GoUp,
HardDelete,
Mark,
MoveCursorDown,
MoveCursorDownPage,
MoveCursorUp,
MoveCursorUpPage,
MoveToFirst,
MoveToLast,
NextTimelineBlock,
NextTimelineCommand,
Open,
OpenInFileBrowser,
OpenOrRun,
PasteMerge,
PasteOverwrite,
PasteSkip,
PinFavorite,
PreviousTimelineBlock,
PreviousTimelineCommand,
Refresh,
Rename,
RunCommand,
ScanContainerSize,
ShowAllShotcut,
SoftDelete,
SwitchToLastTab,
SwitchToTab1,
SwitchToTab2,
SwitchToTab3,
SwitchToTab4,
SwitchToTab5,
SwitchToTab6,
SwitchToTab7,
SwitchToTab8,
TimelinePause,
TimelineRefresh,
TimelineStart,
ToggleAdvancedIcons,
ToggleHidden,
}
}

View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Reactive" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Core\FileTime.Core.Abstraction\FileTime.Core.Abstraction.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,16 @@
using System.Collections.ObjectModel;
using FileTime.App.Core.ViewModels;
namespace FileTime.App.Core
{
public interface IAppState
{
ObservableCollection<ITabViewModel> Tabs { get; }
ITabViewModel? SelectedTab { get; }
IObservable<string?> SearchText { get; }
void AddTab(ITabViewModel tabViewModel);
void RemoveTab(ITabViewModel tabViewModel);
void SetSearchText(string? searchText);
}
}

View File

@@ -0,0 +1,10 @@
namespace FileTime.App.Core.Models.Enums
{
public enum ItemAttributeType
{
File,
Element,
Container,
SizeContainer
}
}

View File

@@ -0,0 +1,12 @@
namespace FileTime.App.Core.Models.Enums
{
public enum ItemViewMode
{
Default,
Alternative,
Selected,
Marked,
MarkedSelected,
MarkedAlternative
}
}

View File

@@ -0,0 +1,14 @@
namespace FileTime.App.Core.Models
{
public class ItemNamePart
{
public string Text { get; set; }
public bool IsSpecial { get; set; }
public ItemNamePart(string text, bool isSpecial = false)
{
Text = text;
IsSpecial = isSpecial;
}
}
}

View File

@@ -0,0 +1,11 @@
using FileTime.App.Core.Models;
namespace FileTime.App.Core.Services
{
public interface IItemNameConverterService
{
List<ItemNamePart> GetDisplayName(string name, string? searchText);
string GetFileExtension(string fullName);
string GetFileName(string fullName);
}
}

View File

@@ -0,0 +1,7 @@
namespace FileTime.App.Core.ViewModels
{
public interface IContainerSizeContainerViewModel : IItemViewModel
{
}
}

View File

@@ -0,0 +1,7 @@
namespace FileTime.App.Core.ViewModels
{
public interface IContainerViewModel : IItemViewModel
{
}
}

View File

@@ -0,0 +1,7 @@
namespace FileTime.App.Core.ViewModels
{
public interface IElementViewModel : IItemViewModel
{
}
}

View File

@@ -0,0 +1,7 @@
namespace FileTime.App.Core.ViewModels
{
public interface IFileViewModel : IElementViewModel
{
}
}

View File

@@ -0,0 +1,15 @@
using FileTime.App.Core.Models;
using FileTime.App.Core.Models.Enums;
using FileTime.Core.Models;
namespace FileTime.App.Core.ViewModels
{
public interface IItemViewModel
{
IItem? Item { get; set; }
IObservable<IReadOnlyList<ItemNamePart>>? DisplayName { get; set; }
IObservable<bool>? IsSelected { get; set; }
IObservable<bool>? IsMarked { get; set; }
ItemViewMode ViewMode { get; set; }
}
}

View File

@@ -0,0 +1,16 @@
using FileTime.Core.Models;
using FileTime.Core.Services;
using InitableService;
namespace FileTime.App.Core.ViewModels
{
public interface ITabViewModel : IInitable<ITab>
{
IObservable<IContainer?>? CurrentLocation { get; }
IObservable<IItemViewModel?>? CurrentSelectedItem { get; }
IObservable<IReadOnlyList<IItemViewModel>>? CurrentItems { get; }
IObservable<IReadOnlyList<FullName>> MarkedItems { get; }
ITab? Tab { get; }
}
}

View File

@@ -0,0 +1,49 @@
using System.Collections.ObjectModel;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using FileTime.App.Core.ViewModels;
using MvvmGen;
namespace FileTime.App.Core
{
[ViewModel]
public abstract partial class AppStateBase : IAppState
{
private readonly BehaviorSubject<string?> _searchText = new(null);
public ObservableCollection<ITabViewModel> Tabs { get; } = new();
public IObservable<string?> SearchText { get; private set; }
[Property]
private ITabViewModel? _selectedTab;
partial void OnInitialize()
{
SearchText = _searchText.AsObservable();
}
public void AddTab(ITabViewModel tabViewModel)
{
Tabs.Add(tabViewModel);
if (_selectedTab == null)
{
SelectedTab = Tabs.First();
}
}
public void RemoveTab(ITabViewModel tabViewModel)
{
if (!Tabs.Contains(tabViewModel)) return;
Tabs.Remove(tabViewModel);
if (_selectedTab == tabViewModel)
{
SelectedTab = Tabs.FirstOrDefault();
}
}
public void SetSearchText(string? searchText)
{
_searchText.OnNext(searchText);
}
}
}

View File

@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MvvmGen" Version="1.1.5" />
<PackageReference Include="System.Reactive" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Core\FileTime.Core.Abstraction\FileTime.Core.Abstraction.csproj" />
<ProjectReference Include="..\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,56 @@
using FileTime.App.Core.Models;
namespace FileTime.App.Core.Services
{
public class ItemNameConverterService : IItemNameConverterService
{
public List<ItemNamePart> GetDisplayName(string name, string? searchText)
{
var nameParts = new List<ItemNamePart>();
searchText = searchText?.ToLower();
if (!string.IsNullOrEmpty(searchText))
{
var nameLeft = name;
while (nameLeft.ToLower().IndexOf(searchText, StringComparison.Ordinal) is int rapidTextStart && rapidTextStart != -1)
{
var before = rapidTextStart > 0 ? nameLeft.Substring(0, rapidTextStart) : null;
var rapidTravel = nameLeft.Substring(rapidTextStart, searchText.Length);
nameLeft = nameLeft.Substring(rapidTextStart + searchText.Length);
if (before != null)
{
nameParts.Add(new ItemNamePart(before));
}
nameParts.Add(new ItemNamePart(rapidTravel, true));
}
if (nameLeft.Length > 0)
{
nameParts.Add(new ItemNamePart(nameLeft));
}
}
else
{
nameParts.Add(new ItemNamePart(name));
}
return nameParts;
}
public string GetFileName(string fullName)
{
var parts = fullName.Split('.');
var fileName = string.Join('.', parts[..^1]);
return string.IsNullOrEmpty(fileName) ? fullName : fileName;
}
public string GetFileExtension(string fullName)
{
var parts = fullName.Split('.');
return parts.Length == 1 || (parts.Length == 2 && string.IsNullOrEmpty(parts[0])) ? "" : parts[^1];
}
}
}

View File

@@ -0,0 +1,7 @@
namespace FileTime.App.Core.ViewModels
{
public class ContainerSizeContainerViewModel : ItemViewModel, IContainerSizeContainerViewModel
{
}
}

View File

@@ -0,0 +1,6 @@
namespace FileTime.App.Core.ViewModels
{
public class ContainerViewModel : ItemViewModel, IContainerViewModel
{
}
}

View File

@@ -0,0 +1,7 @@
namespace FileTime.App.Core.ViewModels
{
public class ElementViewModel : ItemViewModel, IElementViewModel
{
}
}

View File

@@ -0,0 +1,7 @@
namespace FileTime.App.Core.ViewModels
{
public class FileViewModel : ElementViewModel, IFileViewModel
{
}
}

View File

@@ -0,0 +1,26 @@
using FileTime.App.Core.Models;
using FileTime.App.Core.Models.Enums;
using FileTime.Core.Models;
using MvvmGen;
namespace FileTime.App.Core.ViewModels
{
[ViewModel]
public abstract partial class ItemViewModel : IItemViewModel
{
[Property]
private IItem? _item;
[Property]
private IObservable<IReadOnlyList<ItemNamePart>>? _displayName;
[Property]
private IObservable<bool>? _isSelected;
[Property]
private IObservable<bool>? _isMarked;
[Property]
private ItemViewMode _viewMode;
}
}

View File

@@ -0,0 +1,105 @@
using System.Reactive.Linq;
using System.Reactive.Subjects;
using FileTime.App.Core.Services;
using FileTime.Core.Models;
using FileTime.Core.Services;
using Microsoft.Extensions.DependencyInjection;
namespace FileTime.App.Core.ViewModels
{
public class TabViewModel : ITabViewModel, IDisposable
{
private readonly IServiceProvider _serviceProvider;
private readonly IItemNameConverterService _itemNameConverterService;
private readonly IAppState _appState;
private readonly BehaviorSubject<IEnumerable<FullName>> _markedItems = new(Enumerable.Empty<FullName>());
private readonly List<IDisposable> _disposables = new();
private bool disposed;
public IObservable<IContainer?>? CurrentLocation { get; private set; }
public IObservable<IItemViewModel?>? CurrentSelectedItem { get; private set; }
public IObservable<IReadOnlyList<IItemViewModel>>? CurrentItems { get; private set; }
public IObservable<IReadOnlyList<FullName>> MarkedItems { get; }
public ITab? Tab { get; private set; }
public TabViewModel(
IServiceProvider serviceProvider,
IItemNameConverterService itemNameConverterService,
IAppState appState)
{
_serviceProvider = serviceProvider;
_itemNameConverterService = itemNameConverterService;
_appState = appState;
MarkedItems = _markedItems.Select(e => e.ToList()).AsObservable();
}
public void Init(ITab tab)
{
CurrentLocation = tab.CurrentLocation.AsObservable();
CurrentItems = tab.CurrentItems.Select(items => items.Select(MapItemToViewModel).ToList());
CurrentSelectedItem = CurrentItems.CombineLatest(
tab.CurrentSelectedItem,
(currentItems, currentSelectedItemPath) => currentItems.FirstOrDefault(i => i.Item?.FullName == currentSelectedItemPath?.Path));
tab.CurrentLocation.Subscribe((_) => _markedItems.OnNext(Enumerable.Empty<FullName>()));
Tab = tab;
}
private IItemViewModel MapItemToViewModel(IItem item)
{
if (item is IContainer container)
{
var containerViewModel = _serviceProvider.GetRequiredService<IContainerViewModel>();
InitIItemViewModel(containerViewModel, item);
return containerViewModel;
}
else if (item is IElement element)
{
var elementViewModel = _serviceProvider.GetRequiredService<IElementViewModel>();
InitIItemViewModel(elementViewModel, item);
return elementViewModel;
}
throw new ArgumentException($"{nameof(item)} is not {nameof(IContainer)} neighter {nameof(IElement)}");
void InitIItemViewModel(IItemViewModel itemViewModel, IItem item)
{
itemViewModel.Item = item;
itemViewModel.DisplayName = _appState.SearchText.Select(s => _itemNameConverterService.GetDisplayName(item.DisplayName, s));
itemViewModel.IsMarked = MarkedItems.Select(m => m.Contains(item.FullName));
itemViewModel.IsSelected = MarkedItems.Select(m => m.Contains(item.FullName));
}
}
~TabViewModel()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!disposed && disposing)
{
foreach (var disposable in _disposables)
{
try
{
disposable.Dispose();
}
catch { }
}
}
disposed = true;
}
}
}

View File

@@ -10,4 +10,8 @@
<PackageReference Include="System.Reactive" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Library\InitableService\InitableService.csproj" />
</ItemGroup>
</Project>

View File

@@ -3,5 +3,6 @@ namespace FileTime.Core.Models
public static class Constants
{
public const char SeparatorChar = '/';
public const int MaximumObservableMergeOperations = 4;
}
}

View File

@@ -1,7 +1,14 @@
using FileTime.Core.Models;
using InitableService;
namespace FileTime.Core.Services
{
public interface ITab
public interface ITab : IInitable<IContainer>
{
IObservable<IContainer?> CurrentLocation { get; }
IObservable<IAbsolutePath?> CurrentSelectedItem { get; }
IObservable<IEnumerable<IItem>> CurrentItems { get; }
void ChangeLocation(IContainer newLocation);
}
}

View File

@@ -7,6 +7,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
<PackageReference Include="System.Reactive" Version="5.0.0" />
</ItemGroup>

View File

@@ -1,7 +1,65 @@
using System.Reactive.Linq;
using System.Reactive.Subjects;
using FileTime.Core.Models;
namespace FileTime.Core.Services
{
public class Tab : ITab
{
private readonly BehaviorSubject<IContainer?> _currentLocation = new(null);
private readonly BehaviorSubject<IAbsolutePath?> _currentSelectedItem = new(null);
public IObservable<IContainer?> CurrentLocation { get; }
public IObservable<IEnumerable<IItem>> CurrentItems { get; }
public IObservable<IAbsolutePath?> CurrentSelectedItem { get; }
public Tab()
{
CurrentLocation = _currentLocation.AsObservable();
CurrentItems = _currentLocation
.Select(c =>
Observable.FromAsync(async () =>
c == null
? Enumerable.Empty<IItem>()
: await c.Items
.ToAsyncEnumerable()
.SelectAwait(
async i =>
{
try
{
//TODO: force create by AbsolutePath name
return await i.ContentProvider.GetItemByFullNameAsync(i.Path);
}
catch { return null!; }
}
)
.Where(i => i != null)
.ToListAsync()
)
)
.Merge(Constants.MaximumObservableMergeOperations);
CurrentSelectedItem = CurrentLocation.Select(GetSelectedItemByLocation).Merge(_currentSelectedItem).Throttle(TimeSpan.FromMilliseconds(500));
CurrentItems.Subscribe(c =>
{
;
});
}
public void Init(IContainer currentLocation)
{
_currentLocation.OnNext(currentLocation);
}
private IAbsolutePath? GetSelectedItemByLocation(IContainer? currentLocation)
{
return currentLocation?.Items[0];
}
public void ChangeLocation(IContainer newLocation)
{
_currentLocation.OnNext(newLocation);
}
}
}

View File

@@ -19,7 +19,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AppCommon", "AppCommon", "{
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Avalonia", "Avalonia", "{01F231DE-4A65-435F-B4BB-77EE5221890C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.GuiApp", "GuiApp\Avalonia\FileTime.GuiApp\FileTime.GuiApp.csproj", "{C389087E-EB78-4DCD-96AF-F1E2A4DEE0B0}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.GuiApp.App", "GuiApp\Avalonia\FileTime.GuiApp.App\FileTime.GuiApp.App.csproj", "{C389087E-EB78-4DCD-96AF-F1E2A4DEE0B0}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Providers", "Providers", "{2FC40FE1-4446-44AB-BF77-00F94D995FA3}"
EndProject
@@ -31,6 +31,20 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.ConsoleUI", "Conso
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.ConsoleUI.App", "ConsoleApp\FileTime.ConsoleUI.App\FileTime.ConsoleUI.App.csproj", "{77ABB06A-43AF-49D6-A802-A389CCB53724}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Library", "Library", "{07CA18AA-B85D-4DEE-BB86-F569F6029853}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InitableService", "Library\InitableService\InitableService.csproj", "{C67E3453-9730-40B5-8D2D-5D4EEBE008E2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.App.Core", "AppCommon\FileTime.App.Core\FileTime.App.Core.csproj", "{FC4AE72E-E302-45A0-BB9D-E87BB3250F0D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.App.Core.Abstraction", "AppCommon\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj", "{3E9CCD7F-86AA-4BD8-A100-0CD71BDB1AA0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.GuiApp", "GuiApp\Avalonia\FileTime.GuiApp\FileTime.GuiApp.csproj", "{26331AB9-6E4D-40DB-8FF0-CB7133F67CA0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.GuiApp.CustomImpl", "GuiApp\Avalonia\FileTime.GuiApp.CustomImpl\FileTime.GuiApp.CustomImpl.csproj", "{4B742649-225F-4C73-B118-1B29FE2A5774}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.Providers.Local.Abstractions", "Providers\FileTime.Providers.Local.Abstractions\FileTime.Providers.Local.Abstractions.csproj", "{1500A537-2116-4111-B216-7632040619B0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -69,6 +83,30 @@ Global
{77ABB06A-43AF-49D6-A802-A389CCB53724}.Debug|Any CPU.Build.0 = Debug|Any CPU
{77ABB06A-43AF-49D6-A802-A389CCB53724}.Release|Any CPU.ActiveCfg = Release|Any CPU
{77ABB06A-43AF-49D6-A802-A389CCB53724}.Release|Any CPU.Build.0 = Release|Any CPU
{C67E3453-9730-40B5-8D2D-5D4EEBE008E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C67E3453-9730-40B5-8D2D-5D4EEBE008E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C67E3453-9730-40B5-8D2D-5D4EEBE008E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C67E3453-9730-40B5-8D2D-5D4EEBE008E2}.Release|Any CPU.Build.0 = Release|Any CPU
{FC4AE72E-E302-45A0-BB9D-E87BB3250F0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FC4AE72E-E302-45A0-BB9D-E87BB3250F0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC4AE72E-E302-45A0-BB9D-E87BB3250F0D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC4AE72E-E302-45A0-BB9D-E87BB3250F0D}.Release|Any CPU.Build.0 = Release|Any CPU
{3E9CCD7F-86AA-4BD8-A100-0CD71BDB1AA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3E9CCD7F-86AA-4BD8-A100-0CD71BDB1AA0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3E9CCD7F-86AA-4BD8-A100-0CD71BDB1AA0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3E9CCD7F-86AA-4BD8-A100-0CD71BDB1AA0}.Release|Any CPU.Build.0 = Release|Any CPU
{26331AB9-6E4D-40DB-8FF0-CB7133F67CA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{26331AB9-6E4D-40DB-8FF0-CB7133F67CA0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{26331AB9-6E4D-40DB-8FF0-CB7133F67CA0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{26331AB9-6E4D-40DB-8FF0-CB7133F67CA0}.Release|Any CPU.Build.0 = Release|Any CPU
{4B742649-225F-4C73-B118-1B29FE2A5774}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4B742649-225F-4C73-B118-1B29FE2A5774}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4B742649-225F-4C73-B118-1B29FE2A5774}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4B742649-225F-4C73-B118-1B29FE2A5774}.Release|Any CPU.Build.0 = Release|Any CPU
{1500A537-2116-4111-B216-7632040619B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1500A537-2116-4111-B216-7632040619B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1500A537-2116-4111-B216-7632040619B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1500A537-2116-4111-B216-7632040619B0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -83,6 +121,12 @@ Global
{A55F4A86-03ED-4D25-807A-04619A1D9507} = {A5291117-3001-498B-AC8B-E14F71F72570}
{67D4102C-8B92-451C-B4A9-5A82B68A7A5D} = {CAEEAD3C-41EB-405C-ACA9-BA1E4C352549}
{77ABB06A-43AF-49D6-A802-A389CCB53724} = {CAEEAD3C-41EB-405C-ACA9-BA1E4C352549}
{C67E3453-9730-40B5-8D2D-5D4EEBE008E2} = {07CA18AA-B85D-4DEE-BB86-F569F6029853}
{FC4AE72E-E302-45A0-BB9D-E87BB3250F0D} = {A5291117-3001-498B-AC8B-E14F71F72570}
{3E9CCD7F-86AA-4BD8-A100-0CD71BDB1AA0} = {A5291117-3001-498B-AC8B-E14F71F72570}
{26331AB9-6E4D-40DB-8FF0-CB7133F67CA0} = {01F231DE-4A65-435F-B4BB-77EE5221890C}
{4B742649-225F-4C73-B118-1B29FE2A5774} = {01F231DE-4A65-435F-B4BB-77EE5221890C}
{1500A537-2116-4111-B216-7632040619B0} = {2FC40FE1-4446-44AB-BF77-00F94D995FA3}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {859FB3DF-C60A-46B1-82E5-90274905D1EF}

View File

@@ -0,0 +1,215 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FileTime.GuiApp"
x:Class="FileTime.GuiApp.App"
xmlns:converters="using:FileTime.GuiApp.Converters">
<Application.Resources>
<ResourceDictionary>
<Color x:Key="AppBackgroundColor">#E7073642</Color>
<Color x:Key="ContainerBackgroundColor">#083e4c</Color>
<Color x:Key="TransparentContainerBackgroundColor">#D0083e4c</Color>
<Color x:Key="BarelyTransparentBackgroundColor">#80083e4c</Color>
<Color x:Key="ItemBackgroundColor">#00000000</Color>
<Color x:Key="AlternativeItemBackgroundColor">#10000000</Color>
<Color x:Key="SelectedItemBackgroundColor">#93a1a1</Color>
<Color x:Key="MarkedItemBackgroundColor">#00000000</Color>
<Color x:Key="MarkedAlternativeItemBackgroundColor">#10000000</Color>
<Color x:Key="MarkedSelectedItemBackgroundColor">#b58900</Color>
<Color x:Key="ForegroundColor">#93a1a1</Color>
<Color x:Key="AccentColor">#268bd2</Color>
<Color x:Key="AccentComplementColor">#fdf6e3</Color>
<Color x:Key="LightForegroundColor">#7793a1a1</Color>
<Color x:Key="AlternativeItemForegroundColor">#93a1a1</Color>
<Color x:Key="SelectedItemForegroundColor">#073642</Color>
<Color x:Key="MarkedItemForegroundColor">#b58900</Color>
<Color x:Key="MarkedAlternativeItemForegroundColor">#b58900</Color>
<Color x:Key="MarkedSelectedItemForegroundColor">#002b36</Color>
<Color x:Key="ErrorColor">#dc322f</Color>
<LinearGradientBrush x:Key="ContentSeparatorBrush">
<GradientStop Offset="0" Color="#0093a1a1" />
<GradientStop Offset="0.5" Color="#93a1a1" />
<GradientStop Offset="1" Color="#0093a1a1" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="ContainerGradientBackgroundBrush">
<GradientStop Offset="0" Color="#00b58900" />
<GradientStop Offset="0.4" Color="#CCb58900" />
<GradientStop Offset="0.6" Color="#CCb58900" />
<GradientStop Offset="1" Color="#00b58900" />
</LinearGradientBrush>
<SolidColorBrush
x:Key="AppBackgroundBrush"
Color="{DynamicResource AppBackgroundColor}" />
<SolidColorBrush
x:Key="ContainerBackgroundBrush"
Color="{DynamicResource ContainerBackgroundColor}" />
<SolidColorBrush
x:Key="TransparentContainerBackgroundBrush"
Color="{DynamicResource TransparentContainerBackgroundColor}" />
<SolidColorBrush
x:Key="ItemBackgroundBrush"
Color="{DynamicResource ItemBackgroundColor}" />
<SolidColorBrush
x:Key="AlternativeItemBackgroundBrush"
Color="{DynamicResource AlternativeItemBackgroundColor}" />
<SolidColorBrush
x:Key="SelectedItemBackgroundBrush"
Color="{DynamicResource SelectedItemBackgroundColor}" />
<SolidColorBrush
x:Key="MarkedItemBackgroundBrush"
Color="{DynamicResource MarkedItemBackgroundColor}" />
<SolidColorBrush
x:Key="MarkedSelectedItemBackgroundBrush"
Color="{DynamicResource MarkedSelectedItemBackgroundColor}" />
<SolidColorBrush
x:Key="MarkedAlternativeItemBackgroundBrush"
Color="{DynamicResource MarkedAlternativeItemBackgroundColor}" />
<SolidColorBrush
x:Key="ForegroundBrush"
Color="{DynamicResource ForegroundColor}" />
<SolidColorBrush
x:Key="AccentBrush"
Color="{DynamicResource AccentColor}" />
<SolidColorBrush
x:Key="AccentComplementBrush"
Color="{DynamicResource AccentComplementColor}" />
<SolidColorBrush
x:Key="LightForegroundBrush"
Color="{DynamicResource LightForegroundColor}" />
<SolidColorBrush
x:Key="AlternativeItemForegroundBrush"
Color="{DynamicResource AlternativeItemForegroundColor}" />
<SolidColorBrush
x:Key="SelectedItemForegroundBrush"
Color="{DynamicResource SelectedItemForegroundColor}" />
<SolidColorBrush
x:Key="MarkedItemForegroundBrush"
Color="{DynamicResource MarkedItemForegroundColor}" />
<SolidColorBrush
x:Key="MarkedAlternativeItemForegroundBrush"
Color="{DynamicResource MarkedAlternativeItemForegroundColor}" />
<SolidColorBrush
x:Key="MarkedSelectedItemForegroundBrush"
Color="{DynamicResource MarkedSelectedItemForegroundColor}" />
<SolidColorBrush
x:Key="ErrorBrush"
Color="{DynamicResource ErrorColor}" />
<SolidColorBrush x:Key="SystemControlHighlightListAccentLowBrush" Color="{DynamicResource SelectedItemBackgroundColor}" />
<converters:ItemViewModeToBrushConverter
x:Key="ItemViewModeToForegroundConverter"
DefaultBrush="{StaticResource ForegroundBrush}"
AlternativeBrush="{StaticResource AlternativeItemForegroundBrush}"
SelectedBrush="{StaticResource SelectedItemForegroundBrush}"
MarkedBrush="{StaticResource MarkedItemForegroundBrush}"
MarkedAlternativeBrush="{StaticResource MarkedAlternativeItemForegroundBrush}"
MarkedSelectedBrush="{StaticResource MarkedSelectedItemForegroundBrush}"/>
<converters:ItemViewModeToBrushConverter
x:Key="ItemViewModeToBackgroundConverter"
DefaultBrush="{StaticResource ItemBackgroundBrush}"
AlternativeBrush="{StaticResource AlternativeItemBackgroundBrush}"
SelectedBrush="{StaticResource SelectedItemBackgroundBrush}"
MarkedBrush="{StaticResource MarkedItemBackgroundBrush}"
MarkedAlternativeBrush="{StaticResource MarkedAlternativeItemBackgroundBrush}"
MarkedSelectedBrush="{StaticResource MarkedSelectedItemBackgroundBrush}"/>
<converters:NamePartShrinkerConverter x:Key="NamePartShrinkerConverter"/>
<converters:ItemViewModelIsAttributeTypeConverter x:Key="ItemViewModelIsAttributeTypeConverter"/>
<converters:ItemViewModelIsAttributeTypeConverter x:Key="ItemViewModelIsNotAttributeTypeConverter" Invert="true"/>
<converters:GetFileExtensionConverter x:Key="GetFileExtensionConverter"/>
<converters:FormatSizeConverter x:Key="FormatSizeConverter"/>
<converters:DateTimeConverter x:Key="DateTimeConverter"/>
</ResourceDictionary>
</Application.Resources>
<Application.Styles>
<FluentTheme Mode="Dark"/>
<Style Selector="TextBlock">
<Setter Property="Foreground" Value="{DynamicResource ForegroundBrush}"/>
<Setter Property="FontSize" Value="16"/>
</Style>
<Style Selector="TextBlock.SmallText">
<Setter Property="FontSize" Value="12"/>
</Style>
<Style Selector="TextBlock.ExtraSmallText">
<Setter Property="FontSize" Value="11"/>
</Style>
<Style Selector="TextBox">
<Setter Property="Foreground" Value="{DynamicResource ForegroundBrush}"/>
<Setter Property="Background" Value="{DynamicResource ContainerBackgroundBrush}"/>
</Style>
<Style Selector="ListBox.ContentListView">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
</Style>
<Style Selector="ListBox.ContentListView > ListBoxItem">
<Setter Property="Margin" Value="0"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<!--Setter Property="ContextMenu">
<ContextMenu Items="{Binding Converter={StaticResource ContextMenuGenerator}}"/>
</Setter-->
</Style>
<Style Selector="Grid.SidebarContainerPresenter">
<Setter Property="Background" Value="#01000000"/>
</Style>
<Style Selector="Grid.SidebarContainerPresenter:pointerover">
<Setter Property="Background" Value="{DynamicResource AppBackgroundColor}"/>
</Style>
<Style Selector="Border.SelectedTimelineCommand">
<Setter Property="BorderBrush" Value="{DynamicResource ForegroundBrush}"/>
</Style>
<Style Selector="ListBox.RadioButtonListBox">
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="Background" Value="Transparent"/>
</Style>
<Style Selector="ListBox.RadioButtonListBox ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border>
<RadioButton
Content="{TemplateBinding ContentPresenter.Content}"
VerticalAlignment="Center"
IsChecked="{Binding Path=IsSelected,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}"
Foreground="{DynamicResource ForegroundBrush}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style Selector="Image.LoadingAnimation">
<Style.Animations>
<Animation Duration="0:0:2" IterationCount="INFINITE" Easing="QuadraticEaseInOut">
<KeyFrame Cue="0%">
<Setter Property="RotateTransform.Angle" Value="45"/>
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="RotateTransform.Angle" Value="405"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Application.Styles>
</Application>

View File

@@ -1,13 +1,29 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using FileTime.App.DependencyInjection;
using FileTime.GuiApp.ViewModels;
using FileTime.GuiApp.Views;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace FileTime.GuiApp
{
public partial class App : Application
{
static App()
{
DI.ServiceProvider ??= DependencyInjection
.RegisterDefaultServices()
.AddConfiguration()
.RegisterLogging()
.AddViewModels()
.BuildServiceProvider()
.InitSerilog();
var logger = DI.ServiceProvider.GetRequiredService<ILogger<App>>();
logger.LogInformation("App initialization completed.");
}
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
@@ -19,7 +35,7 @@ namespace FileTime.GuiApp
{
desktop.MainWindow = new MainWindow
{
DataContext = new MainWindowViewModel(),
DataContext = new MainWindowLoadingViewModel(),
};
}

View File

@@ -0,0 +1,44 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<!--Avalonia doesen't support TrimMode=link currently,but we are working on that https://github.com/AvaloniaUI/Avalonia/issues/6892 -->
<TrimMode>copyused</TrimMode>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationIcon>filetime.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Content Include="filetime.ico" />
</ItemGroup>
<ItemGroup>
<!--This helps with theme dll-s trimming.
If you will publish your application in self-contained mode with p:PublishTrimmed=true and it will use Fluent theme Default theme will be trimmed from the output and vice versa.
https://github.com/AvaloniaUI/Avalonia/issues/5593 -->
<TrimmableAssembly Include="Avalonia.Themes.Fluent" />
<TrimmableAssembly Include="Avalonia.Themes.Default" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.13" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.13" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.13" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.13" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="3.1.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.3.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.3.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\AppCommon\FileTime.App.Core\FileTime.App.Core.csproj" />
<ProjectReference Include="..\..\..\AppCommon\FileTime.App.DependencyInjection\FileTime.App.DependencyInjection.csproj" />
<ProjectReference Include="..\FileTime.GuiApp.CustomImpl\FileTime.GuiApp.CustomImpl.csproj" />
<ProjectReference Include="..\FileTime.GuiApp\FileTime.GuiApp.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<Content Include="appsettings.Development.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,62 @@
using System;
using System.IO;
using Avalonia;
using FileTime.GuiApp;
namespace FileTime.GuiApp
{
public static class Program
{
public static string AppDataRoot { get; }
public static string EnvironmentName { get; }
static Program()
{
#if DEBUG
EnvironmentName = "Development";
AppDataRoot = Path.Combine(Environment.CurrentDirectory, "appdata");
#else
EnvironmentName = "Release";
var possibleDataRootsPaths = new List<string>()
{
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FileTime"),
Path.Combine(Assembly.GetEntryAssembly()?.Location ?? ".", "fallbackDataRoot")
};
string? appDataRoot = null;
foreach (var possibleAppDataRoot in possibleDataRootsPaths)
{
try
{
var appDataRootDirectory = new DirectoryInfo(possibleAppDataRoot);
if (!appDataRootDirectory.Exists) appDataRootDirectory.Create();
//TODO write test
appDataRoot = possibleAppDataRoot;
break;
}
catch { }
}
if (appDataRoot == null) throw new UnauthorizedAccessException();
AppDataRoot = appDataRoot;
#endif
}
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace();
}
}

View File

@@ -0,0 +1,77 @@
using System;
using System.IO;
using FileTime.App.Core;
using FileTime.App.Core.Services;
using FileTime.App.Core.ViewModels;
using FileTime.Core.Services;
using FileTime.GuiApp.Configuration;
using FileTime.GuiApp.Logging;
using FileTime.GuiApp.ViewModels;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Serilog;
using Serilog.Configuration;
namespace FileTime.GuiApp
{
public static class Startup
{
internal static IServiceCollection AddViewModels(this IServiceCollection serviceCollection)
{
return serviceCollection
.AddSingleton<MainWindowViewModel>()
.AddSingleton<AppState>()
.AddSingleton<IAppState, AppState>(s => s.GetRequiredService<AppState>())
//TODO: move??
.AddTransient<ITab, Tab>()
.AddTransient<ITabViewModel, TabViewModel>()
.AddTransient<IContainerViewModel, ContainerViewModel>()
.AddTransient<IElementViewModel, ElementViewModel>()
.AddTransient<IItemNameConverterService, ItemNameConverterService>();
}
internal static IServiceCollection RegisterLogging(this IServiceCollection serviceCollection)
{
return serviceCollection.AddLogging(loggingBuilder =>
loggingBuilder.AddSerilog(dispose: true)
);
}
internal static IServiceCollection AddConfiguration(this IServiceCollection serviceCollection)
{
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(MainConfiguration.Configuration)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{Program.EnvironmentName}.json", true, true)
.Build();
return serviceCollection
.Configure<ProgramsConfiguration>(configuration.GetSection(SectionNames.ProgramsSectionName))
.Configure<KeyBindingConfiguration>(configuration.GetSection(SectionNames.KeybindingSectionName))
.AddSingleton<IConfiguration>(configuration);
}
internal static IServiceProvider InitSerilog(this IServiceProvider serviceProvider)
{
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(serviceProvider.GetService<IConfiguration>())
.Enrich.FromLogContext()
.WriteTo.File(
Path.Combine(Program.AppDataRoot, "logs", "appLog.log"),
fileSizeLimitBytes: 10 * 1024 * 1024,
rollOnFileSizeLimit: true,
rollingInterval: RollingInterval.Day)
.WriteTo.MessageBoxSink(serviceProvider)
.CreateLogger();
return serviceProvider;
}
internal static LoggerConfiguration MessageBoxSink(
this LoggerSinkConfiguration loggerConfiguration,
IServiceProvider serviceProvider)
{
return loggerConfiguration.Sink(serviceProvider.GetService<ToastMessageSink>());
}
}
}

View File

@@ -0,0 +1,11 @@
{
"Serilog": {
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Information",
"System": "Warning"
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\AppCommon\FileTime.App.Core\FileTime.App.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,6 @@
using FileTime.App.Core;
namespace FileTime.GuiApp.ViewModels
{
public class AppState : AppStateBase { }
}

View File

@@ -1,11 +0,0 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FileTime.GuiApp"
x:Class="FileTime.GuiApp.App">
<Application.DataTemplates>
</Application.DataTemplates>
<Application.Styles>
<FluentTheme Mode="Light"/>
</Application.Styles>
</Application>

View File

@@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Input;
using FileTime.App.Core.Command;
namespace FileTime.GuiApp.Configuration
{
public class CommandBindingConfiguration
{
public List<KeyConfig> Keys { get; set; } = new List<KeyConfig>();
public Commands Command { get; set; } = Commands.None;
public string KeysDisplayText => GetKeysDisplayText();
public CommandBindingConfiguration() { }
public CommandBindingConfiguration(Commands command, IEnumerable<KeyConfig> keys)
{
Keys = new List<KeyConfig>(keys);
Command = command;
}
public CommandBindingConfiguration(Commands command, KeyConfig key)
{
Keys = new List<KeyConfig>() { key };
Command = command;
}
public CommandBindingConfiguration(Commands command, IEnumerable<Key> keys)
{
Keys = keys.Select(k => new KeyConfig(k)).ToList();
Command = command;
}
public CommandBindingConfiguration(Commands command, Key key)
{
Keys = new List<KeyConfig>() { new KeyConfig(key) };
Command = command;
}
public string GetKeysDisplayText()
{
var s = "";
foreach (var k in Keys)
{
var keyString = k.Key.ToString();
if (keyString.Length == 1)
{
s += AddKeyWithCtrlOrAlt(k, s, (_, _, _) => k.Shift ? keyString.ToUpper() : keyString.ToLower());
}
else
{
s += AddKeyWithCtrlOrAlt(k, s, AddSpecialKey);
}
}
return s;
}
private static string AddKeyWithCtrlOrAlt(KeyConfig key, string currentText, Func<KeyConfig, string, bool, string> keyProcessor)
{
var s = "";
bool ctrlOrAlt = key.Ctrl || key.Alt;
if (ctrlOrAlt && currentText.Length > 0 && currentText.Last() != ' ') s += " ";
if (key.Ctrl) s += "CTRL+";
if (key.Alt) s += "ALT+";
s += keyProcessor(key, currentText, ctrlOrAlt);
if (ctrlOrAlt) s += " ";
return s;
}
private static string AddSpecialKey(KeyConfig key, string currentText, bool wasCtrlOrAlt)
{
var s = "";
if (currentText.Length > 0 && currentText.Last() != ' ' && !wasCtrlOrAlt) s += " ";
s += key.Key.ToString();
if (!wasCtrlOrAlt) s += " ";
return s;
}
}
}

View File

@@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace FileTime.GuiApp.Configuration
{
public class KeyBindingConfiguration
{
public bool UseDefaultBindings { get; set; } = true;
public List<CommandBindingConfiguration> DefaultKeyBindings { get; set; } = new();
public List<CommandBindingConfiguration> KeyBindings { get; set; } = new();
}
}

View File

@@ -0,0 +1,22 @@
using Avalonia.Input;
namespace FileTime.GuiApp.Configuration
{
public class KeyConfig
{
public Key Key { get; set; }
public bool Shift { get; set; }
public bool Alt { get; set; }
public bool Ctrl { get; set; }
public KeyConfig() { }
public KeyConfig(Key key, bool shift = false, bool alt = false, bool ctrl = false)
{
Key = key;
Shift = shift;
Alt = alt;
Ctrl = ctrl;
}
}
}

View File

@@ -0,0 +1,128 @@
using Avalonia.Input;
using FileTime.App.Core.Command;
using System;
using System.Collections.Generic;
namespace FileTime.GuiApp.Configuration
{
public static class MainConfiguration
{
private static readonly Lazy<List<CommandBindingConfiguration>> _defaultKeybindings = new(InitDefaultKeyBindings);
public static Dictionary<string, string> Configuration { get; }
static MainConfiguration()
{
Configuration = new();
PopulateDefaultEditorPrograms(Configuration);
PopulateDefaultKeyBindings(Configuration, _defaultKeybindings.Value, SectionNames.KeybindingSectionName + ":" + nameof(KeyBindingConfiguration.DefaultKeyBindings));
}
private static void PopulateDefaultKeyBindings(Dictionary<string, string> configuration, List<CommandBindingConfiguration> commandBindingConfigs, string basePath)
{
for (var i = 0; i < commandBindingConfigs.Count; i++)
{
var baseKey = basePath + $":[{i}]:";
var commandBindingConfig = commandBindingConfigs[i];
configuration.Add(baseKey + nameof(CommandBindingConfiguration.Command), commandBindingConfig.Command.ToString());
for (var j = 0; j < commandBindingConfig.Keys.Count; j++)
{
var key = commandBindingConfig.Keys[j];
var keyBaseKey = baseKey + $"keys:[{j}]:";
configuration.Add(keyBaseKey + nameof(KeyConfig.Key), key.Key.ToString());
configuration.Add(keyBaseKey + nameof(KeyConfig.Shift), key.Shift.ToString());
configuration.Add(keyBaseKey + nameof(KeyConfig.Alt), key.Alt.ToString());
configuration.Add(keyBaseKey + nameof(KeyConfig.Ctrl), key.Ctrl.ToString());
}
}
}
private static List<CommandBindingConfiguration> InitDefaultKeyBindings()
{
return new List<CommandBindingConfiguration>()
{
new CommandBindingConfiguration(Commands.AutoRefresh, new KeyConfig(Key.R, shift: true)),
new CommandBindingConfiguration(Commands.ChangeTimelineMode, new[] { Key.T, Key.M }),
new CommandBindingConfiguration(Commands.CloseTab, Key.Q),
new CommandBindingConfiguration(Commands.Compress, new[] { Key.Y, Key.C }),
new CommandBindingConfiguration(Commands.Copy, new[] { Key.Y, Key.Y }),
new CommandBindingConfiguration(Commands.CopyHash, new[] { Key.C, Key.H }),
new CommandBindingConfiguration(Commands.CopyPath, new[] { Key.C, Key.P }),
new CommandBindingConfiguration(Commands.CreateContainer, Key.F7),
new CommandBindingConfiguration(Commands.CreateContainer, new[] { Key.C, Key.C }),
new CommandBindingConfiguration(Commands.CreateElement, new[] { Key.C, Key.E }),
new CommandBindingConfiguration(Commands.Cut, new[] { Key.D, Key.D }),
new CommandBindingConfiguration(Commands.Edit, new KeyConfig(Key.F4)),
new CommandBindingConfiguration(Commands.EnterRapidTravel, new KeyConfig(Key.OemComma, shift: true)),
new CommandBindingConfiguration(Commands.FindByName, new[] { Key.F, Key.N }),
new CommandBindingConfiguration(Commands.FindByNameRegex, new[] { Key.F, Key.R }),
new CommandBindingConfiguration(Commands.GoToHome, new[] { Key.G, Key.H }),
new CommandBindingConfiguration(Commands.GoToPath, new KeyConfig(Key.L, ctrl: true)),
new CommandBindingConfiguration(Commands.GoToPath, new[] { Key.G, Key.P }),
new CommandBindingConfiguration(Commands.GoToProvider, new[] { Key.G, Key.T }),
new CommandBindingConfiguration(Commands.GoToRoot, new[] { Key.G, Key.R }),
new CommandBindingConfiguration(Commands.HardDelete, new[] { new KeyConfig(Key.D,shift: true), new KeyConfig(Key.D, shift: true) }),
new CommandBindingConfiguration(Commands.Mark, Key.Space),
new CommandBindingConfiguration(Commands.MoveToLast, new KeyConfig(Key.G, shift: true)),
new CommandBindingConfiguration(Commands.MoveToFirst, new[] { Key.G, Key.G }),
new CommandBindingConfiguration(Commands.NextTimelineBlock, Key.L ),
new CommandBindingConfiguration(Commands.NextTimelineCommand, Key.J ),
new CommandBindingConfiguration(Commands.OpenInFileBrowser, new[] { Key.O, Key.E }),
new CommandBindingConfiguration(Commands.PasteMerge, new[] { Key.P, Key.P }),
new CommandBindingConfiguration(Commands.PasteOverwrite, new[] { Key.P, Key.O }),
new CommandBindingConfiguration(Commands.PasteSkip, new[] { Key.P, Key.S }),
new CommandBindingConfiguration(Commands.PinFavorite, new[] { Key.F, Key.P }),
new CommandBindingConfiguration(Commands.PreviousTimelineBlock, Key.H ),
new CommandBindingConfiguration(Commands.PreviousTimelineCommand, Key.K ),
new CommandBindingConfiguration(Commands.Refresh, Key.R),
new CommandBindingConfiguration(Commands.Rename, Key.F2),
new CommandBindingConfiguration(Commands.Rename, new[] { Key.C, Key.W }),
new CommandBindingConfiguration(Commands.RunCommand, new KeyConfig(Key.D4, shift: true)),
new CommandBindingConfiguration(Commands.ScanContainerSize, new[] { Key.C, Key.S }),
new CommandBindingConfiguration(Commands.ShowAllShotcut, Key.F1),
new CommandBindingConfiguration(Commands.SoftDelete, new[] { new KeyConfig(Key.D), new KeyConfig(Key.D, shift: true) }),
new CommandBindingConfiguration(Commands.SwitchToLastTab, Key.D9),
new CommandBindingConfiguration(Commands.SwitchToTab1, Key.D1),
new CommandBindingConfiguration(Commands.SwitchToTab2, Key.D2),
new CommandBindingConfiguration(Commands.SwitchToTab3, Key.D3),
new CommandBindingConfiguration(Commands.SwitchToTab4, Key.D4),
new CommandBindingConfiguration(Commands.SwitchToTab5, Key.D5),
new CommandBindingConfiguration(Commands.SwitchToTab6, Key.D6),
new CommandBindingConfiguration(Commands.SwitchToTab7, Key.D7),
new CommandBindingConfiguration(Commands.SwitchToTab8, Key.D8),
new CommandBindingConfiguration(Commands.TimelinePause, new[] { Key.T, Key.P }),
new CommandBindingConfiguration(Commands.TimelineRefresh, new[] { Key.T, Key.R }),
new CommandBindingConfiguration(Commands.TimelineStart, new[] { Key.T, Key.S }),
new CommandBindingConfiguration(Commands.ToggleAdvancedIcons, new[] { Key.Z, Key.I }),
new CommandBindingConfiguration(Commands.GoUp, Key.Left),
new CommandBindingConfiguration(Commands.Open, Key.Right),
new CommandBindingConfiguration(Commands.OpenOrRun, Key.Enter),
new CommandBindingConfiguration(Commands.MoveCursorUp, Key.Up),
new CommandBindingConfiguration(Commands.MoveCursorDown, Key.Down),
new CommandBindingConfiguration(Commands.MoveCursorUpPage, Key.PageUp),
new CommandBindingConfiguration(Commands.MoveCursorDownPage, Key.PageDown),
};
}
private static void PopulateDefaultEditorPrograms(Dictionary<string, string> configuration)
{
var editorPrograms = new List<ProgramConfiguration>()
{
new ProgramConfiguration(@"c:\Program Files\Notepad++\notepad++.exe"),
new ProgramConfiguration("notepad.exe"),
};
for (var i = 0; i < editorPrograms.Count; i++)
{
if (editorPrograms[i].Path is not string path) continue;
configuration.Add($"{SectionNames.ProgramsSectionName}:{nameof(ProgramsConfiguration.DefaultEditorPrograms)}:[{i}]:{nameof(ProgramConfiguration.Path)}", path);
if (editorPrograms[i].Arguments is string arguments)
{
configuration.Add($"{SectionNames.ProgramsSectionName}:{nameof(ProgramsConfiguration.DefaultEditorPrograms)}:[{i}]:{nameof(ProgramConfiguration.Arguments)}", arguments);
}
}
}
}
}

View File

@@ -0,0 +1,16 @@
namespace FileTime.GuiApp.Configuration
{
public class ProgramConfiguration
{
public string? Path { get; set; }
public string? Arguments { get; set; }
public ProgramConfiguration() { }
public ProgramConfiguration(string? path, string? arguments = null)
{
Path = path;
Arguments = arguments;
}
}
}

View File

@@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace FileTime.GuiApp.Configuration
{
public class ProgramsConfiguration
{
public List<ProgramConfiguration> DefaultEditorPrograms { get; set; } = new();
public List<ProgramConfiguration> EditorPrograms { get; set; } = new();
}
}

View File

@@ -0,0 +1,8 @@
namespace FileTime.GuiApp.Configuration
{
public static class SectionNames
{
public const string KeybindingSectionName = "KeyBindings";
public const string ProgramsSectionName = "Programs";
}
}

View File

@@ -0,0 +1,18 @@
using System.Globalization;
using Avalonia.Data.Converters;
namespace FileTime.GuiApp.Converters
{
public class DateTimeConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) =>
value is DateTime dateTime && parameter is string parameterS
? dateTime.ToString(parameterS)
: value;
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,47 @@
using System.Globalization;
using Avalonia.Data.Converters;
namespace FileTime.GuiApp.Converters
{
public class FormatSizeConverter : IValueConverter
{
private const long OneKiloByte = 1024;
private const long OneMegaByte = OneKiloByte * 1024;
private const long OneGigaByte = OneMegaByte * 1024;
private const long OneTerraByte = OneGigaByte * 1024;
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
return (value, int.TryParse(parameter?.ToString(), out var prec)) switch
{
(long size, true) => ToSizeString(size, prec),
(long size, false) => ToSizeString(size),
(null, _) => "...",
_ => value
};
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public static string ToSizeString(long fileSize, int precision = 1)
{
var fileSizeD = (decimal)fileSize;
var (size, suffix) = fileSize switch
{
> OneTerraByte => (fileSizeD / OneTerraByte, "T"),
> OneGigaByte => (fileSizeD / OneGigaByte, "G"),
> OneMegaByte => (fileSizeD / OneMegaByte, "M"),
> OneKiloByte => (fileSizeD / OneKiloByte, "K"),
_ => (fileSizeD, "B")
};
var result = string.Format("{0:N" + precision + "}", size).Replace(',', '.');
if (result.Contains('.')) result = result.TrimEnd('0').TrimEnd('.');
return result + " " + suffix;
}
}
}

View File

@@ -0,0 +1,24 @@
using System.Globalization;
using Avalonia.Data.Converters;
using FileTime.App.Core.Services;
using Microsoft.Extensions.DependencyInjection;
namespace FileTime.GuiApp.Converters
{
public class GetFileExtensionConverter : IValueConverter
{
private IItemNameConverterService? _itemNameConverterService;
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is not string fullName) return value;
_itemNameConverterService ??= DI.ServiceProvider.GetRequiredService<IItemNameConverterService>();
return _itemNameConverterService.GetFileExtension(fullName);
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,41 @@
using System.Globalization;
using Avalonia.Data.Converters;
using Avalonia.Media;
using FileTime.App.Core.Models.Enums;
namespace FileTime.GuiApp.Converters
{
public class ItemViewModeToBrushConverter : IValueConverter
{
public Brush? DefaultBrush { get; set; }
public Brush? AlternativeBrush { get; set; }
public Brush? SelectedBrush { get; set; }
public Brush? MarkedBrush { get; set; }
public Brush? MarkedSelectedBrush { get; set; }
public Brush? MarkedAlternativeBrush { get; set; }
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is ItemViewMode viewMode)
{
return viewMode switch
{
ItemViewMode.Default => DefaultBrush,
ItemViewMode.Alternative => AlternativeBrush,
ItemViewMode.Selected => SelectedBrush,
ItemViewMode.Marked => MarkedBrush,
ItemViewMode.MarkedSelected => MarkedSelectedBrush,
ItemViewMode.MarkedAlternative => MarkedAlternativeBrush,
_ => throw new NotImplementedException()
};
}
return value;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,37 @@
using System.Globalization;
using Avalonia.Data.Converters;
using FileTime.App.Core.Models.Enums;
using FileTime.App.Core.ViewModels;
namespace FileTime.GuiApp.Converters
{
public class ItemViewModelIsAttributeTypeConverter : IValueConverter
{
public bool Invert { get; set; }
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
var attributeType = GetAttributeType(value);
if (parameter == null) return attributeType;
var result = parameter is ItemAttributeType targetAttribute && attributeType == targetAttribute;
if (Invert && parameter is ItemAttributeType) result = !result;
return result;
}
private static ItemAttributeType? GetAttributeType(object? value)
{
return value switch
{
IFileViewModel => ItemAttributeType.File,
IContainerSizeContainerViewModel => ItemAttributeType.SizeContainer,
IElementViewModel => ItemAttributeType.Element,
IContainerViewModel => ItemAttributeType.Container,
_ => null
};
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,115 @@
using System.Globalization;
using Avalonia.Data.Converters;
using Avalonia.Media;
using FileTime.App.Core.Models;
using FileTime.GuiApp.ViewModels;
namespace FileTime.GuiApp.Converters
{
public class NamePartShrinkerConverter : IMultiValueConverter
{
private const int PixelPerChar = 8;
public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
{
if (values.Count > 0 && values[0] is IList<ItemNamePart> nameParts)
{
var attributeWidth = values[2] is bool b && b ? 340 : 0;
var newNameParts = nameParts;
if (values.Count > 1 && values[1] is double width && width > 0)
{
newNameParts = GetNamePartsForWidth(nameParts, width - attributeWidth);
}
return newNameParts.Select(p => new ItemNamePartViewModel(p.Text, p.IsSpecial ? TextDecorations.Underline : null)).ToList();
}
return null;
}
private static List<ItemNamePart> GetNamePartsForWidth(IList<ItemNamePart> nameParts, double maxWidth)
{
//Best case, we are in the range
var textLength = nameParts.Select(p => p.Text.Length).Sum();
if (textLength * PixelPerChar <= maxWidth)
{
return nameParts.ToList();
}
//Trying at least with the special parts
var newNameParts = new ItemNamePart?[nameParts.Count];
for (var i = 0; i < nameParts.Count; i++)
{
if (nameParts[i].IsSpecial)
{
newNameParts[i] = nameParts[i];
}
}
return GetNamePartsForWidthOptimistic(nameParts, newNameParts, maxWidth);
}
private static List<ItemNamePart> GetNamePartsForWidthOptimistic(IList<ItemNamePart> nameParts, ItemNamePart?[] newNameParts, double maxWidth)
{
var trimmedIndexes = new List<int>();
for (var i = 0; i < newNameParts.Length; i++)
{
if (newNameParts[i] == null)
{
trimmedIndexes.Add(i);
newNameParts[i] = new ItemNamePart("...");
}
}
var textLength = newNameParts.Select(p => p?.Text.Length ?? 0).Sum();
if (textLength * PixelPerChar > maxWidth)
{
return GetNamePartsForWidthPessimistic(nameParts, maxWidth);
}
foreach (var trimmedIndex in trimmedIndexes)
{
var baseTextLength = newNameParts.Select((p, i) => i == trimmedIndex ? 0 : (p?.Text.Length ?? 0)).Sum();
var proposedText = nameParts[trimmedIndex].Text;
var trimmed = false;
while ((baseTextLength + proposedText.Length + (trimmed ? 3 : 0)) * PixelPerChar > maxWidth)
{
proposedText = proposedText[0..^1];
trimmed = true;
}
newNameParts[trimmedIndex] = new ItemNamePart(proposedText + (trimmed ? "..." : ""));
if (trimmed) break;
}
return newNameParts.OfType<ItemNamePart>().ToList();
}
private static List<ItemNamePart> GetNamePartsForWidthPessimistic(IList<ItemNamePart> nameParts, double maxWidth)
{
var newNameParts = new List<ItemNamePart>(nameParts);
foreach (var namePart in nameParts)
{
var baseTextLength = newNameParts.Select(p => p.Text.Length).Sum();
var proposedText = namePart.Text;
var trimmed = false;
while ((baseTextLength + 3) * PixelPerChar > maxWidth && proposedText != "")
{
proposedText = proposedText[0..^1];
trimmed = true;
}
if (!string.IsNullOrWhiteSpace(proposedText)) newNameParts.Add(new ItemNamePart(proposedText, namePart.IsSpecial));
if (trimmed) break;
}
if (newNameParts.Last().IsSpecial)
{
newNameParts.Add(new ItemNamePart("..."));
}
else
{
var last = newNameParts.Last();
last.Text += "...";
}
return newNameParts;
}
}
}

View File

@@ -0,0 +1,7 @@
namespace FileTime.GuiApp
{
public static class DI
{
public static IServiceProvider ServiceProvider { get; set; } = null!;
}
}

View File

@@ -1,36 +1,47 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!--Avalonia doesen't support TrimMode=link currently,but we are working on that https://github.com/AvaloniaUI/Avalonia/issues/6892 -->
<TrimMode>copyused</TrimMode>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationIcon>Assets\filetime.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<None Remove="Assets\filetime.ico" />
</ItemGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**" />
<None Remove=".gitignore" />
</ItemGroup>
<ItemGroup>
<Content Include="Assets\filetime.ico" />
<AvaloniaXaml Update="Views\MainWindow.axaml">
<Generator>MSBuild:Compile</Generator>
</AvaloniaXaml>
</ItemGroup>
<ItemGroup>
<!--This helps with theme dll-s trimming.
If you will publish your application in self-contained mode with p:PublishTrimmed=true and it will use Fluent theme Default theme will be trimmed from the output and vice versa.
https://github.com/AvaloniaUI/Avalonia/issues/5593 -->
<TrimmableAssembly Include="Avalonia.Themes.Fluent" />
<TrimmableAssembly Include="Avalonia.Themes.Default" />
<Compile Update="Views\MainWindow.axaml.cs">
<DependentUpon>MainWindow.axaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.13" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.13" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.13" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.13" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.3.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="MvvmGen" Version="1.1.5" />
<PackageReference Include="Serilog.Extensions.Logging" Version="3.1.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.3.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.3.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\AppCommon\FileTime.App.DependencyInjection\FileTime.App.DependencyInjection.csproj" />
<ProjectReference Include="..\..\..\AppCommon\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
<ProjectReference Include="..\..\..\Providers\FileTime.Providers.Local.Abstractions\FileTime.Providers.Local.Abstractions.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,24 @@
using Serilog.Core;
using Serilog.Events;
namespace FileTime.GuiApp.Logging
{
public class ToastMessageSink : ILogEventSink
{
//private readonly IDialogService dialogService;
public ToastMessageSink(/*IDialogService dialogService*/)
{
//this.dialogService = dialogService;
}
public void Emit(LogEvent logEvent)
{
/*if (logEvent.Level >= LogEventLevel.Error)
{
var message = logEvent.RenderMessage();
dialogService.ShowToastMessage(message);
}*/
}
}
}

View File

@@ -0,0 +1,20 @@
namespace FileTime.GuiApp.Models
{
public class InputElementWrapper
{
/*public InputElement InputElement { get; }
public string Value { get; set; }
public object? Option { get; set; }
public char? PasswordChar { get; set; }
public InputElementWrapper(InputElement inputElement, string? defaultValue = null)
{
InputElement = inputElement;
Value = defaultValue ?? "";
PasswordChar = inputElement.InputType == InputType.Password ? '*' : null;
}*/
}
}

View File

@@ -1,23 +0,0 @@
using System;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.ReactiveUI;
namespace FileTime.GuiApp
{
class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.LogToTrace();
}
}

View File

@@ -0,0 +1,7 @@
namespace FileTime.GuiApp.ViewModels
{
public interface IMainWindowViewModelBase
{
bool Loading { get; }
}
}

View File

@@ -0,0 +1,16 @@
using Avalonia.Media;
namespace FileTime.GuiApp.ViewModels
{
public class ItemNamePartViewModel
{
public string Text { get; set; }
public TextDecorationCollection? TextDecorations { get; set; }
public ItemNamePartViewModel(string text, TextDecorationCollection? textDecorations)
{
Text = text;
TextDecorations = textDecorations;
}
}
}

View File

@@ -0,0 +1,7 @@
namespace FileTime.GuiApp.ViewModels
{
public class MainWindowLoadingViewModel : IMainWindowViewModelBase
{
public bool Loading => true;
}
}

View File

@@ -1,10 +1,37 @@
using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Input;
using FileTime.App.Core;
using FileTime.App.Core.ViewModels;
using FileTime.Core.Models;
using FileTime.Core.Services;
using FileTime.Providers.Local;
using Microsoft.Extensions.DependencyInjection;
using MvvmGen;
namespace FileTime.GuiApp.ViewModels
{
public class MainWindowViewModel
[ViewModel]
[Inject(typeof(IAppState), "_appState")]
[Inject(typeof(ILocalContentProvider), "_localContentProvider")]
[Inject(typeof(IServiceProvider), PropertyName = "_serviceProvider")]
public partial class MainWindowViewModel : IMainWindowViewModelBase
{
public bool Loading => false;
public IAppState AppState => _appState;
partial void OnInitialize()
{
if (AppState.Tabs.Count == 0)
{
var tab = _serviceProvider.GetInitableResolver<IContainer>(_localContentProvider).GetRequiredService<ITab>();
var tabViewModel = _serviceProvider.GetInitableResolver(tab).GetRequiredService<ITabViewModel>();
_appState.AddTab(tabViewModel);
}
}
public void ProcessKeyDown(Key key, KeyModifiers keyModifiers, Action<bool> setHandled)
{
}
}
}

View File

@@ -0,0 +1,89 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:appcoreenums="using:FileTime.App.Core.Models.Enums"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="FileTime.GuiApp.Views.ItemView"
x:Name="ItemRoot"
HorizontalContentAlignment="Stretch"
HorizontalAlignment="Stretch"
Background="{Binding ViewMode,Converter={StaticResource ItemViewModeToBackgroundConverter}}">
<Grid ColumnDefinitions="20,*,Auto" Margin="3" x:Name="RootGrid">
<Grid.Styles>
<Style Selector="TextBlock">
<Setter Property="Foreground" Value="{Binding DataContext.ViewMode,Converter={StaticResource ItemViewModeToForegroundConverter},ElementName=ItemRoot}"/>
</Style>
</Grid.Styles>
<!--Image
Width="18"
Height="18"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Source="{Binding Converter={StaticResource ItemToImageConverter}}" /-->
<ItemsControl
Margin="5,0,0,0"
Grid.Column="1"
VerticalAlignment="Center"
HorizontalAlignment="Stretch">
<ItemsControl.Items>
<MultiBinding Converter="{StaticResource NamePartShrinkerConverter}">
<MultiBinding.Bindings>
<Binding Path="DisplayName^"/>
<Binding ElementName="RootGrid" Path="Bounds.Width"/>
<Binding ElementName="ItemRoot" Path="ShowAttributes"/>
</MultiBinding.Bindings>
</MultiBinding>
</ItemsControl.Items>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock
Text="{Binding Text}"
TextDecorations="{Binding TextDecorations}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Grid Grid.Column="2" IsVisible="{Binding ShowAttributes,ElementName=ItemRoot}">
<Grid ColumnDefinitions="50,50,90,40,45"
HorizontalAlignment="Right"
IsVisible="{Binding Converter={StaticResource ItemViewModelIsAttributeTypeConverter},ConverterParameter={x:Static appcoreenums:ItemAttributeType.File}}">
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Text="{Binding BaseItem.DisplayName, Converter={StaticResource GetFileExtensionConverter}}"/>
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Grid.Column="1" Text="{Binding Size, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}"/>
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Grid.Column="2" Text="{Binding BaseItem.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter=yyyy-MM-dd}"/>
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Grid.Column="3" Text="{Binding BaseItem.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter=hh:mm}"/>
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Grid.Column="4" Text="{Binding BaseItem.Attributes}"/>
</Grid>
<Grid ColumnDefinitions="90,40,45"
HorizontalAlignment="Right"
IsVisible="{Binding Converter={StaticResource ItemViewModelIsAttributeTypeConverter},ConverterParameter={x:Static appcoreenums:ItemAttributeType.Container}}">
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Text="{Binding Item.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter=yyyy-MM-dd}"/>
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Grid.Column="1" Text="{Binding BaseItem.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter=hh:mm}"/>
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Grid.Column="2" Text="{Binding BaseItem.Attributes}"/>
</Grid>
<Grid ColumnDefinitions="50,90,40,45"
HorizontalAlignment="Right"
IsVisible="{Binding Converter={StaticResource ItemViewModelIsAttributeTypeConverter},ConverterParameter={x:Static appcoreenums:ItemAttributeType.SizeContainer}}">
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Text="{Binding Size, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}"/>
</Grid>
<Grid ColumnDefinitions="50,50"
HorizontalAlignment="Right"
IsVisible="{Binding Converter={StaticResource ItemViewModelIsAttributeTypeConverter},ConverterParameter={x:Static appcoreenums:ItemAttributeType.Element}}">
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Text="{Binding BaseItem.DisplayName, Converter={StaticResource GetFileExtensionConverter}}"/>
<TextBlock HorizontalAlignment="Right" Classes="SmallText" Grid.Column="1" Text="{Binding Size, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}"/>
</Grid>
</Grid>
</Grid>
</UserControl>

View File

@@ -0,0 +1,21 @@
using Avalonia;
using Avalonia.Controls;
namespace FileTime.GuiApp.Views
{
public partial class ItemView : UserControl
{
public static readonly StyledProperty<bool> ShowAttributesProperty = AvaloniaProperty.Register<ItemView, bool>(nameof(ShowAttributes), true);
public bool ShowAttributes
{
get => GetValue(ShowAttributesProperty);
set => SetValue(ShowAttributesProperty, value);
}
public ItemView()
{
InitializeComponent();
}
}
}

View File

@@ -1,17 +1,54 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:FileTime.GuiApp.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:FileTime.GuiApp.ViewModels"
xmlns:corevm="using:FileTime.App.Core.ViewModels"
xmlns:local="using:FileTime.GuiApp.Views"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
InputElement.KeyDown="OnKeyDown"
Opened="OnWindowOpened"
x:Class="FileTime.GuiApp.Views.MainWindow"
Icon="/Assets/filetime.ico"
Title="FileTime.GuiApp">
Title="FileTime"
TransparencyLevelHint="Blur"
Background="Transparent"
ExtendClientAreaToDecorationsHint="True"
x:DataType="vm:MainWindowViewModel"
x:CompileBindings="True">
<Design.DataContext>
<vm:MainWindowViewModel/>
</Design.DataContext>
<Design.DataContext>
<vm:MainWindowViewModel/>
</Design.DataContext>
<Grid Background="{DynamicResource AppBackgroundBrush}">
<Grid IsVisible="{Binding Loading, Converter={x:Static BoolConverters.Not}}">
<Grid ColumnDefinitions="250,*" RowDefinitions="Auto,*">
<ListBox
Grid.Row="1"
x:Name="CurrentItems"
x:CompileBindings="False"
AutoScrollToSelectedItem="True"
IsTabStop="True"
Items="{Binding AppState.SelectedTab.CurrentItems^}"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ScrollViewer.VerticalScrollBarVisibility="Visible"
Classes="ContentListView">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="corevm:IItemViewModel">
<local:ItemView HorizontalContentAlignment="Stretch" HorizontalAlignment="Stretch"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
<!--Borders-->
<TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
<Grid IsVisible="{Binding Loading}">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<Image Source="/Assets/filetime.ico" Width="128" Height="128"/>
<TextBlock Text="Loading..." HorizontalAlignment="Center" Margin="50"/>
</StackPanel>
</Grid>
</Grid>
</Window>

View File

@@ -1,12 +1,53 @@
using System;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using FileTime.GuiApp.Models;
using FileTime.GuiApp.ViewModels;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace FileTime.GuiApp.Views
{
public partial class MainWindow : Window
{
private readonly ILogger<MainWindow>? _logger;
private InputElementWrapper? _inputElementWrapper;
public MainWindowViewModel? ViewModel
{
get => DataContext as MainWindowViewModel;
set
{
if (value != DataContext)
{
DataContext = value;
}
}
}
public MainWindow()
{
_logger = DI.ServiceProvider.GetService<ILogger<MainWindow>>();
_logger?.LogInformation($"Starting {nameof(MainWindow)} initialization...");
InitializeComponent();
}
private void OnWindowOpened(object sender, EventArgs e)
{
if (DataContext is not MainWindowViewModel)
{
_logger?.LogInformation($"{nameof(MainWindow)} opened, starting {nameof(MainWindowViewModel)} initialization...");
ViewModel = DI.ServiceProvider.GetRequiredService<MainWindowViewModel>();
}
}
private void OnKeyDown(object sender, KeyEventArgs e)
{
if (_inputElementWrapper == null)
{
ViewModel?.ProcessKeyDown(e.Key, e.KeyModifiers, h => e.Handled = h);
}
}
}
}

View File

@@ -0,0 +1,148 @@
// Autogenerated
namespace InitableService;
public interface IInitable
{
void Init();
}
public interface IInitable<T1>
{
void Init(T1 obj1);
}
public interface IInitable<T1, T2>
{
void Init(T1 obj1, T2 obj2);
}
public interface IInitable<T1, T2, T3>
{
void Init(T1 obj1, T2 obj2, T3 obj3);
}
public interface IInitable<T1, T2, T3, T4>
{
void Init(T1 obj1, T2 obj2, T3 obj3, T4 obj4);
}
public interface IInitable<T1, T2, T3, T4, T5>
{
void Init(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5);
}
public interface IInitable<T1, T2, T3, T4, T5, T6>
{
void Init(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6);
}
public interface IInitable<T1, T2, T3, T4, T5, T6, T7>
{
void Init(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7);
}
public interface IInitable<T1, T2, T3, T4, T5, T6, T7, T8>
{
void Init(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8);
}
public interface IInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9>
{
void Init(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9);
}
public interface IInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
{
void Init(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9, T10 obj10);
}
public interface IInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>
{
void Init(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9, T10 obj10, T11 obj11);
}
public interface IInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>
{
void Init(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9, T10 obj10, T11 obj11, T12 obj12);
}
public interface IInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>
{
void Init(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9, T10 obj10, T11 obj11, T12 obj12, T13 obj13);
}
public interface IInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>
{
void Init(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9, T10 obj10, T11 obj11, T12 obj12, T13 obj13, T14 obj14);
}
public interface IInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>
{
void Init(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9, T10 obj10, T11 obj11, T12 obj12, T13 obj13, T14 obj14, T15 obj15);
}
public interface IAsyncInitable
{
Task InitAsync();
}
public interface IAsyncInitable<T1>
{
Task InitAsync(T1 obj1);
}
public interface IAsyncInitable<T1, T2>
{
Task InitAsync(T1 obj1, T2 obj2);
}
public interface IAsyncInitable<T1, T2, T3>
{
Task InitAsync(T1 obj1, T2 obj2, T3 obj3);
}
public interface IAsyncInitable<T1, T2, T3, T4>
{
Task InitAsync(T1 obj1, T2 obj2, T3 obj3, T4 obj4);
}
public interface IAsyncInitable<T1, T2, T3, T4, T5>
{
Task InitAsync(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5);
}
public interface IAsyncInitable<T1, T2, T3, T4, T5, T6>
{
Task InitAsync(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6);
}
public interface IAsyncInitable<T1, T2, T3, T4, T5, T6, T7>
{
Task InitAsync(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7);
}
public interface IAsyncInitable<T1, T2, T3, T4, T5, T6, T7, T8>
{
Task InitAsync(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8);
}
public interface IAsyncInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9>
{
Task InitAsync(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9);
}
public interface IAsyncInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
{
Task InitAsync(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9, T10 obj10);
}
public interface IAsyncInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>
{
Task InitAsync(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9, T10 obj10, T11 obj11);
}
public interface IAsyncInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>
{
Task InitAsync(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9, T10 obj10, T11 obj11, T12 obj12);
}
public interface IAsyncInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>
{
Task InitAsync(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9, T10 obj10, T11 obj11, T12 obj12, T13 obj13);
}
public interface IAsyncInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>
{
Task InitAsync(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9, T10 obj10, T11 obj11, T12 obj12, T13 obj13, T14 obj14);
}
public interface IAsyncInitable<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>
{
Task InitAsync(T1 obj1, T2 obj2, T3 obj3, T4 obj4, T5 obj5, T6 obj6, T7 obj7, T8 obj8, T9 obj9, T10 obj10, T11 obj11, T12 obj12, T13 obj13, T14 obj14, T15 obj15);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,180 @@
#!/usr/bin/env dotnet-script
{
const string header =
@"// Autogenerated
using InitableService;
namespace Microsoft.Extensions.DependencyInjection;
public static class InitableExtensions
{
";
const string footer = "}";
using var initableWriter = File.CreateText("InitableExtensions.cs");
initableWriter.WriteLine(header);
const int generateForParams = 15;
for (var i = 0; i <= generateForParams; i++)
{
WriteInitableExtension(initableWriter, false, i);
initableWriter.WriteLine();
}
for (var i = 0; i <= generateForParams; i++)
{
WriteInitableExtension(initableWriter, true, i);
initableWriter.WriteLine();
}
initableWriter.WriteLine(footer);
for (var i = 0; i <= generateForParams; i++)
{
WriteInitableResolver(initableWriter, false, i);
initableWriter.WriteLine();
}
for (var i = 0; i <= generateForParams; i++)
{
WriteInitableResolver(initableWriter, true, i);
initableWriter.WriteLine();
}
initableWriter.Flush();
}
void WriteInitableExtension(StreamWriter initableWriter, bool isAsync, int i)
{
initableWriter.Write($"\tpublic static {(isAsync ? "Async" : "")}Resolver");
if (i > 0)
{
initableWriter.Write("<");
initableWriter.Write(string.Join(", ", Enumerable.Range(1, i).Select(c => "T" + c)));
initableWriter.Write(">");
}
initableWriter.Write($" Get{(isAsync ? "Async" : "")}InitableResolver");
if (i > 0)
{
initableWriter.Write("<");
initableWriter.Write(string.Join(", ", Enumerable.Range(1, i).Select(c => "T" + c)));
initableWriter.Write(">");
}
initableWriter.Write("(");
var thisParameter = "this IServiceProvider serviceProvider";
if (i > 0)
{
initableWriter.Write(string.Join(", ", Enumerable.Range(1, i).Select(c => $"T{c} obj{c}").Prepend(thisParameter)));
}
else
{
initableWriter.Write(thisParameter);
}
initableWriter.WriteLine(")");
initableWriter.WriteLine("\t{");
initableWriter.Write($"\t\treturn new {(isAsync ? "Async" : "")}Resolver");
if (i > 0)
{
initableWriter.Write("<");
initableWriter.Write(string.Join(", ", Enumerable.Range(1, i).Select(c => "T" + c)));
initableWriter.Write(">");
}
initableWriter.Write("(");
if (i > 0)
{
initableWriter.Write(string.Join(", ", Enumerable.Range(1, i).Select(c => $"obj{c}").Prepend("serviceProvider")));
}
else
{
initableWriter.Write("serviceProvider");
}
initableWriter.WriteLine(");");
initableWriter.WriteLine("\t}");
}
void WriteInitableResolver(StreamWriter initableWriter, bool isAsync, int i)
{
initableWriter.Write($"public class {(isAsync ? "Async" : "")}Resolver");
if (i > 0)
{
initableWriter.Write("<");
initableWriter.Write(string.Join(", ", Enumerable.Range(1, i).Select(c => "T" + c)));
initableWriter.Write(">");
}
initableWriter.WriteLine();
initableWriter.WriteLine("{");
initableWriter.WriteLine("\tIServiceProvider _serviceProvider;");
if (i > 0)
{
for (var j = 1; j <= i; j++)
{
initableWriter.WriteLine($"\tprivate T{j} _obj{j};");
}
}
initableWriter.Write($"\tpublic {(isAsync ? "Async" : "")}Resolver(");
if (i > 0)
{
initableWriter.Write(string.Join(", ", Enumerable.Range(1, i).Select(c => $"T{c} obj{c}").Prepend("IServiceProvider serviceProvider")));
}
else
{
initableWriter.Write("IServiceProvider serviceProvider");
}
initableWriter.WriteLine(")");
initableWriter.WriteLine("\t{");
initableWriter.WriteLine("\t\t_serviceProvider = serviceProvider;");
if (i > 0)
{
for (var j = 1; j <= i; j++)
{
initableWriter.WriteLine($"\t\t_obj{j} = obj{j};");
}
}
initableWriter.WriteLine("\t}");
initableWriter.WriteLine("");
var returnType = isAsync ? "async Task<TResult>" : "TResult";
initableWriter.WriteLine($"\tpublic {returnType} GetRequiredService{(isAsync ? "Async" : "")}<TResult>()");
initableWriter.Write($"\t\twhere TResult : class, I{(isAsync ? "Async" : "")}Initable");
if (i > 0)
{
initableWriter.Write("<");
initableWriter.Write(string.Join(", ", Enumerable.Range(1, i).Select(c => "T" + c)));
initableWriter.Write(">");
}
initableWriter.WriteLine("");
initableWriter.WriteLine("\t{");
initableWriter.WriteLine("\t\tvar initableService = _serviceProvider.GetRequiredService<TResult>()!;");
if (isAsync)
{
initableWriter.Write("\t\tawait initableService.InitAsync");
}
else
{
initableWriter.Write("\t\tinitableService.Init");
}
initableWriter.Write("(");
if (i > 0)
{
initableWriter.Write(string.Join(", ", Enumerable.Range(1, i).Select(c => $"_obj{c}")));
}
initableWriter.WriteLine(");");
initableWriter.WriteLine("\t\treturn initableService;");
initableWriter.WriteLine("\t}");
initableWriter.WriteLine("}");
}

View File

@@ -0,0 +1,66 @@
#!/usr/bin/env dotnet-script
{
const string header =
@"// Autogenerated
namespace InitableService;
";
const string footer = "";
using var initableWriter = File.CreateText("Initable.cs");
initableWriter.WriteLine(header);
const int generateForParams = 15;
for (var i = 0; i <= generateForParams; i++)
{
WriteInitable(initableWriter, false, i);
}
for (var i = 0; i <= generateForParams; i++)
{
WriteInitable(initableWriter, true, i);
initableWriter.WriteLine();
}
initableWriter.WriteLine(footer);
initableWriter.Flush();
}
void WriteInitable(StreamWriter initableWriter, bool isAsync, int i)
{
initableWriter.Write($"public interface I{(isAsync ? "Async" : "")}Initable");
if (i > 0)
{
initableWriter.Write("<");
initableWriter.Write(string.Join(", ", Enumerable.Range(1, i).Select(c => "T" + c)));
initableWriter.Write(">");
}
initableWriter.WriteLine();
initableWriter.WriteLine("{");
if (isAsync)
{
initableWriter.Write("\tTask InitAsync");
}
else
{
initableWriter.Write("\tvoid Init");
}
if (i > 0)
{
/*initableWriter.Write("<");
initableWriter.Write(string.Join(", ", Enumerable.Range(1, i).Select(c => "T" + c)));
initableWriter.Write(">");*/
initableWriter.Write("(");
initableWriter.Write(string.Join(", ", Enumerable.Range(1, i).Select(c => $"T{c} obj{c}")));
initableWriter.Write(")");
}
else
{
initableWriter.Write("()");
}
initableWriter.WriteLine(";");
initableWriter.WriteLine("}");
}

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\AppCommon\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,9 @@
using FileTime.Core.Services;
namespace FileTime.Providers.Local
{
public interface ILocalContentProvider : IContentProvider
{
}
}

View File

@@ -14,6 +14,7 @@
<ProjectReference Include="..\..\Core\FileTime.Core.Abstraction\FileTime.Core.Abstraction.csproj" />
<ProjectReference Include="..\..\Core\FileTime.Core.Models\FileTime.Core.Models.csproj" />
<ProjectReference Include="..\..\Core\FileTime.Core.Services\FileTime.Core.Services.csproj" />
<ProjectReference Include="..\FileTime.Providers.Local.Abstractions\FileTime.Providers.Local.Abstractions.csproj" />
</ItemGroup>
</Project>

View File

@@ -5,7 +5,7 @@ using FileTime.Core.Services;
namespace FileTime.Providers.Local
{
public class LocalContentProvider : ContentProviderBase
public class LocalContentProvider : ContentProviderBase, ILocalContentProvider
{
protected bool IsCaseInsensitive { get; init; }
public LocalContentProvider() : base("local")
@@ -43,7 +43,7 @@ namespace FileTime.Providers.Local
return Task.FromResult((IItem)FileToElement(new FileInfo(path)));
}
throw new FileNotFoundException();
throw new FileNotFoundException("Directory or file not found", path);
}
public override Task<List<IAbsolutePath>> GetItemsByContainerAsync(FullName fullName) => Task.FromResult(GetItemsByContainer(fullName));

View File

@@ -8,8 +8,8 @@ namespace FileTime.Providers.Local
public static IServiceCollection AddLocalServices(this IServiceCollection serviceCollection)
{
return serviceCollection
.AddSingleton<LocalContentProvider>()
.AddSingleton<IContentProvider, LocalContentProvider>(sp => sp.GetService<LocalContentProvider>() ?? throw new Exception($"No {nameof(LocalContentProvider)} instance found"));
.AddSingleton<ILocalContentProvider, LocalContentProvider>()
.AddSingleton<IContentProvider, ILocalContentProvider>(sp => sp.GetRequiredService<ILocalContentProvider>());
}
}
}