diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/Services/IClipboardService.cs b/src/AppCommon/FileTime.App.Core.Abstraction/Services/IClipboardService.cs new file mode 100644 index 0000000..2dc008e --- /dev/null +++ b/src/AppCommon/FileTime.App.Core.Abstraction/Services/IClipboardService.cs @@ -0,0 +1,16 @@ +using FileTime.Core.Command; +using FileTime.Core.Models; + +namespace FileTime.App.Core.Services +{ + public interface IClipboardService + { + Type? CommandType { get; } + IReadOnlyList Content { get; } + + void AddContent(IAbsolutePath absolutePath); + void RemoveContent(IAbsolutePath absolutePath); + void Clear(); + void SetCommand() where T : ITransportationCommand; + } +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ITabViewModel.cs b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ITabViewModel.cs index 8e7065a..bc1c7d9 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ITabViewModel.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ITabViewModel.cs @@ -15,7 +15,7 @@ namespace FileTime.App.Core.ViewModels IObservable CurrentLocation { get; } IObservable CurrentSelectedItem { get; } IObservable>?> CurrentItems { get; } - IObservable> MarkedItems { get; } + IObservable> MarkedItems { get; } IObservable>?> SelectedsChildren { get; } IObservable>?> ParentsChildren { get; } BindedCollection? CurrentItemsCollection { get; } @@ -23,8 +23,8 @@ namespace FileTime.App.Core.ViewModels BindedCollection? ParentsChildrenCollection { get; } IObservable?> CurrentItemsCollectionObservable { get; } void ClearMarkedItems(); - void RemoveMarkedItem(FullName item); - void AddMarkedItem(FullName item); - void ToggleMarkedItem(FullName item); + void RemoveMarkedItem(IAbsolutePath item); + void AddMarkedItem(IAbsolutePath item); + void ToggleMarkedItem(IAbsolutePath item); } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/FileTime.App.Core.csproj b/src/AppCommon/FileTime.App.Core/FileTime.App.Core.csproj index 723e46d..c889896 100644 --- a/src/AppCommon/FileTime.App.Core/FileTime.App.Core.csproj +++ b/src/AppCommon/FileTime.App.Core/FileTime.App.Core.csproj @@ -18,6 +18,7 @@ + diff --git a/src/AppCommon/FileTime.App.Core/Services/ClipboardService.cs b/src/AppCommon/FileTime.App.Core/Services/ClipboardService.cs new file mode 100644 index 0000000..e00a298 --- /dev/null +++ b/src/AppCommon/FileTime.App.Core/Services/ClipboardService.cs @@ -0,0 +1,51 @@ +using FileTime.Core.Command; +using FileTime.Core.Models; + +namespace FileTime.App.Core.Services +{ + public class ClipboardService : IClipboardService + { + private List _content; + public IReadOnlyList Content { get; private set; } + public Type? CommandType { get; private set; } + + public ClipboardService() + { + _content = new List(); + Content = _content.AsReadOnly(); + } + + public void AddContent(IAbsolutePath absolutePath) + { + foreach (var content in _content) + { + if (content.Equals(absolutePath)) return; + } + + _content.Add(absolutePath); + } + + public void RemoveContent(IAbsolutePath absolutePath) + { + for (var i = 0; i < _content.Count; i++) + { + if (_content[i].Equals(absolutePath)) + { + _content.RemoveAt(i--); + } + } + } + + public void Clear() + { + _content = new List(); + Content = _content.AsReadOnly(); + CommandType = null; + } + + public void SetCommand() where T : ITransportationCommand + { + CommandType = typeof(T); + } + } +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/Services/CommandHandler/CommandHanderBase.cs b/src/AppCommon/FileTime.App.Core/Services/CommandHandler/CommandHandlerBase.cs similarity index 82% rename from src/AppCommon/FileTime.App.Core/Services/CommandHandler/CommandHanderBase.cs rename to src/AppCommon/FileTime.App.Core/Services/CommandHandler/CommandHandlerBase.cs index 699ab43..ba42208 100644 --- a/src/AppCommon/FileTime.App.Core/Services/CommandHandler/CommandHanderBase.cs +++ b/src/AppCommon/FileTime.App.Core/Services/CommandHandler/CommandHandlerBase.cs @@ -1,16 +1,17 @@ using System.Reactive.Linq; +using DynamicData; using FileTime.App.Core.Command; using FileTime.App.Core.ViewModels; using FileTime.Core.Models; namespace FileTime.App.Core.Services.CommandHandler { - public abstract class CommandHanderBase : ICommandHandler + public abstract class CommandHandlerBase : ICommandHandler { private readonly Dictionary> _commandHandlers = new(); private readonly IAppState? _appState; - protected CommandHanderBase(IAppState? appState = null) + protected CommandHandlerBase(IAppState? appState = null) { _appState = appState; } @@ -40,9 +41,12 @@ namespace FileTime.App.Core.Services.CommandHandler protected IDisposable SaveCurrentItems(Action> handler) => RunWithAppState(appState => appState.SelectedTab.Select(t => t?.CurrentItemsCollectionObservable ?? Observable.Return((IEnumerable?)Enumerable.Empty())).Switch().Subscribe(i => handler(i ?? Enumerable.Empty()))); + protected IDisposable SaveMarkedItems(Action> handler) + => RunWithAppState(appstate => appstate.SelectedTab.Select(t => t == null ? Observable.Empty>() : t.MarkedItems).Switch().Subscribe(handler)); + private IDisposable RunWithAppState(Func act) { - if (_appState == null) throw new NullReferenceException($"AppState is nit initialized in {nameof(CommandHanderBase)}."); + if (_appState == null) throw new NullReferenceException($"AppState is nit initialized in {nameof(CommandHandlerBase)}."); return act(_appState); } diff --git a/src/AppCommon/FileTime.App.Core/Services/CommandHandler/ItemManipulationCommandHandler.cs b/src/AppCommon/FileTime.App.Core/Services/CommandHandler/ItemManipulationCommandHandler.cs index 2b183b2..c197273 100644 --- a/src/AppCommon/FileTime.App.Core/Services/CommandHandler/ItemManipulationCommandHandler.cs +++ b/src/AppCommon/FileTime.App.Core/Services/CommandHandler/ItemManipulationCommandHandler.cs @@ -1,24 +1,44 @@ +using DynamicData; using FileTime.App.Core.Command; +using FileTime.App.Core.Models; using FileTime.App.Core.ViewModels; +using FileTime.Core.Command; +using FileTime.Core.Command.Copy; +using FileTime.Core.Models; namespace FileTime.App.Core.Services.CommandHandler { - public class ItemManipulationCommandHandler : CommandHanderBase + public class ItemManipulationCommandHandler : CommandHandlerBase { private ITabViewModel? _selectedTab; private IItemViewModel? _currentSelectedItem; private readonly ICommandHandlerService _commandHandlerService; + private readonly IClipboardService _clipboardService; + private BindedCollection? _markedItems; - public ItemManipulationCommandHandler(IAppState appState, ICommandHandlerService commandHandlerService) : base(appState) + public ItemManipulationCommandHandler( + IAppState appState, + ICommandHandlerService commandHandlerService, + IClipboardService clipboardService) : base(appState) { _commandHandlerService = commandHandlerService; + _clipboardService = clipboardService; - SaveSelectedTab(t => _selectedTab = t); + SaveSelectedTab(t => + { + _selectedTab = t; + _markedItems?.Dispose(); + _markedItems = t == null ? null : new BindedCollection(t.MarkedItems); + }); SaveCurrentSelectedItem(i => _currentSelectedItem = i); AddCommandHandlers(new (Commands, Func)[] { - (Commands.Mark, MarkItem) + (Commands.Copy, Copy), + (Commands.Mark, MarkItem), + (Commands.PasteMerge, PasteMerge), + (Commands.PasteOverwrite, PasteOverwrite), + (Commands.PasteSkip, PasteSkip), }); } @@ -26,8 +46,51 @@ namespace FileTime.App.Core.Services.CommandHandler { if (_selectedTab == null || _currentSelectedItem?.BaseItem?.FullName == null) return; - _selectedTab.ToggleMarkedItem(_currentSelectedItem.BaseItem.FullName); + _selectedTab.ToggleMarkedItem(new AbsolutePath(_currentSelectedItem.BaseItem)); await _commandHandlerService.HandleCommandAsync(Commands.MoveCursorDown); } + + private Task Copy() + { + _clipboardService.Clear(); + _clipboardService.SetCommand(); + + if ((_markedItems?.Collection.Count ?? 0) > 0) + { + foreach (var item in _markedItems!.Collection) + { + _clipboardService.AddContent(item); + } + + _selectedTab?.ClearMarkedItems(); + } + else if (_currentSelectedItem?.BaseItem != null) + { + _clipboardService.AddContent(new AbsolutePath(_currentSelectedItem.BaseItem)); + } + + return Task.CompletedTask; + } + + private async Task PasteMerge() + { + await Paste(TransportMode.Merge); + } + + private async Task PasteOverwrite() + { + await Paste(TransportMode.Overwrite); + } + + private async Task PasteSkip() + { + await Paste(TransportMode.Skip); + } + + private Task Paste(TransportMode skip) + { + if (_clipboardService.CommandType is null) return Task.CompletedTask; + return Task.CompletedTask; + } } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/Services/CommandHandler/NavigationCommandHandler.cs b/src/AppCommon/FileTime.App.Core/Services/CommandHandler/NavigationCommandHandler.cs index 28928d9..d98ae63 100644 --- a/src/AppCommon/FileTime.App.Core/Services/CommandHandler/NavigationCommandHandler.cs +++ b/src/AppCommon/FileTime.App.Core/Services/CommandHandler/NavigationCommandHandler.cs @@ -6,7 +6,7 @@ using FileTime.Core.Models; namespace FileTime.App.Core.Services.CommandHandler { - public class NavigationCommandHandler : CommandHanderBase + public class NavigationCommandHandler : CommandHandlerBase { private ITabViewModel? _selectedTab; private IContainer? _currentLocation; diff --git a/src/AppCommon/FileTime.App.Core/Startup.cs b/src/AppCommon/FileTime.App.Core/Startup.cs index 6773660..13a8229 100644 --- a/src/AppCommon/FileTime.App.Core/Startup.cs +++ b/src/AppCommon/FileTime.App.Core/Startup.cs @@ -15,6 +15,7 @@ namespace FileTime.App.Core .AddTransient() .AddTransient() .AddSingleton() + .AddSingleton() .AddCommandHandlers(); } diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/ItemViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/ItemViewModel.cs index 7ae6409..c73e746 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/ItemViewModel.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/ItemViewModel.cs @@ -1,4 +1,5 @@ using System.Reactive.Linq; +using DynamicData; using FileTime.App.Core.Models; using FileTime.App.Core.Models.Enums; using FileTime.App.Core.Services; @@ -45,7 +46,7 @@ namespace FileTime.App.Core.ViewModels BaseItem = item; DisplayName = _appState.SearchText.Select(s => _itemNameConverterService.GetDisplayName(item.DisplayName, s)); DisplayNameText = item.DisplayName; - IsMarked = parentTab.MarkedItems.Select(m => m.Contains(item.FullName)); + IsMarked = parentTab.MarkedItems.ToCollection().Select(m => m.Any(i => i.Path.Path == item.FullName?.Path)); IsSelected = parentTab.CurrentSelectedItem.Select(EqualsTo); IsAlternative = parentTab.CurrentItemsCollectionObservable.Select(c => c?.Index().FirstOrDefault(i => EqualsTo(i.Value)).Key % 2 == 0); ViewMode = Observable.CombineLatest(IsMarked, IsSelected, IsAlternative, GenerateViewMode); diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs index cd2142d..27bd68c 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs @@ -1,5 +1,4 @@ using System.Reactive.Linq; -using System.Reactive.Subjects; using DynamicData; using FileTime.App.Core.Extensions; using FileTime.App.Core.Models; @@ -19,8 +18,7 @@ namespace FileTime.App.Core.ViewModels private readonly IItemNameConverterService _itemNameConverterService; private readonly IAppState _appState; private readonly IRxSchedulerService _rxSchedulerService; - private readonly BehaviorSubject> _markedItems = new(Enumerable.Empty()); - private readonly List _currentMarkedItems = new(); + private readonly SourceList _markedItems = new(); private readonly List _disposables = new(); private bool disposed; @@ -32,7 +30,7 @@ namespace FileTime.App.Core.ViewModels public IObservable CurrentLocation { get; private set; } = null!; public IObservable CurrentSelectedItem { get; private set; } = null!; public IObservable>?> CurrentItems { get; private set; } = null!; - public IObservable> MarkedItems { get; } + public IObservable> MarkedItems { get; } public IObservable>?> SelectedsChildren { get; private set; } = null!; public IObservable>?> ParentsChildren { get; private set; } = null!; @@ -57,7 +55,7 @@ namespace FileTime.App.Core.ViewModels _itemNameConverterService = itemNameConverterService; _appState = appState; - MarkedItems = _markedItems.Select(e => e.ToList()).AsObservable(); + MarkedItems = _markedItems.Connect().StartWithEmpty(); IsSelected = _appState.SelectedTab.Select(s => s == this); _rxSchedulerService = rxSchedulerService; } @@ -117,7 +115,7 @@ namespace FileTime.App.Core.ViewModels SelectedsChildrenCollection = children.MapNull(c => new BindedCollection(c!)); }); - tab.CurrentLocation.Subscribe((_) => _markedItems.OnNext(Enumerable.Empty())); + tab.CurrentLocation.Subscribe((_) => _markedItems.Clear()); IObservable>?> InitSelectedsChildren() { @@ -195,21 +193,18 @@ namespace FileTime.App.Core.ViewModels throw new ArgumentException($"{nameof(item)} is not {nameof(IContainer)} neither {nameof(IElement)}"); } - public void AddMarkedItem(FullName item) + public void AddMarkedItem(IAbsolutePath item) => _markedItems.Add(item); + + public void RemoveMarkedItem(IAbsolutePath item) { - _currentMarkedItems.Add(item); - _markedItems.OnNext(_currentMarkedItems); + var itemsToRemove = _markedItems.Items.Where(i => i.Path.Path == item.Path.Path).ToList(); + + _markedItems.RemoveMany(itemsToRemove); } - public void RemoveMarkedItem(FullName item) + public void ToggleMarkedItem(IAbsolutePath item) { - _currentMarkedItems.RemoveAll(i => i.Path == item.Path); - _markedItems.OnNext(_currentMarkedItems); - } - - public void ToggleMarkedItem(FullName item) - { - if (_currentMarkedItems.Any(i => i.Path == item.Path)) + if (_markedItems.Items.Any(i => i.Path.Path == item.Path.Path)) { RemoveMarkedItem(item); } @@ -221,8 +216,7 @@ namespace FileTime.App.Core.ViewModels public void ClearMarkedItems() { - _currentMarkedItems.Clear(); - _markedItems.OnNext(_currentMarkedItems); + _markedItems.Clear(); } ~TabViewModel() => Dispose(false); diff --git a/src/Core/FileTime.Core.Abstraction/Command/ICommand.cs b/src/Core/FileTime.Core.Abstraction/Command/ICommand.cs new file mode 100644 index 0000000..d119d7e --- /dev/null +++ b/src/Core/FileTime.Core.Abstraction/Command/ICommand.cs @@ -0,0 +1,7 @@ +namespace FileTime.Core.Command +{ + public interface ICommand + { + + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Command/ITransportationCommand.cs b/src/Core/FileTime.Core.Abstraction/Command/ITransportationCommand.cs new file mode 100644 index 0000000..338c855 --- /dev/null +++ b/src/Core/FileTime.Core.Abstraction/Command/ITransportationCommand.cs @@ -0,0 +1,7 @@ +namespace FileTime.Core.Command +{ + public interface ITransportationCommand : ICommand + { + + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs b/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs new file mode 100644 index 0000000..528c95a --- /dev/null +++ b/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs @@ -0,0 +1,7 @@ +namespace FileTime.Core.Command.Copy +{ + public class CopyCommand : ITransportationCommand + { + + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/FileTime.Core.Command.csproj b/src/Core/FileTime.Core.Command/FileTime.Core.Command.csproj new file mode 100644 index 0000000..05261ba --- /dev/null +++ b/src/Core/FileTime.Core.Command/FileTime.Core.Command.csproj @@ -0,0 +1,13 @@ + + + + + + + + net6.0 + enable + enable + + + diff --git a/src/Core/FileTime.Core.Command/TransportMode.cs b/src/Core/FileTime.Core.Command/TransportMode.cs new file mode 100644 index 0000000..45b11cf --- /dev/null +++ b/src/Core/FileTime.Core.Command/TransportMode.cs @@ -0,0 +1,9 @@ +namespace FileTime.Core.Command +{ + public enum TransportMode + { + Merge, + Overwrite, + Skip + } +} \ No newline at end of file diff --git a/src/FileTime.sln b/src/FileTime.sln index 62117f1..a264f79 100644 --- a/src/FileTime.sln +++ b/src/FileTime.sln @@ -51,6 +51,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{8C3CFEFE EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Tools", "Tools\FileTime.Tools\FileTime.Tools.csproj", "{B7A45654-E56C-43C8-998E-0F4661395540}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Core.Command", "Core\FileTime.Core.Command\FileTime.Core.Command.csproj", "{1846BE76-8F68-4FC3-8954-871F14743F0B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -121,6 +123,10 @@ Global {B7A45654-E56C-43C8-998E-0F4661395540}.Debug|Any CPU.Build.0 = Debug|Any CPU {B7A45654-E56C-43C8-998E-0F4661395540}.Release|Any CPU.ActiveCfg = Release|Any CPU {B7A45654-E56C-43C8-998E-0F4661395540}.Release|Any CPU.Build.0 = Release|Any CPU + {1846BE76-8F68-4FC3-8954-871F14743F0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1846BE76-8F68-4FC3-8954-871F14743F0B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1846BE76-8F68-4FC3-8954-871F14743F0B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1846BE76-8F68-4FC3-8954-871F14743F0B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -143,6 +149,7 @@ Global {1500A537-2116-4111-B216-7632040619B0} = {2FC40FE1-4446-44AB-BF77-00F94D995FA3} {D7D1C76A-05B0-49BC-BCFF-06340E264EC1} = {01F231DE-4A65-435F-B4BB-77EE5221890C} {B7A45654-E56C-43C8-998E-0F4661395540} = {8C3CFEFE-78A5-4940-B388-D15FCE02ECE9} + {1846BE76-8F68-4FC3-8954-871F14743F0B} = {3324D046-1E05-46B5-B1BA-82910D56B332} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {859FB3DF-C60A-46B1-82E5-90274905D1EF}