diff --git a/src/FileTime.sln b/src/FileTime.sln index 4bc0bb2..93f9599 100644 --- a/src/FileTime.sln +++ b/src/FileTime.sln @@ -79,6 +79,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.App.FuzzyPanel", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.App.FuzzyPanel.Abstraction", "AppCommon\FileTime.App.FuzzyPanel.Abstraction\FileTime.App.FuzzyPanel.Abstraction.csproj", "{7690E4EA-DA2A-45B4-AD83-80EE07A05169}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.GuiApp.DesignPreview", "GuiApp\Avalonia\FileTime.GuiApp.DesignPreview\FileTime.GuiApp.DesignPreview.csproj", "{D0EC224E-F043-4657-BD6A-1ADE52DFF8B5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -205,6 +207,10 @@ Global {7690E4EA-DA2A-45B4-AD83-80EE07A05169}.Debug|Any CPU.Build.0 = Debug|Any CPU {7690E4EA-DA2A-45B4-AD83-80EE07A05169}.Release|Any CPU.ActiveCfg = Release|Any CPU {7690E4EA-DA2A-45B4-AD83-80EE07A05169}.Release|Any CPU.Build.0 = Release|Any CPU + {D0EC224E-F043-4657-BD6A-1ADE52DFF8B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D0EC224E-F043-4657-BD6A-1ADE52DFF8B5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D0EC224E-F043-4657-BD6A-1ADE52DFF8B5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D0EC224E-F043-4657-BD6A-1ADE52DFF8B5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -241,6 +247,7 @@ Global {5B3D2008-371F-485C-92C0-127F6CD64F64} = {A5291117-3001-498B-AC8B-E14F71F72570} {7FDCE43D-D084-4539-B797-8A72D4DD610D} = {A5291117-3001-498B-AC8B-E14F71F72570} {7690E4EA-DA2A-45B4-AD83-80EE07A05169} = {A5291117-3001-498B-AC8B-E14F71F72570} + {D0EC224E-F043-4657-BD6A-1ADE52DFF8B5} = {01F231DE-4A65-435F-B4BB-77EE5221890C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {859FB3DF-C60A-46B1-82E5-90274905D1EF} diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.App/App.axaml.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.App/App.axaml.cs index b0c8c66..2a88927 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.App/App.axaml.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.App/App.axaml.cs @@ -15,7 +15,7 @@ namespace FileTime.GuiApp.App; public class App : Application { - static App() + private static void InitializeApp() { var configuration = Startup.CreateConfiguration(); DI.ServiceProvider = DependencyInjection @@ -33,13 +33,14 @@ public class App : Application var logger = DI.ServiceProvider.GetRequiredService>(); logger.LogInformation("App initialization completed"); } + public override void Initialize() => AvaloniaXamlLoader.Load(this); public override void OnFrameworkInitializationCompleted() { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - desktop.MainWindow = new MainWindow + desktop.MainWindow = new MainWindow(InitializeApp) { DataContext = new MainWindowLoadingViewModel(), }; diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/FileTime.GuiApp.DesignPreview.csproj b/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/FileTime.GuiApp.DesignPreview.csproj new file mode 100644 index 0000000..7c4ddb9 --- /dev/null +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/FileTime.GuiApp.DesignPreview.csproj @@ -0,0 +1,14 @@ + + + + net7.0 + enable + enable + + + + + + + + diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/GuiAppStatePreview.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/GuiAppStatePreview.cs new file mode 100644 index 0000000..6d46097 --- /dev/null +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/GuiAppStatePreview.cs @@ -0,0 +1,59 @@ +using System.Collections.ObjectModel; +using System.Reactive.Subjects; +using FileTime.App.Core.Models.Enums; +using FileTime.App.Core.ViewModels; +using FileTime.App.Core.ViewModels.Timeline; +using FileTime.Core.Models; +using FileTime.GuiApp.Configuration; +using FileTime.GuiApp.Models; +using FileTime.GuiApp.ViewModels; + +namespace FileTime.GuiApp.DesignPreview.Services; + +public class GuiAppStatePreview : IGuiAppState +{ + public GuiAppStatePreview() + { + var tab = new TabViewModelPreview(this); + SelectedTab = new BehaviorSubject(tab); + CurrentSelectedTab = tab; + + var tabs = new ObservableCollection(new [] {tab}); + Tabs = new ReadOnlyObservableCollection(tabs); + + SearchText = new BehaviorSubject(null); + ViewMode = new BehaviorSubject(FileTime.App.Core.Models.Enums.ViewMode.Default); + + PreviousKeys = new(); + + ActivePanel = new BehaviorSubject(GuiPanel.FileBrowser); + PopupTexts = new ObservableCollection(); + } + + public ReadOnlyObservableCollection Tabs { get; } + public IObservable SelectedTab { get; } + public IObservable SearchText { get; } + public IObservable ViewMode { get; } + public string RapidTravelText { get; set; } + public ITabViewModel? CurrentSelectedTab { get; } + public ITimelineViewModel TimelineViewModel { get; } + public void SetSearchText(string? searchText) => throw new NotImplementedException(); + + public void SetSelectedTab(ITabViewModel tabToSelect) => throw new NotImplementedException(); + + public void SwitchViewMode(ViewMode newViewMode) => throw new NotImplementedException(); + + public void RemoveTab(ITabViewModel tabViewModel) => throw new NotImplementedException(); + + public void AddTab(ITabViewModel tabViewModel) => throw new NotImplementedException(); + + public List PreviousKeys { get; } + public bool IsAllShortcutVisible { get; set; } + public bool NoCommandFound { get; set; } + public List PossibleCommands { get; set; } + public BindedCollection RootDriveInfos { get; set; } + public IReadOnlyList Places { get; set; } + public ObservableCollection PopupTexts { get; } + public IObservable ActivePanel { get; } + public void SetActivePanel(GuiPanel newPanel) => throw new NotImplementedException(); +} \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/ItemPreview.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/ItemPreview.cs new file mode 100644 index 0000000..463bf93 --- /dev/null +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/ItemPreview.cs @@ -0,0 +1,64 @@ +using DynamicData; +using FileTime.Core.Enums; +using FileTime.Core.Models; +using FileTime.Core.Timeline; + +namespace FileTime.GuiApp.DesignPreview.Services; + +public class ItemPreview +{ + public static IContainer CurrentContainer { get; } + + static ItemPreview() + { + var exceptions = new SourceList(); + CurrentContainer = new Container( + "HomePreview", + "HomePreview", + new FullName("local/root/test/path/HomePreview"), + new NativePath("/root/test/path/HomePreview"), + new AbsolutePath( + null!, + PointInTime.Present, + new FullName("local/root/test/path"), + AbsolutePathType.Container + ), + false, + true, + DateTime.Now, + SupportsDelete.True, + true, + "attr", + null!, + false, + PointInTime.Present, + exceptions.Connect(), + new ExtensionCollection().AsReadOnly(), + new SourceCache(a => a.Path.Path).Connect() + ); + } + + public static IElement GenerateElement(string name, string parentPath = "local/root/test/path/HomePreview") => + new Element( + name, + name, + new FullName(parentPath + "/" + name), + new NativePath("/root/test/path/HomePreview/" + name), + new AbsolutePath( + null!, + PointInTime.Present, + new FullName(parentPath), + AbsolutePathType.Container + ), + false, + true, + DateTime.Now, + SupportsDelete.True, + true, + "attr", + null!, + PointInTime.Present, + new SourceList().Connect(), + new ExtensionCollection().AsReadOnly() + ); +} \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/TabPreview.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/TabPreview.cs new file mode 100644 index 0000000..4098162 --- /dev/null +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/TabPreview.cs @@ -0,0 +1,49 @@ +using System.Reactive.Linq; +using System.Reactive.Subjects; +using DynamicData; +using FileTime.Core.Models; +using FileTime.Core.Services; + +namespace FileTime.GuiApp.DesignPreview.Services; + +public class TabPreview : ITab +{ + public TabPreview() + { + var currentLocation = new BehaviorSubject(ItemPreview.CurrentContainer); + CurrentLocation = currentLocation.AsObservable(); + + var currentItems = new SourceCache(i => i.Name); + var items = GenerateItems(); + currentItems.AddOrUpdate(items); + CurrentSelectedItemPreview = items[0]; + CurrentItems = new BehaviorSubject>>(currentItems.Connect()); + CurrentSelectedItem = new BehaviorSubject(new AbsolutePath(null!, CurrentSelectedItemPreview)); + } + + public IItem CurrentSelectedItemPreview { get; } + + private static List GenerateItems() + => Enumerable.Range(1, 10).Select(i => (IItem)ItemPreview.GenerateElement("Element" + i)).ToList(); + + public IObservable CurrentLocation { get; } + public IObservable CurrentSelectedItem { get; } + public IObservable>?> CurrentItems { get; } + public FullName? LastDeepestSelectedPath { get; } + public void SetCurrentLocation(IContainer newLocation) => throw new NotImplementedException(); + + public void AddItemFilter(ItemFilter filter) => throw new NotImplementedException(); + + public void RemoveItemFilter(ItemFilter filter) => throw new NotImplementedException(); + + public void RemoveItemFilter(string name) => throw new NotImplementedException(); + + public void SetSelectedItem(AbsolutePath newSelectedItem) => throw new NotImplementedException(); + + public void ForceSetCurrentLocation(IContainer newLocation) => throw new NotImplementedException(); + public void Init(IContainer obj1) => throw new NotImplementedException(); + + public void Dispose() + { + } +} \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/TabViewModelPreview.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/TabViewModelPreview.cs new file mode 100644 index 0000000..14d75de --- /dev/null +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/TabViewModelPreview.cs @@ -0,0 +1,118 @@ +using System.Reactive.Linq; +using System.Reactive.Subjects; +using DynamicData; +using FileTime.App.Core.Models.Enums; +using FileTime.App.Core.Services; +using FileTime.App.Core.ViewModels; +using FileTime.Core.Models; +using FileTime.Core.Services; + +namespace FileTime.GuiApp.DesignPreview.Services; + +public class TabViewModelPreview : ITabViewModel +{ + private static readonly ItemNameConverterService _itemNameConverterService = new(); + + public TabViewModelPreview(IAppState appState) + { + var tab = new TabPreview(); + Tab = tab; + TabNumber = 1; + IsSelected = new BehaviorSubject(true); + CurrentLocation = tab.CurrentLocation; + + IItemViewModel currentSelectedItem = tab.CurrentSelectedItemPreview is IContainer + ? CreateCurrentSelectedItemContainer() + : CreateCurrentSelectedItemElement(); + + CurrentSelectedItem = new BehaviorSubject(currentSelectedItem); + + CurrentItems = tab.CurrentItems + .Select>?, IObservable>?>( + items => + items!.Transform(i => MapItemToViewModel(i, ItemViewModelType.Main))); + + ContainerViewModel CreateCurrentSelectedItemContainer() + { + var vm = new ContainerViewModel( + _itemNameConverterService, + appState + ); + vm.Init( + (IContainer) tab.CurrentSelectedItemPreview, + this, + ItemViewModelType.Main + ); + + return vm; + } + + ElementViewModel CreateCurrentSelectedItemElement() + { + var vm = new ElementViewModel( + _itemNameConverterService, + appState + ); + + vm.Init( + (IElement) tab.CurrentSelectedItemPreview, + this, + ItemViewModelType.Main + ); + + return vm; + } + + IItemViewModel MapItemToViewModel(IItem item, ItemViewModelType type) + { + if (item is IContainer container) + { + var containerViewModel = new ContainerViewModel(_itemNameConverterService, appState); + containerViewModel.Init(container, this, type); + + return containerViewModel; + } + else if (item is IElement element) + { + var elementViewModel = new ElementViewModel(_itemNameConverterService, appState); + elementViewModel.Init(element, this, type); + + return elementViewModel; + } + + throw new Exception(); + } + } + + public ITab? Tab { get; } + public int TabNumber { get; } + public IObservable IsSelected { get; } + public IObservable CurrentLocation { get; } + public IObservable CurrentSelectedItem { get; } + public IObservable>?> CurrentItems { get; } + public IObservable> MarkedItems { get; } + public IObservable>?> SelectedsChildren { get; } + public IObservable>?> ParentsChildren { get; } + public BindedCollection? CurrentItemsCollection { get; } + public BindedCollection? SelectedsChildrenCollection { get; } + public BindedCollection? ParentsChildrenCollection { get; } + public IObservable?> CurrentItemsCollectionObservable { get; } + public IObservable?> ParentsChildrenCollectionObservable { get; } + public IObservable?> SelectedsChildrenCollectionObservable { get; } + public IContainer? CachedCurrentLocation { get; } + public void ClearMarkedItems() => throw new NotImplementedException(); + + public void RemoveMarkedItem(FullName fullName) => throw new NotImplementedException(); + + public void AddMarkedItem(FullName fullName) => throw new NotImplementedException(); + + public void ToggleMarkedItem(FullName fullName) => throw new NotImplementedException(); + + public void Init(ITab obj1, int obj2) + { + } + + public void Dispose() + { + } +} \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/FileTime.GuiApp.csproj b/src/GuiApp/Avalonia/FileTime.GuiApp/FileTime.GuiApp.csproj index c341a5e..dbe2965 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/FileTime.GuiApp.csproj +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/FileTime.GuiApp.csproj @@ -43,6 +43,7 @@ + diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/ViewModels/IMainWindowViewModel.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/ViewModels/IMainWindowViewModel.cs new file mode 100644 index 0000000..d87c355 --- /dev/null +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/ViewModels/IMainWindowViewModel.cs @@ -0,0 +1,16 @@ +using FileTime.App.CommandPalette.Services; +using FileTime.App.Core.Services; +using FileTime.App.FrequencyNavigation.Services; +using FileTime.GuiApp.Services; + +namespace FileTime.GuiApp.ViewModels; + +public interface IMainWindowViewModel : IMainWindowViewModelBase +{ + string Title { get; } + IGuiAppState AppState { get; } + IItemPreviewService ItemPreviewService { get; } + IDialogService DialogService { get; } + IFrequencyNavigationService FrequencyNavigationService { get; } + ICommandPaletteService CommandPaletteService { get; } +} \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/ViewModels/MainWindowDesignViewModel.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/ViewModels/MainWindowDesignViewModel.cs new file mode 100644 index 0000000..ae4892e --- /dev/null +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/ViewModels/MainWindowDesignViewModel.cs @@ -0,0 +1,25 @@ +using System.Reactive.Subjects; +using FileTime.App.CommandPalette.Services; +using FileTime.App.Core.Services; +using FileTime.App.FrequencyNavigation.Services; +using FileTime.GuiApp.DesignPreview.Services; +using FileTime.GuiApp.Services; + +namespace FileTime.GuiApp.ViewModels; + +public class MainWindowDesignViewModel : IMainWindowViewModel +{ + public bool Loading => false; + public IObservable MainFont { get; } = new BehaviorSubject(""); + public string Title => "FileTime Design Preview"; + public IGuiAppState AppState { get; } + public IItemPreviewService ItemPreviewService { get; } + public IDialogService DialogService { get; } + public IFrequencyNavigationService FrequencyNavigationService { get; } + public ICommandPaletteService CommandPaletteService { get; } + + public MainWindowDesignViewModel() + { + AppState = new GuiAppStatePreview(); + } +} \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/ViewModels/MainWindowViewModel.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/ViewModels/MainWindowViewModel.cs index 244fb6c..2d0120d 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/ViewModels/MainWindowViewModel.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/ViewModels/MainWindowViewModel.cs @@ -28,7 +28,7 @@ namespace FileTime.GuiApp.ViewModels; [Inject(typeof(IFontService), "_fontService")] [Inject(typeof(IFrequencyNavigationService), PropertyAccessModifier = AccessModifier.Public)] [Inject(typeof(ICommandPaletteService), PropertyAccessModifier = AccessModifier.Public)] -public partial class MainWindowViewModel : IMainWindowViewModelBase +public partial class MainWindowViewModel : IMainWindowViewModel { public bool Loading => false; public IObservable MainFont => _fontService.MainFont.Select(x => x ?? ""); diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml index 5005ddc..dcca1b8 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml @@ -5,8 +5,8 @@ FontFamily="{Binding MainFont^, Mode=OneWay}" Icon="/Assets/filetime.ico" KeyDown="OnKeyDown" - MinHeight="600" - MinWidth="800" + MinHeight="800" + MinWidth="1000" Opened="OnWindowOpened" RequestedThemeVariant="Dark" Title="FileTime" @@ -44,7 +44,7 @@ - + diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml.cs index 4029b41..2fad389 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml.cs @@ -17,8 +17,9 @@ namespace FileTime.GuiApp.Views; public partial class MainWindow : Window, IUiAccessor { - private readonly ILogger? _logger; - private readonly IModalService _modalService; + private readonly Action? _initializer; + private ILogger? _logger; + private IModalService? _modalService; private IReadOnlyCollection? _openModals; private ReadInputsViewModel? _inputViewModel; private IDisposable? _inputViewModelSubscription; @@ -37,29 +38,40 @@ public partial class MainWindow : Window, IUiAccessor public MainWindow() { - _logger = DI.ServiceProvider.GetService>(); - _logger?.LogInformation($"Starting {nameof(MainWindow)} initialization..."); - _modalService = DI.ServiceProvider.GetRequiredService(); - _modalService.OpenModals.ToCollection().Subscribe(m => _openModals = m); - DI.ServiceProvider.GetRequiredService().UiAccessor = this; InitializeComponent(); - - ReadInputContainer.PropertyChanged += ReadInputContainerOnPropertyChanged; - DataContextChanged += (sender, args) => + if (Design.IsDesignMode) { - if (DataContext is not MainWindowViewModel mainWindowViewModel) return; + DataContext = new MainWindowDesignViewModel(); + } + } - _inputViewModelSubscription?.Dispose(); - _inputViewModelSubscription = mainWindowViewModel.DialogService.ReadInput.Subscribe( - inputViewModel => _inputViewModel = inputViewModel - ); - }; + public MainWindow(Action initializer) : this() + { + _initializer = initializer; } private void OnWindowOpened(object sender, EventArgs e) { - if (DataContext is not MainWindowViewModel) + if (DataContext is not MainWindowViewModel && !Design.IsDesignMode) { + _initializer?.Invoke(); + + _logger = DI.ServiceProvider.GetService>(); + _modalService = DI.ServiceProvider.GetRequiredService(); + _modalService.OpenModals.ToCollection().Subscribe(m => _openModals = m); + DI.ServiceProvider.GetRequiredService().UiAccessor = this; + + ReadInputContainer.PropertyChanged += ReadInputContainerOnPropertyChanged; + DataContextChanged += (_, _) => + { + if (DataContext is not MainWindowViewModel mainWindowViewModel) return; + + _inputViewModelSubscription?.Dispose(); + _inputViewModelSubscription = mainWindowViewModel.DialogService.ReadInput.Subscribe( + inputViewModel => _inputViewModel = inputViewModel + ); + }; + _logger?.LogInformation( $"{nameof(MainWindow)} opened, starting {nameof(MainWindowViewModel)} initialization..."); ViewModel = DI.ServiceProvider.GetRequiredService();