WIP CommandScheduler UI

This commit is contained in:
2023-01-31 23:26:36 +01:00
parent 06a9fc27d7
commit 30ecc6e515
37 changed files with 553 additions and 181 deletions

View File

@@ -0,0 +1,13 @@
namespace FileTime.App.Core.UserCommand;
public class PauseCommandSchedulerCommand : IIdentifiableUserCommand
{
public const string CommandName = "pause_command_scheduler";
public static PauseCommandSchedulerCommand Instance { get; } = new();
private PauseCommandSchedulerCommand()
{
}
public string UserCommandID => CommandName;
}

View File

@@ -0,0 +1,13 @@
namespace FileTime.App.Core.UserCommand;
public sealed class StartCommandSchedulerCommand : IIdentifiableUserCommand
{
public const string CommandName = "start_command_scheduler";
public static StartCommandSchedulerCommand Instance { get; } = new();
private StartCommandSchedulerCommand()
{
}
public string UserCommandID => CommandName;
}

View File

@@ -1,7 +1,6 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Reactive.Subjects;
using FileTime.App.Core.Models.Enums; using FileTime.App.Core.Models.Enums;
using FileTime.Core.Timeline; using FileTime.App.Core.ViewModels.Timeline;
namespace FileTime.App.Core.ViewModels; namespace FileTime.App.Core.ViewModels;
@@ -13,6 +12,7 @@ public interface IAppState
IObservable<ViewMode> ViewMode { get; } IObservable<ViewMode> ViewMode { get; }
string RapidTravelText { get; set; } string RapidTravelText { get; set; }
ITabViewModel? CurrentSelectedTab { get; } ITabViewModel? CurrentSelectedTab { get; }
ITimelineViewModel TimelineViewModel { get; }
void AddTab(ITabViewModel tabViewModel); void AddTab(ITabViewModel tabViewModel);
void RemoveTab(ITabViewModel tabViewModel); void RemoveTab(ITabViewModel tabViewModel);

View File

@@ -0,0 +1,8 @@
namespace FileTime.App.Core.ViewModels.Timeline;
public interface ICommandTimeStateViewModel
{
IObservable<int> TotalProgress { get; }
IObservable<string> DisplayLabel { get; }
IObservable<bool> IsSelected { get; }
}

View File

@@ -0,0 +1,9 @@
using FileTime.Core.Models;
using FileTime.Core.Timeline;
namespace FileTime.App.Core.ViewModels.Timeline;
public interface IParallelCommandsViewModel
{
BindedCollection<ICommandTimeStateViewModel> Commands { get; }
}

View File

@@ -0,0 +1,8 @@
using FileTime.Core.Models;
namespace FileTime.App.Core.ViewModels.Timeline;
public interface ITimelineViewModel
{
BindedCollection<IParallelCommandsViewModel> ParallelCommandsGroups { get; }
}

View File

@@ -0,0 +1,25 @@
using FileTime.App.Core.UserCommand;
using FileTime.Core.Timeline;
namespace FileTime.App.Core.Services.UserCommandHandler;
public class CommandSchedulerUserCommandHandlerService : UserCommandHandlerServiceBase
{
private readonly ICommandScheduler _commandScheduler;
public CommandSchedulerUserCommandHandlerService(ICommandScheduler commandScheduler)
{
_commandScheduler = commandScheduler;
AddCommandHandlers(new IUserCommandHandler[]
{
new TypeUserCommandHandler<PauseCommandSchedulerCommand>(PauseCommandScheduler),
new TypeUserCommandHandler<StartCommandSchedulerCommand>(StartCommandScheduler),
});
}
private async Task PauseCommandScheduler()
=> await _commandScheduler.SetRunningEnabledAsync(false);
private async Task StartCommandScheduler()
=> await _commandScheduler.SetRunningEnabledAsync(true);
}

View File

@@ -52,7 +52,7 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi
SaveCurrentLocation(l => _currentLocation = l); SaveCurrentLocation(l => _currentLocation = l);
SaveCurrentSelectedItem(i => _currentSelectedItem = i); SaveCurrentSelectedItem(i => _currentSelectedItem = i);
_markedItems = new BindedCollection<FullName>(appState.SelectedTab.Select(t => t?.MarkedItems)); _markedItems = appState.SelectedTab.Select(t => t?.MarkedItems).ToBindedCollection();
AddCommandHandlers(new IUserCommandHandler[] AddCommandHandlers(new IUserCommandHandler[]
{ {

View File

@@ -3,6 +3,7 @@ using FileTime.App.Core.Services.UserCommandHandler;
using FileTime.App.Core.StartupServices; using FileTime.App.Core.StartupServices;
using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels;
using FileTime.App.Core.ViewModels.ItemPreview; using FileTime.App.Core.ViewModels.ItemPreview;
using FileTime.App.Core.ViewModels.Timeline;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
@@ -23,6 +24,7 @@ public static class Startup
serviceCollection.TryAddSingleton<IClipboardService, ClipboardService>(); serviceCollection.TryAddSingleton<IClipboardService, ClipboardService>();
serviceCollection.TryAddSingleton<IIdentifiableUserCommandService, IdentifiableUserCommandService>(); serviceCollection.TryAddSingleton<IIdentifiableUserCommandService, IdentifiableUserCommandService>();
serviceCollection.TryAddSingleton<IItemPreviewService, ItemPreviewService>(); serviceCollection.TryAddSingleton<IItemPreviewService, ItemPreviewService>();
serviceCollection.TryAddSingleton<ITimelineViewModel, TimelineViewModel>();
return serviceCollection return serviceCollection
.AddCommandHandlers() .AddCommandHandlers()
@@ -35,6 +37,7 @@ public static class Startup
return serviceCollection return serviceCollection
.AddSingleton<IUserCommandHandler, NavigationUserCommandHandlerService>() .AddSingleton<IUserCommandHandler, NavigationUserCommandHandlerService>()
.AddSingleton<IUserCommandHandler, ItemManipulationUserCommandHandlerService>() .AddSingleton<IUserCommandHandler, ItemManipulationUserCommandHandlerService>()
.AddSingleton<IUserCommandHandler, ToolUserCommandHandlerService>(); .AddSingleton<IUserCommandHandler, ToolUserCommandHandlerService>()
.AddSingleton<IUserCommandHandler, CommandSchedulerUserCommandHandlerService>();
} }
} }

View File

@@ -37,7 +37,9 @@ public class DefaultIdentifiableCommandHandlerRegister : IStartupHandler
AddUserCommand(PasteCommand.Merge); AddUserCommand(PasteCommand.Merge);
AddUserCommand(PasteCommand.Overwrite); AddUserCommand(PasteCommand.Overwrite);
AddUserCommand(PasteCommand.Skip); AddUserCommand(PasteCommand.Skip);
AddUserCommand(PauseCommandSchedulerCommand.Instance);
AddUserCommand(RefreshCommand.Instance); AddUserCommand(RefreshCommand.Instance);
AddUserCommand(StartCommandSchedulerCommand.Instance);
AddUserCommand(SwitchToTabCommand.SwitchToLastTab); AddUserCommand(SwitchToTabCommand.SwitchToLastTab);
AddUserCommand(SwitchToTabCommand.SwitchToTab1); AddUserCommand(SwitchToTabCommand.SwitchToTab1);
AddUserCommand(SwitchToTabCommand.SwitchToTab2); AddUserCommand(SwitchToTabCommand.SwitchToTab2);

View File

@@ -3,12 +3,14 @@ using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using DynamicData; using DynamicData;
using FileTime.App.Core.Models.Enums; using FileTime.App.Core.Models.Enums;
using FileTime.App.Core.ViewModels.Timeline;
using MvvmGen; using MvvmGen;
using MoreLinq; using MoreLinq;
namespace FileTime.App.Core.ViewModels; namespace FileTime.App.Core.ViewModels;
[ViewModel] [ViewModel]
[Inject(typeof(ITimelineViewModel), "TimelineViewModel", PropertyAccessModifier = AccessModifier.Public)]
public abstract partial class AppStateBase : IAppState public abstract partial class AppStateBase : IAppState
{ {
private readonly BehaviorSubject<string?> _searchText = new(null); private readonly BehaviorSubject<string?> _searchText = new(null);
@@ -76,7 +78,10 @@ public abstract partial class AppStateBase : IAppState
private ITabViewModel? GetSelectedTab(IEnumerable<ITabViewModel> tabs, ITabViewModel? expectedSelectedTab) private ITabViewModel? GetSelectedTab(IEnumerable<ITabViewModel> tabs, ITabViewModel? expectedSelectedTab)
{ {
var (preferred, others) = tabs.OrderBy(t => t.TabNumber).Partition(t => t.TabNumber >= (expectedSelectedTab?.TabNumber ?? 0)); var (preferred, others) =
tabs
.OrderBy(t => t.TabNumber)
.Partition(t => t.TabNumber >= (expectedSelectedTab?.TabNumber ?? 0));
return preferred.Concat(others.Reverse()).FirstOrDefault(); return preferred.Concat(others.Reverse()).FirstOrDefault();
} }
} }

View File

@@ -0,0 +1,21 @@
using System.Reactive.Subjects;
using FileTime.Core.Timeline;
namespace FileTime.App.Core.ViewModels.Timeline;
public class CommandTimeStateViewModel : ICommandTimeStateViewModel
{
public IObservable<int> TotalProgress { get; }
public IObservable<string> DisplayLabel { get; }
public IObservable<bool> IsSelected { get; }
public CommandTimeStateViewModel(CommandTimeState commandTimeState)
{
DisplayLabel = commandTimeState.Command.DisplayLabel;
TotalProgress = commandTimeState.Command.TotalProgress;
//TODO
IsSelected = new BehaviorSubject<bool>(false);
}
}

View File

@@ -0,0 +1,19 @@
using DynamicData.Alias;
using FileTime.Core.Extensions;
using FileTime.Core.Models;
using FileTime.Core.Timeline;
namespace FileTime.App.Core.ViewModels.Timeline;
public class ParallelCommandsViewModel : IParallelCommandsViewModel
{
public BindedCollection<ICommandTimeStateViewModel> Commands { get; }
public ParallelCommandsViewModel(ParallelCommands parallelCommands)
{
Commands = parallelCommands
.Commands
.Select(c => new CommandTimeStateViewModel(c) as ICommandTimeStateViewModel)
.ToBindedCollection();
}
}

View File

@@ -0,0 +1,20 @@
using DynamicData.Alias;
using FileTime.Core.Extensions;
using FileTime.Core.Models;
using FileTime.Core.Timeline;
namespace FileTime.App.Core.ViewModels.Timeline;
public class TimelineViewModel : ITimelineViewModel
{
public BindedCollection<IParallelCommandsViewModel> ParallelCommandsGroups { get; }
public TimelineViewModel(ICommandScheduler commandScheduler)
{
ParallelCommandsGroups =
commandScheduler
.CommandsToRun
.Select(p => new ParallelCommandsViewModel(p) as IParallelCommandsViewModel)
.ToBindedCollection();
}
}

View File

@@ -2,6 +2,7 @@ namespace FileTime.Core.Command;
public enum ExecutionState public enum ExecutionState
{ {
Initializing,
Waiting, Waiting,
Running, Running,
Finished Finished

View File

@@ -4,6 +4,10 @@ namespace FileTime.Core.Command;
public interface ICommand public interface ICommand
{ {
IObservable<string> DisplayLabel { get; }
IObservable<int> TotalProgress { get; }
IObservable<int> CurrentProgress { get; }
Task<CanCommandRun> CanRun(PointInTime currentTime); Task<CanCommandRun> CanRun(PointInTime currentTime);
Task<PointInTime> SimulateCommand(PointInTime currentTime); Task<PointInTime> SimulateCommand(PointInTime currentTime);
} }

View File

@@ -0,0 +1,27 @@
using DynamicData;
using FileTime.Core.Models;
namespace FileTime.Core.Extensions;
public static class BindedCollectionExtensions
{
public static BindedCollection<T> ToBindedCollection<T>(this IObservable<IChangeSet<T>> source)
{
return new BindedCollection<T>(source);
}
public static BindedCollection<T> ToBindedCollection<T>(this IObservable<IObservable<IChangeSet<T>>?> source)
{
return new BindedCollection<T>(source);
}
public static BindedCollection<T, TKey> ToBindedCollection<T, TKey>(this IObservable<IChangeSet<T, TKey>> source) where TKey : notnull
{
return new BindedCollection<T, TKey>(source);
}
public static BindedCollection<T, TKey> ToBindedCollection<T, TKey>(this IObservable<IObservable<IChangeSet<T, TKey>>?> source) where TKey : notnull
{
return new BindedCollection<T, TKey>(source);
}
}

View File

@@ -53,7 +53,7 @@ public static class DynamicDataExtensions
this IObservable<IChangeSet<AbsolutePath, string>> stream) this IObservable<IChangeSet<AbsolutePath, string>> stream)
=> await GetItemsAsync(stream.ToCollection()); => await GetItemsAsync(stream.ToCollection());
private static Task<IEnumerable<AbsolutePath>?> GetItemsAsync( public static Task<IEnumerable<AbsolutePath>?> GetItemsAsync(
this IObservable<IReadOnlyCollection<AbsolutePath>> stream) this IObservable<IReadOnlyCollection<AbsolutePath>> stream)
{ {
var taskCompletionSource = new TaskCompletionSource<IEnumerable<AbsolutePath>?>(); var taskCompletionSource = new TaskCompletionSource<IEnumerable<AbsolutePath>?>();

View File

@@ -3,7 +3,7 @@ using System.ComponentModel;
using DynamicData; using DynamicData;
using PropertyChanged.SourceGenerator; using PropertyChanged.SourceGenerator;
namespace FileTime.App.Core.Models; namespace FileTime.Core.Models;
public partial class BindedCollection<T> : IDisposable, INotifyPropertyChanged public partial class BindedCollection<T> : IDisposable, INotifyPropertyChanged
{ {

View File

@@ -4,18 +4,50 @@ namespace FileTime.Core.Timeline;
public class CommandTimeState public class CommandTimeState
{ {
private object _executionStateLock = new object();
private ExecutionState _executionState;
public ICommand Command { get; } public ICommand Command { get; }
public CanCommandRun CanRun { get; private set; } = CanCommandRun.False; public CanCommandRun CanRun { get; private set; } = CanCommandRun.False;
public bool ForceRun { get; set; } public bool ForceRun { get; set; }
public ExecutionState ExecutionState { get; set; }
public ExecutionState ExecutionState
{
get
{
lock (_executionStateLock)
{
return _executionState;
}
}
set
{
lock (_executionStateLock)
{
_executionState = value;
}
}
}
public CommandTimeState(ICommand command) public CommandTimeState(ICommand command)
{ {
Command = command; Command = command;
SetToWait();
} }
public async Task UpdateStateAsync(PointInTime? startPoint) public async Task UpdateStateAsync(PointInTime? startPoint)
{ {
CanRun = startPoint == null ? CanCommandRun.False : await Command.CanRun(startPoint); CanRun = startPoint == null ? CanCommandRun.False : await Command.CanRun(startPoint);
} }
private async void SetToWait()
{
// This command won't be shown in the scheduler until this timeout is over
// Short living commands while not "blink on" the list
await Task.Delay(100);
lock (_executionStateLock)
{
if (_executionState == ExecutionState.Initializing)
_executionState = ExecutionState.Waiting;
}
}
} }

View File

@@ -1,3 +1,4 @@
using DynamicData;
using FileTime.Core.Command; using FileTime.Core.Command;
using FileTime.Core.Models; using FileTime.Core.Models;
@@ -7,5 +8,8 @@ public interface ICommandScheduler
{ {
Task AddCommand(ICommand command, int? batchId = null, bool toNewBatch = false); Task AddCommand(ICommand command, int? batchId = null, bool toNewBatch = false);
IObservable<FullName> ContainerToRefresh { get; } IObservable<FullName> ContainerToRefresh { get; }
IObservable<IChangeSet<ParallelCommands>> CommandsToRun { get; }
bool IsRunningEnabled { get; }
void RefreshContainer(FullName container); void RefreshContainer(FullName container);
Task SetRunningEnabledAsync(bool value);
} }

View File

@@ -1,15 +1,32 @@
using DynamicData;
using FileTime.Core.Command; using FileTime.Core.Command;
using FileTime.Core.Extensions;
using FileTime.Core.Models;
namespace FileTime.Core.Timeline; namespace FileTime.Core.Timeline;
public class ParallelCommands public class ParallelCommands : IDisposable
{ {
private static ushort _idCounter; private static ushort _idCounter;
private List<CommandTimeState> _commands; private readonly SourceList<CommandTimeState> _commands;
private PointInTime? _startTime;
public ushort Id { get; } public ushort Id { get; }
public IReadOnlyList<CommandTimeState> Commands { get; }
public IObservable<IChangeSet<CommandTimeState>> Commands { get; }
public BindedCollection<CommandTimeState> CommandsCollection { get; }
public PointInTime? Result { get; private set; } public PointInTime? Result { get; private set; }
public PointInTime? StartTime => _startTime;
public async Task<PointInTime?> SetStartTimeAsync(PointInTime? startTime)
{
_startTime = startTime;
return await RefreshResult();
}
public ParallelCommands(PointInTime? result) public ParallelCommands(PointInTime? result)
: this(new List<CommandTimeState>(), result) : this(new List<CommandTimeState>(), result)
{ {
@@ -19,8 +36,10 @@ public class ParallelCommands
{ {
Id = _idCounter++; Id = _idCounter++;
_commands = commands; _commands = new SourceList<CommandTimeState>();
Commands = _commands.AsReadOnly(); _commands.Edit((innerList) => innerList.AddRange(commands));
Commands = _commands.Connect();
CommandsCollection = Commands.ToBindedCollection();
Result = result; Result = result;
} }
@@ -63,10 +82,17 @@ public class ParallelCommands
} }
} }
public async Task<PointInTime?> RefreshResult(PointInTime? startPoint) public async Task RemoveCommand(ICommand command)
{ {
var result = startPoint; var commandTimeState = _commands.Items.First(c => c.Command == command);
foreach (var commandTimeState in _commands) _commands.Remove(commandTimeState);
await RefreshResult();
}
public async Task<PointInTime?> RefreshResult()
{
var result = StartTime;
foreach (var commandTimeState in _commands.Items)
{ {
await commandTimeState.UpdateStateAsync(result); await commandTimeState.UpdateStateAsync(result);
if (result != null) if (result != null)
@@ -87,7 +113,8 @@ public class ParallelCommands
return Result; return Result;
} }
public void RemoveAt(int number) => _commands.RemoveAt(number); public void Dispose()
{
internal void Remove(CommandTimeState command) => _commands.Remove(command); CommandsCollection.Dispose();
}
} }

View File

@@ -0,0 +1,36 @@
using System.Reactive.Linq;
using System.Reactive.Subjects;
using FileTime.Core.Timeline;
namespace FileTime.Core.Command;
public abstract class CommandBase : ICommand
{
private readonly BehaviorSubject<string> _displayLabel;
private readonly BehaviorSubject<int> _totalProgress;
private readonly BehaviorSubject<int> _currentProgress;
public IObservable<string> DisplayLabel { get; }
public IObservable<int> TotalProgress { get; }
public IObservable<int> CurrentProgress { get; }
protected CommandBase(string displayLabel = "", int totalProgress = 0, int currentProgress = 0)
{
_displayLabel = new(displayLabel);
_totalProgress = new(totalProgress);
_currentProgress = new(currentProgress);
DisplayLabel = _displayLabel.AsObservable();
TotalProgress = _totalProgress.AsObservable();
CurrentProgress = _currentProgress.AsObservable();
}
public abstract Task<CanCommandRun> CanRun(PointInTime currentTime);
public abstract Task<PointInTime> SimulateCommand(PointInTime currentTime);
protected void SetDisplayLabel(string displayLabel) => _displayLabel.OnNext(displayLabel);
protected void SetTotalProgress(int totalProgress) => _totalProgress.OnNext(totalProgress);
protected void SetCurrentProgress(int currentProgress) => _currentProgress.OnNext(currentProgress);
}

View File

@@ -5,7 +5,7 @@ using FileTime.Core.Timeline;
namespace FileTime.Core.Command.Copy; namespace FileTime.Core.Command.Copy;
public class CopyCommand : ITransportationCommand public class CopyCommand : CommandBase, ITransportationCommand
{ {
private readonly ITimelessContentProvider _timelessContentProvider; private readonly ITimelessContentProvider _timelessContentProvider;
private readonly ICommandSchedulerNotifier _commandSchedulerNotifier; private readonly ICommandSchedulerNotifier _commandSchedulerNotifier;
@@ -22,18 +22,19 @@ public class CopyCommand : ITransportationCommand
public CopyCommand( public CopyCommand(
ITimelessContentProvider timelessContentProvider, ITimelessContentProvider timelessContentProvider,
ICommandSchedulerNotifier commandSchedulerNotifier) ICommandSchedulerNotifier commandSchedulerNotifier)
: base("Copy")
{ {
_timelessContentProvider = timelessContentProvider; _timelessContentProvider = timelessContentProvider;
_commandSchedulerNotifier = commandSchedulerNotifier; _commandSchedulerNotifier = commandSchedulerNotifier;
} }
public Task<CanCommandRun> CanRun(PointInTime currentTime) public override Task<CanCommandRun> CanRun(PointInTime currentTime)
{ {
//TODO: //TODO:
return Task.FromResult(CanCommandRun.True); return Task.FromResult(CanCommandRun.True);
} }
public async Task<PointInTime> SimulateCommand(PointInTime currentTime) public override async Task<PointInTime> SimulateCommand(PointInTime currentTime)
{ {
if (Sources == null) throw new ArgumentException(nameof(Sources) + " can not be null"); if (Sources == null) throw new ArgumentException(nameof(Sources) + " can not be null");
if (Target == null) throw new ArgumentException(nameof(Target) + " can not be null"); if (Target == null) throw new ArgumentException(nameof(Target) + " can not be null");

View File

@@ -7,7 +7,7 @@ using InitableService;
namespace FileTime.Core.Command.Create; namespace FileTime.Core.Command.Create;
public abstract class CreateItemBase : IExecutableCommand, IInitable<FullName, string> public abstract class CreateItemBase : CommandBase, IExecutableCommand, IInitable<FullName, string>
{ {
private readonly ITimelessContentProvider _timelessContentProvider; private readonly ITimelessContentProvider _timelessContentProvider;
private readonly IContentAccessorFactory _contentAccessorFactory; private readonly IContentAccessorFactory _contentAccessorFactory;
@@ -17,12 +17,13 @@ public abstract class CreateItemBase : IExecutableCommand, IInitable<FullName, s
protected CreateItemBase( protected CreateItemBase(
ITimelessContentProvider timelessContentProvider, ITimelessContentProvider timelessContentProvider,
IContentAccessorFactory contentAccessorFactory) IContentAccessorFactory contentAccessorFactory)
: base("Create")
{ {
_timelessContentProvider = timelessContentProvider; _timelessContentProvider = timelessContentProvider;
_contentAccessorFactory = contentAccessorFactory; _contentAccessorFactory = contentAccessorFactory;
} }
public async Task<CanCommandRun> CanRun(PointInTime currentTime) public override async Task<CanCommandRun> CanRun(PointInTime currentTime)
{ {
if (Parent is null) if (Parent is null)
{ {
@@ -49,7 +50,7 @@ public abstract class CreateItemBase : IExecutableCommand, IInitable<FullName, s
return existingItem switch return existingItem switch
{ {
null => CanCommandRun.True, null => CanCommandRun.True,
{ Type: AbsolutePathType.Container } => CanCommandRun.Forcable, {Type: AbsolutePathType.Container} => CanCommandRun.Forcable,
_ => CanCommandRun.False _ => CanCommandRun.False
}; };
} }
@@ -60,7 +61,7 @@ public abstract class CreateItemBase : IExecutableCommand, IInitable<FullName, s
return CanCommandRun.False; return CanCommandRun.False;
} }
public Task<PointInTime> SimulateCommand(PointInTime currentTime) public override Task<PointInTime> SimulateCommand(PointInTime currentTime)
{ {
if (Parent is null) if (Parent is null)
{ {

View File

@@ -5,7 +5,7 @@ using FileTime.Core.Timeline;
namespace FileTime.Core.Command.Delete; namespace FileTime.Core.Command.Delete;
public class DeleteCommand : IExecutableCommand public class DeleteCommand : CommandBase, IExecutableCommand
{ {
private readonly IContentAccessorFactory _contentAccessorFactory; private readonly IContentAccessorFactory _contentAccessorFactory;
private readonly ITimelessContentProvider _timelessContentProvider; private readonly ITimelessContentProvider _timelessContentProvider;
@@ -15,18 +15,19 @@ public class DeleteCommand : IExecutableCommand
public DeleteCommand( public DeleteCommand(
IContentAccessorFactory contentAccessorFactory, IContentAccessorFactory contentAccessorFactory,
ITimelessContentProvider timelessContentProvider) ITimelessContentProvider timelessContentProvider)
: base("Delete")
{ {
_contentAccessorFactory = contentAccessorFactory; _contentAccessorFactory = contentAccessorFactory;
_timelessContentProvider = timelessContentProvider; _timelessContentProvider = timelessContentProvider;
} }
public Task<CanCommandRun> CanRun(PointInTime currentTime) public override Task<CanCommandRun> CanRun(PointInTime currentTime)
{ {
//TODO //TODO
return Task.FromResult(CanCommandRun.True); return Task.FromResult(CanCommandRun.True);
} }
public Task<PointInTime> SimulateCommand(PointInTime currentTime) public override Task<PointInTime> SimulateCommand(PointInTime currentTime)
{ {
//TODO //TODO
return Task.FromResult(currentTime); return Task.FromResult(currentTime);
@@ -38,8 +39,8 @@ public class DeleteCommand : IExecutableCommand
//Delete //Delete
await TraverseTree( await TraverseTree(
PointInTime.Present, PointInTime.Present,
ItemsToDelete, ItemsToDelete,
new Dictionary<string, IItemDeleter>(), new Dictionary<string, IItemDeleter>(),
new DeleteStrategy() new DeleteStrategy()
); );
@@ -69,9 +70,9 @@ public class DeleteCommand : IExecutableCommand
if (itemToDelete is IContainer container) if (itemToDelete is IContainer container)
{ {
await TraverseTree( await TraverseTree(
currentTime, currentTime,
(await container.Items.GetItemsAsync())?.Select(i => i.Path) ?? Enumerable.Empty<FullName>(), (await container.Items.GetItemsAsync())?.Select(i => i.Path) ?? Enumerable.Empty<FullName>(),
itemDeleters, itemDeleters,
deleteStrategy deleteStrategy
); );
} }

View File

@@ -13,28 +13,31 @@ public class CommandScheduler : ICommandScheduler
private readonly Subject<FullName> _containerToRefresh = new(); private readonly Subject<FullName> _containerToRefresh = new();
private readonly object _guard = new(); private readonly object _guard = new();
private bool _enableRunning = true; private bool _isRunningEnabled = true;
private bool _resourceIsInUse; private bool _resourceIsInUse;
public IObservable<FullName> ContainerToRefresh { get; } public IObservable<FullName> ContainerToRefresh { get; }
public bool EnableRunning public bool IsRunningEnabled => _isRunningEnabled;
{
get
{
var result = true;
RunWithLock(() => result = _enableRunning);
return result;
}
set { RunWithLock(() => _enableRunning = value); } public async Task SetRunningEnabledAsync(bool value)
{
_isRunningEnabled = value;
if (value)
{
await RunWithLockAsync(ExecuteCommands);
}
} }
public IObservable<IChangeSet<ParallelCommands>> CommandsToRun { get; }
public CommandScheduler(ILocalCommandExecutor localExecutor) public CommandScheduler(ILocalCommandExecutor localExecutor)
{ {
CommandsToRun = _commandsToRun.Connect();
ContainerToRefresh = _containerToRefresh.AsObservable(); ContainerToRefresh = _containerToRefresh.AsObservable();
localExecutor.CommandFinished += LocalExecutorOnCommandFinished; localExecutor.CommandFinished += ExecutorOnCommandFinished;
_commandExecutors.Add(localExecutor); _commandExecutors.Add(localExecutor);
} }
@@ -46,6 +49,7 @@ public class CommandScheduler : ICommandScheduler
if (_commandsToRun.Count == 0) if (_commandsToRun.Count == 0)
{ {
//TODO: Add event handler to update
batchToAdd = new ParallelCommands(PointInTime.CreateEmpty()); batchToAdd = new ParallelCommands(PointInTime.CreateEmpty());
_commandsToRun.Add(batchToAdd); _commandsToRun.Add(batchToAdd);
} }
@@ -82,13 +86,11 @@ public class CommandScheduler : ICommandScheduler
private void ExecuteCommands() private void ExecuteCommands()
{ {
if (!_enableRunning) return; if (!_isRunningEnabled) return;
var parallelCommandsToExecute = _commandsToRun.Items.FirstOrDefault(); var commandsToExecute = _commandsToRun.Items.FirstOrDefault()?.CommandsCollection.Collection;
if (parallelCommandsToExecute is null || if (commandsToExecute is null || commandsToExecute.All(c => c.ExecutionState != ExecutionState.Initializing && c.ExecutionState != ExecutionState.Waiting)) return;
parallelCommandsToExecute.Commands.All(c => c.ExecutionState != ExecutionState.Waiting)) return;
var commandsToExecute = parallelCommandsToExecute.Commands;
foreach (var commandToExecute in commandsToExecute) foreach (var commandToExecute in commandsToExecute)
{ {
@@ -108,15 +110,20 @@ public class CommandScheduler : ICommandScheduler
return _commandExecutors[0]; return _commandExecutors[0];
} }
private void LocalExecutorOnCommandFinished(object? sender, ICommand command) private async void ExecutorOnCommandFinished(object? sender, ICommand command)
{ {
var parallelCommandsToExecute = _commandsToRun.Items.FirstOrDefault(); var firstCommandBlock = _commandsToRun
if (parallelCommandsToExecute is null) return; .Items
.FirstOrDefault();
var state = firstCommandBlock
?.CommandsCollection
.Collection
?.FirstOrDefault(c => c.Command == command);
var state = parallelCommandsToExecute.Commands.FirstOrDefault(c => c.Command == command);
if (state is null) return; if (state is null) return;
state.ExecutionState = ExecutionState.Finished; state.ExecutionState = ExecutionState.Finished;
if (firstCommandBlock is not null) await firstCommandBlock.RemoveCommand(command);
} }
private async Task RefreshCommands() private async Task RefreshCommands()
@@ -125,7 +132,7 @@ public class CommandScheduler : ICommandScheduler
foreach (var batch in _commandsToRun.Items) foreach (var batch in _commandsToRun.Items)
{ {
currentTime = await batch.RefreshResult(currentTime); currentTime = await batch.SetStartTimeAsync(currentTime);
} }
} }

View File

@@ -5,7 +5,7 @@ namespace FileTime.Core.Timeline;
public class LocalCommandExecutor : ILocalCommandExecutor public class LocalCommandExecutor : ILocalCommandExecutor
{ {
private readonly ICommandRunner _commandRunner; private readonly ICommandRunner _commandRunner;
public event EventHandler<ICommand> CommandFinished; public event EventHandler<ICommand>? CommandFinished;
public LocalCommandExecutor(ICommandRunner commandRunner) public LocalCommandExecutor(ICommandRunner commandRunner)
{ {
@@ -15,7 +15,7 @@ public class LocalCommandExecutor : ILocalCommandExecutor
public void ExecuteCommand(ICommand command) public void ExecuteCommand(ICommand command)
{ {
var context = new CommandRunnerContext(command); var context = new CommandRunnerContext(command);
var thread = new Thread(new ParameterizedThreadStart(RunCommand)); var thread = new Thread(RunCommand);
thread.Start(context); thread.Start(context);
} }
@@ -29,6 +29,6 @@ public class LocalCommandExecutor : ILocalCommandExecutor
} }
catch (Exception ex) { } catch (Exception ex) { }
CommandFinished.Invoke(this, context.Command); CommandFinished?.Invoke(this, context.Command);
} }
} }

View File

@@ -56,6 +56,7 @@ public static class MainConfiguration
//new CommandBindingConfiguration(ConfigCommand.Cut, new[] { Key.D, Key.D }), //new CommandBindingConfiguration(ConfigCommand.Cut, new[] { Key.D, Key.D }),
//new CommandBindingConfiguration(ConfigCommand.Edit, new KeyConfig(Key.F4)), //new CommandBindingConfiguration(ConfigCommand.Edit, new KeyConfig(Key.F4)),
new(EnterRapidTravelCommand.CommandName,new KeyConfig(Key.OemComma, shift: true)), new(EnterRapidTravelCommand.CommandName,new KeyConfig(Key.OemComma, shift: true)),
new(EnterRapidTravelCommand.CommandName,new KeyConfig(Key.OemQuestion, shift: true)),
//new CommandBindingConfiguration(ConfigCommand.FindByName, new[] { Key.F, Key.N }), //new CommandBindingConfiguration(ConfigCommand.FindByName, new[] { Key.F, Key.N }),
//new CommandBindingConfiguration(ConfigCommand.FindByNameRegex, new[] { Key.F, Key.R }), //new CommandBindingConfiguration(ConfigCommand.FindByNameRegex, new[] { Key.F, Key.R }),
new(GoToHomeCommand.CommandName, new[] { Key.G, Key.H }), new(GoToHomeCommand.CommandName, new[] { Key.G, Key.H }),
@@ -92,9 +93,9 @@ public static class MainConfiguration
new(SwitchToTabCommand.SwitchToTab6CommandName, Key.D6), new(SwitchToTabCommand.SwitchToTab6CommandName, Key.D6),
new(SwitchToTabCommand.SwitchToTab7CommandName, Key.D7), new(SwitchToTabCommand.SwitchToTab7CommandName, Key.D7),
new(SwitchToTabCommand.SwitchToTab8CommandName, Key.D8), new(SwitchToTabCommand.SwitchToTab8CommandName, Key.D8),
//new CommandBindingConfiguration(ConfigCommand.TimelinePause, new[] { Key.T, Key.P }), new (PauseCommandSchedulerCommand.CommandName, new[] { Key.T, Key.P }),
//new CommandBindingConfiguration(ConfigCommand.TimelineRefresh, new[] { Key.T, Key.R }), //new CommandBindingConfiguration(ConfigCommand.TimelineRefresh, new[] { Key.T, Key.R }),
//new CommandBindingConfiguration(ConfigCommand.TimelineStart, new[] { Key.T, Key.S }), new (StartCommandSchedulerCommand.CommandName, new[] { Key.T, Key.S }),
//new CommandBindingConfiguration(ConfigCommand.ToggleAdvancedIcons, new[] { Key.Z, Key.I }), //new CommandBindingConfiguration(ConfigCommand.ToggleAdvancedIcons, new[] { Key.Z, Key.I }),
new(GoUpCommand.CommandName, Key.Left), new(GoUpCommand.CommandName, Key.Left),
new(OpenSelectedCommand.CommandName, Key.Right), new(OpenSelectedCommand.CommandName, Key.Right),

View File

@@ -1,6 +1,7 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using FileTime.App.Core.Models; using FileTime.App.Core.Models;
using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels;
using FileTime.Core.Models;
using FileTime.GuiApp.Configuration; using FileTime.GuiApp.Configuration;
namespace FileTime.GuiApp.ViewModels; namespace FileTime.GuiApp.ViewModels;

View File

@@ -1,15 +1,20 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using FileTime.App.Core.Models;
using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels;
using FileTime.App.Core.ViewModels.Timeline;
using FileTime.Core.Models;
using FileTime.GuiApp.Configuration; using FileTime.GuiApp.Configuration;
using FileTime.GuiApp.ViewModels; using FileTime.GuiApp.ViewModels;
using MvvmGen; using MvvmGen;
namespace FileTime.GuiApp.CustomImpl.ViewModels; namespace FileTime.GuiApp.CustomImpl.ViewModels;
[ViewModel] [ViewModel(GenerateConstructor = false)]
public partial class GuiAppState : AppStateBase, IGuiAppState public partial class GuiAppState : AppStateBase, IGuiAppState
{ {
public GuiAppState(ITimelineViewModel timelineViewModel) : base(timelineViewModel)
{
}
[Property] private bool _isAllShortcutVisible; [Property] private bool _isAllShortcutVisible;
[Property] private bool _noCommandFound; [Property] private bool _noCommandFound;
@@ -18,7 +23,7 @@ public partial class GuiAppState : AppStateBase, IGuiAppState
[Property] private BindedCollection<RootDriveInfo, string> _rootDriveInfos = new(); [Property] private BindedCollection<RootDriveInfo, string> _rootDriveInfos = new();
[Property] private IReadOnlyList<PlaceInfo> _places; [Property] private IReadOnlyList<PlaceInfo> _places = new List<PlaceInfo>();
public List<KeyConfig> PreviousKeys { get; } = new(); public List<KeyConfig> PreviousKeys { get; } = new();
public ObservableCollection<string> PopupTexts { get; } = new(); public ObservableCollection<string> PopupTexts { get; } = new();

View File

@@ -1,9 +1,10 @@
<Styles xmlns="https://github.com/avaloniaui" <Styles
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Styles.Resources> <Styles.Resources>
<ResourceDictionary> <ResourceDictionary>
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="avares://FileTime.GuiApp/Resources/Converters.axaml"></ResourceInclude> <ResourceInclude Source="avares://FileTime.GuiApp/Resources/Converters.axaml" />
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
</ResourceDictionary> </ResourceDictionary>
</Styles.Resources> </Styles.Resources>
@@ -28,7 +29,7 @@
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />
<Setter Property="HorizontalAlignment" Value="Stretch" /> <Setter Property="HorizontalAlignment" Value="Stretch" />
</Style> </Style>
<Style Selector="ListBox.ContentListView > ListBoxItem"> <Style Selector="ListBox.ContentListView &gt; ListBoxItem">
<Setter Property="Margin" Value="0" /> <Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="0" /> <Setter Property="Padding" Value="0" />
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />
@@ -61,10 +62,10 @@
<ControlTemplate> <ControlTemplate>
<Border> <Border>
<RadioButton <RadioButton
VerticalAlignment="Center"
Content="{TemplateBinding ContentPresenter.Content}" Content="{TemplateBinding ContentPresenter.Content}"
Foreground="{DynamicResource ForegroundBrush}" Foreground="{DynamicResource ForegroundBrush}"
IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
VerticalAlignment="Center" />
</Border> </Border>
</ControlTemplate> </ControlTemplate>
</Setter.Value> </Setter.Value>
@@ -74,9 +75,9 @@
<Style Selector="Image.LoadingAnimation"> <Style Selector="Image.LoadingAnimation">
<Style.Animations> <Style.Animations>
<Animation <Animation
Duration="0:0:2"
Easing="QuadraticEaseInOut" Easing="QuadraticEaseInOut"
IterationCount="INFINITE"> IterationCount="INFINITE"
Duration="0:0:2">
<KeyFrame Cue="0%"> <KeyFrame Cue="0%">
<Setter Property="RotateTransform.Angle" Value="45" /> <Setter Property="RotateTransform.Angle" Value="45" />
</KeyFrame> </KeyFrame>
@@ -86,4 +87,8 @@
</Animation> </Animation>
</Style.Animations> </Style.Animations>
</Style> </Style>
<Style Selector="Border.SelectedTimelineCommand">
<Setter Property="BorderBrush" Value="{DynamicResource ForegroundBrush}" />
</Style>
</Styles> </Styles>

View File

@@ -4,6 +4,7 @@ using FileTime.App.Core.Models;
using FileTime.App.Core.Services; using FileTime.App.Core.Services;
using FileTime.App.Core.UserCommand; using FileTime.App.Core.UserCommand;
using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels;
using FileTime.Core.Extensions;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.GuiApp.Configuration; using FileTime.GuiApp.Configuration;
using FileTime.GuiApp.Extensions; using FileTime.GuiApp.Extensions;
@@ -43,8 +44,8 @@ public class DefaultModeKeyInputHandler : IDefaultModeKeyInputHandler
_appState.SelectedTab.Subscribe(t => _selectedTab = t); _appState.SelectedTab.Subscribe(t => _selectedTab = t);
_appState.SelectedTab.Select(t => t == null ? Observable.Return<IContainer?>(null) : t.CurrentLocation!).Switch().Subscribe(l => _currentLocation = l); _appState.SelectedTab.Select(t => t == null ? Observable.Return<IContainer?>(null) : t.CurrentLocation!).Switch().Subscribe(l => _currentLocation = l);
_openModals = new BindedCollection<IModalViewModel>(modalService.OpenModals); _openModals = modalService.OpenModals.ToBindedCollection();
_keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.Up) }); _keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.Up) });
_keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.Down) }); _keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.Down) });

View File

@@ -3,6 +3,7 @@ using FileTime.App.Core.Models;
using FileTime.App.Core.Services; using FileTime.App.Core.Services;
using FileTime.App.Core.UserCommand; using FileTime.App.Core.UserCommand;
using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels;
using FileTime.Core.Extensions;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Services; using FileTime.Core.Services;
using FileTime.GuiApp.Configuration; using FileTime.GuiApp.Configuration;
@@ -42,7 +43,7 @@ public class RapidTravelModeKeyInputHandler : IRapidTravelModeKeyInputHandler
_appState.SelectedTab.Subscribe(t => _selectedTab = t); _appState.SelectedTab.Subscribe(t => _selectedTab = t);
_openModals = new BindedCollection<IModalViewModel>(modalService.OpenModals); _openModals = modalService.OpenModals.ToBindedCollection();
} }
public async Task HandleInputKey(Key key, SpecialKeysStatus specialKeysStatus, Action<bool> setHandled) public async Task HandleInputKey(Key key, SpecialKeysStatus specialKeysStatus, Action<bool> setHandled)

View File

@@ -4,6 +4,7 @@ using DynamicData;
using DynamicData.Binding; using DynamicData.Binding;
using FileTime.App.Core.Models; using FileTime.App.Core.Models;
using FileTime.App.Core.Services; using FileTime.App.Core.Services;
using FileTime.Core.Extensions;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
using FileTime.GuiApp.ViewModels; using FileTime.GuiApp.ViewModels;
@@ -16,7 +17,7 @@ public class RootDriveInfoService : IStartupHandler
private readonly SourceList<DriveInfo> _rootDrives = new(); private readonly SourceList<DriveInfo> _rootDrives = new();
public RootDriveInfoService( public RootDriveInfoService(
IGuiAppState guiAppState, IGuiAppState guiAppState,
ILocalContentProvider localContentProvider, ILocalContentProvider localContentProvider,
ITimelessContentProvider timelessContentProvider) ITimelessContentProvider timelessContentProvider)
{ {
@@ -53,7 +54,7 @@ public class RootDriveInfoService : IStartupHandler
.Transform(t => new RootDriveInfo(t.Drive, t.Container)) .Transform(t => new RootDriveInfo(t.Drive, t.Container))
.Sort(SortExpressionComparer<RootDriveInfo>.Ascending(d => d.Name)); .Sort(SortExpressionComparer<RootDriveInfo>.Ascending(d => d.Name));
guiAppState.RootDriveInfos = new BindedCollection<RootDriveInfo, string>(rootDriveInfos); guiAppState.RootDriveInfos = rootDriveInfos.ToBindedCollection();
void InitRootDrives() void InitRootDrives()
{ {

View File

@@ -2,35 +2,35 @@
x:Class="FileTime.GuiApp.Views.MainWindow" x:Class="FileTime.GuiApp.Views.MainWindow"
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:appCoreModels="using:FileTime.App.Core.Models"
xmlns:config="using:FileTime.GuiApp.Configuration"
xmlns:corevm="using:FileTime.App.Core.ViewModels" xmlns:corevm="using:FileTime.App.Core.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:interactions="using:FileTime.Core.Interactions"
xmlns:local="using:FileTime.GuiApp.Views" xmlns:local="using:FileTime.GuiApp.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:FileTime.GuiApp.ViewModels" xmlns:vm="using:FileTime.GuiApp.ViewModels"
xmlns:config="using:FileTime.GuiApp.Configuration"
xmlns:appCoreModels="using:FileTime.App.Core.Models"
xmlns:interactions="using:FileTime.Core.Interactions"
MinHeight="600"
MinWidth="800"
Title="FileTime" Title="FileTime"
MinWidth="800"
MinHeight="600"
d:DesignHeight="450" d:DesignHeight="450"
d:DesignWidth="800" d:DesignWidth="800"
x:CompileBindings="True" x:CompileBindings="True"
x:DataType="vm:IMainWindowViewModelBase" x:DataType="vm:IMainWindowViewModelBase"
Background="Transparent" Background="Transparent"
Closed="OnWindowClosed"
ExtendClientAreaToDecorationsHint="True" ExtendClientAreaToDecorationsHint="True"
Icon="/Assets/filetime.ico" Icon="/Assets/filetime.ico"
InputElement.KeyDown="OnKeyDown" InputElement.KeyDown="OnKeyDown"
Opened="OnWindowOpened" Opened="OnWindowOpened"
Closed="OnWindowClosed"
TransparencyLevelHint="Blur" TransparencyLevelHint="Blur"
mc:Ignorable="d"> mc:Ignorable="d">
<Window.Resources> <Window.Resources>
<ResourceDictionary> <ResourceDictionary>
<ResourceDictionary.MergedDictionaries> <ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="avares://FileTime.GuiApp/Resources/SolarizedDarkTheme.axaml"></ResourceInclude> <ResourceInclude Source="avares://FileTime.GuiApp/Resources/SolarizedDarkTheme.axaml" />
<ResourceInclude Source="avares://FileTime.GuiApp/Resources/Brushes.axaml"></ResourceInclude> <ResourceInclude Source="avares://FileTime.GuiApp/Resources/Brushes.axaml" />
<ResourceInclude Source="avares://FileTime.GuiApp/Resources/Converters.axaml"></ResourceInclude> <ResourceInclude Source="avares://FileTime.GuiApp/Resources/Converters.axaml" />
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
</ResourceDictionary> </ResourceDictionary>
</Window.Resources> </Window.Resources>
@@ -62,18 +62,22 @@
<StackPanel <StackPanel
Margin="20,10" Margin="20,10"
Orientation="Horizontal"> Orientation="Horizontal">
<local:PathPresenter <local:PathPresenter DataContext="{Binding AppState.SelectedTab^.CurrentLocation^.FullName.Path, Converter={StaticResource PathPreformatter}}" />
DataContext="{Binding AppState.SelectedTab^.CurrentLocation^.FullName.Path,Converter={StaticResource PathPreformatter}}" />
<TextBlock <TextBlock
Foreground="{StaticResource AccentBrush}" Foreground="{StaticResource AccentBrush}"
Text="{Binding AppState.SelectedTab^.CurrentSelectedItem^.DisplayNameText}" /> Text="{Binding AppState.SelectedTab^.CurrentSelectedItem^.DisplayNameText}" />
</StackPanel> </StackPanel>
</Grid> </Grid>
<Grid Grid.Row="1" RowDefinitions="Auto,Auto,Auto,Auto"> <Grid
Grid.Row="1"
RowDefinitions="Auto,Auto,Auto,Auto">
<Border CornerRadius="10" Background="{DynamicResource ContainerBackgroundBrush}" Padding="10" <Border
Margin="10"> Margin="10"
Padding="10"
Background="{DynamicResource ContainerBackgroundBrush}"
CornerRadius="10">
<Grid RowDefinitions="Auto,Auto"> <Grid RowDefinitions="Auto,Auto">
<TextBlock <TextBlock
@@ -85,10 +89,14 @@
Items="{Binding AppState.RootDriveInfos.Collection}"> Items="{Binding AppState.RootDriveInfos.Collection}">
<ItemsRepeater.ItemTemplate> <ItemsRepeater.ItemTemplate>
<DataTemplate x:DataType="vm:RootDriveInfo"> <DataTemplate x:DataType="vm:RootDriveInfo">
<Grid Classes="SidebarContainerPresenter" <Grid
PointerPressed="OnHasContainerPointerPressed" Cursor="Hand"> Classes="SidebarContainerPresenter"
<Grid Margin="0,5" ColumnDefinitions="Auto,*,Auto" Cursor="Hand"
RowDefinitions="Auto,Auto"> PointerPressed="OnHasContainerPointerPressed">
<Grid
Margin="0,5"
ColumnDefinitions="Auto,*,Auto"
RowDefinitions="Auto,Auto">
<Image <Image
Grid.RowSpan="2" Grid.RowSpan="2"
Width="20" Width="20"
@@ -99,8 +107,8 @@
<StackPanel <StackPanel
Grid.Column="1" Grid.Column="1"
VerticalAlignment="Center"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Orientation="Horizontal"> Orientation="Horizontal">
<TextBlock <TextBlock
@@ -112,35 +120,38 @@
Margin="5,0,0,0" Margin="5,0,0,0"
VerticalAlignment="Center" VerticalAlignment="Center"
Classes="ExtraSmallText" Classes="ExtraSmallText"
Text="{Binding Label}" IsVisible="{Binding Label, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
IsVisible="{Binding Label,Converter={x:Static StringConverters.IsNotNullOrEmpty}}" /> Text="{Binding Label}" />
</StackPanel> </StackPanel>
<StackPanel <StackPanel
HorizontalAlignment="Right"
Grid.Column="2" Grid.Column="2"
Orientation="Horizontal" HorizontalAlignment="Right"
VerticalAlignment="Center"> VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock Classes="SmallText" VerticalAlignment="Center" <TextBlock
Text="{Binding Free, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}"> VerticalAlignment="Center"
</TextBlock> Classes="SmallText"
Text="{Binding Free, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}" />
<TextBlock Classes="SmallText" VerticalAlignment="Center" <TextBlock
Text=" / "> VerticalAlignment="Center"
</TextBlock> Classes="SmallText"
Text=" / " />
<TextBlock Classes="SmallText" VerticalAlignment="Center" <TextBlock
Text="{Binding Size, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}"> VerticalAlignment="Center"
</TextBlock> Classes="SmallText"
Text="{Binding Size, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}" />
</StackPanel> </StackPanel>
<ProgressBar <ProgressBar
Margin="5,0,0,0" Grid.Row="1"
Grid.Column="1" Grid.Column="1"
Grid.ColumnSpan="2" Grid.ColumnSpan="2"
Grid.Row="1"
MinWidth="100" MinWidth="100"
Margin="5,0,0,0"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Maximum="100" Maximum="100"
Value="{Binding UsedPercentage}" /> Value="{Binding UsedPercentage}" />
@@ -152,8 +163,12 @@
</Grid> </Grid>
</Border> </Border>
<Border Grid.Row="1" CornerRadius="10" Background="{DynamicResource ContainerBackgroundBrush}" <Border
Padding="0,10" Margin="10"> Grid.Row="1"
Margin="10"
Padding="0,10"
Background="{DynamicResource ContainerBackgroundBrush}"
CornerRadius="10">
<Grid RowDefinitions="Auto,Auto"> <Grid RowDefinitions="Auto,Auto">
<TextBlock <TextBlock
@@ -165,15 +180,19 @@
Items="{Binding AppState.Places}"> Items="{Binding AppState.Places}">
<ItemsRepeater.ItemTemplate> <ItemsRepeater.ItemTemplate>
<DataTemplate x:DataType="vm:PlaceInfo"> <DataTemplate x:DataType="vm:PlaceInfo">
<Grid Classes="SidebarContainerPresenter" <Grid
PointerPressed="OnHasContainerPointerPressed" Cursor="Hand"> Classes="SidebarContainerPresenter"
<StackPanel Orientation="Horizontal" Margin="10,5" Cursor="Hand"
HorizontalAlignment="Stretch"> PointerPressed="OnHasContainerPointerPressed">
<StackPanel
Margin="10,5"
HorizontalAlignment="Stretch"
Orientation="Horizontal">
<Image <Image
Width="20" Width="20"
Height="20" Height="20"
VerticalAlignment="Center" VerticalAlignment="Center"
Source="{Binding Container,Converter={StaticResource ItemToImageConverter}}" /> Source="{Binding Container, Converter={StaticResource ItemToImageConverter}}" />
<TextBlock <TextBlock
Margin="5,0,0,0" Margin="5,0,0,0"
@@ -258,6 +277,46 @@
Grid.Column="1" Grid.Column="1"
RowDefinitions="Auto,40,*,Auto"> RowDefinitions="Auto,40,*,Auto">
<Grid>
<ItemsControl Items="{Binding AppState.TimelineViewModel.ParallelCommandsGroups.Collection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border
MaxHeight="200"
Margin="0,0,10,0"
Padding="5"
Background="{DynamicResource ContainerBackgroundColor}"
CornerRadius="10">
<ScrollViewer>
<ItemsControl Items="{Binding Commands.Collection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border
BorderThickness="1"
Classes.SelectedTimelineCommand="{Binding IsSelected}">
<StackPanel>
<TextBlock Text="{Binding DisplayLabel^}" />
<ProgressBar
Margin="0,5,0,0"
Maximum="100"
Value="{Binding TotalProgress^}" />
</StackPanel>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
<ItemsControl <ItemsControl
Grid.Row="1" Grid.Row="1"
Items="{Binding AppState.Tabs}"> Items="{Binding AppState.Tabs}">
@@ -308,9 +367,9 @@
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate x:DataType="corevm:IItemViewModel"> <DataTemplate x:DataType="corevm:IItemViewModel">
<local:ItemView <local:ItemView
ShowAttributes="False"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch" /> HorizontalContentAlignment="Stretch"
ShowAttributes="False" />
</DataTemplate> </DataTemplate>
</ListBox.ItemTemplate> </ListBox.ItemTemplate>
</ListBox> </ListBox>
@@ -326,8 +385,7 @@
<Grid <Grid
Grid.Column="2" Grid.Column="2"
RowDefinitions="Auto,*"> RowDefinitions="Auto,*">
<Grid <Grid IsVisible="{Binding AppState.SelectedTab^.CurrentLocation^.IsLoading^, FallbackValue=False}">
IsVisible="{Binding AppState.SelectedTab^.CurrentLocation^.IsLoading^, FallbackValue=False}">
<Image <Image
Width="40" Width="40"
Height="40" Height="40"
@@ -341,9 +399,9 @@
Classes="ContentListView" Classes="ContentListView"
IsTabStop="True" IsTabStop="True"
Items="{Binding AppState.SelectedTab^.CurrentItemsCollection.Collection}" Items="{Binding AppState.SelectedTab^.CurrentItemsCollection.Collection}"
SelectedItem="{Binding AppState.SelectedTab^.CurrentSelectedItem^}"
ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ScrollViewer.VerticalScrollBarVisibility="Visible"> ScrollViewer.VerticalScrollBarVisibility="Visible"
SelectedItem="{Binding AppState.SelectedTab^.CurrentSelectedItem^}">
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate x:DataType="corevm:IItemViewModel"> <DataTemplate x:DataType="corevm:IItemViewModel">
<local:ItemView <local:ItemView
@@ -375,10 +433,8 @@
Fill="{DynamicResource ContentSeparatorBrush}" /> Fill="{DynamicResource ContentSeparatorBrush}" />
<Grid Grid.Column="4"> <Grid Grid.Column="4">
<Grid <Grid IsVisible="{Binding ItemPreviewService.ItemPreview^, Converter={x:Static ObjectConverters.IsNull}}">
IsVisible="{Binding ItemPreviewService.ItemPreview^,Converter={x:Static ObjectConverters.IsNull}}"> <Grid IsVisible="{Binding AppState.SelectedTab^.SelectedsChildrenCollection.Collection, Converter={x:Static ObjectConverters.IsNotNull}, FallbackValue=False}">
<Grid
IsVisible="{Binding AppState.SelectedTab^.SelectedsChildrenCollection.Collection, Converter={x:Static ObjectConverters.IsNotNull}, FallbackValue=False}">
<ListBox <ListBox
x:Name="ChildItems" x:Name="ChildItems"
x:CompileBindings="False" x:CompileBindings="False"
@@ -428,26 +484,31 @@
</ItemsRepeater> </ItemsRepeater>
</Grid> </Grid>
</Grid> </Grid>
<Grid <Grid IsVisible="{Binding ItemPreviewService.ItemPreview^, Converter={x:Static ObjectConverters.IsNotNull}}">
IsVisible="{Binding ItemPreviewService.ItemPreview^,Converter={x:Static ObjectConverters.IsNotNull}}"> <TextBlock
<TextBlock HorizontalAlignment="Center" Text="Don't know how to preview this item." HorizontalAlignment="Center"
IsVisible="{Binding ItemPreviewService.ItemPreview^.Mode, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static appCoreModels:ItemPreviewMode.Unknown},FallbackValue={x:Static appCoreModels:ItemPreviewMode.Unknown}}" /> IsVisible="{Binding ItemPreviewService.ItemPreview^.Mode, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static appCoreModels:ItemPreviewMode.Unknown}, FallbackValue={x:Static appCoreModels:ItemPreviewMode.Unknown}}"
<TextBlock HorizontalAlignment="Center" Text="Empty" Text="Don't know how to preview this item." />
IsVisible="{Binding ItemPreviewService.ItemPreview^.Mode, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static appCoreModels:ItemPreviewMode.Empty},FallbackValue={x:Static appCoreModels:ItemPreviewMode.Unknown}}" /> <TextBlock
<ScrollViewer HorizontalAlignment="Center"
IsVisible="{Binding ItemPreviewService.ItemPreview^.Mode, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static appCoreModels:ItemPreviewMode.Text},FallbackValue={x:Static appCoreModels:ItemPreviewMode.Unknown}}"> IsVisible="{Binding ItemPreviewService.ItemPreview^.Mode, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static appCoreModels:ItemPreviewMode.Empty}, FallbackValue={x:Static appCoreModels:ItemPreviewMode.Unknown}}"
Text="Empty" />
<ScrollViewer IsVisible="{Binding ItemPreviewService.ItemPreview^.Mode, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static appCoreModels:ItemPreviewMode.Text}, FallbackValue={x:Static appCoreModels:ItemPreviewMode.Unknown}}">
<TextBox <TextBox
IsReadOnly="True"
x:CompileBindings="False" x:CompileBindings="False"
IsReadOnly="True"
Text="{Binding ItemPreviewService.ItemPreview^.TextContent}" /> Text="{Binding ItemPreviewService.ItemPreview^.TextContent}" />
</ScrollViewer> </ScrollViewer>
</Grid> </Grid>
</Grid> </Grid>
</Grid> </Grid>
<ItemsRepeater Items="{Binding AppState.PopupTexts}" Margin="0,0,0,20" <ItemsRepeater
HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,0,0,20"
IsVisible="{Binding AppState.PopupTexts.Count,Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}"> HorizontalAlignment="Center"
VerticalAlignment="Top"
IsVisible="{Binding AppState.PopupTexts.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}"
Items="{Binding AppState.PopupTexts}">
<ItemsRepeater.Styles> <ItemsRepeater.Styles>
<Style Selector="TextBlock"> <Style Selector="TextBlock">
<Style.Animations> <Style.Animations>
@@ -464,11 +525,14 @@
</ItemsRepeater.Styles> </ItemsRepeater.Styles>
<ItemsRepeater.ItemTemplate> <ItemsRepeater.ItemTemplate>
<DataTemplate x:DataType="x:String"> <DataTemplate x:DataType="x:String">
<Border Background="{DynamicResource ContainerGradientBackgroundBrush}" Margin="5" <Border
Padding="5"> Margin="5"
<TextBlock Text="{Binding}" Padding="5"
Foreground="{DynamicResource AccentComplementBrush}" Background="{DynamicResource ContainerGradientBackgroundBrush}">
HorizontalAlignment="Center" /> <TextBlock
HorizontalAlignment="Center"
Foreground="{DynamicResource AccentComplementBrush}"
Text="{Binding}" />
</Border> </Border>
</DataTemplate> </DataTemplate>
</ItemsRepeater.ItemTemplate> </ItemsRepeater.ItemTemplate>
@@ -477,7 +541,7 @@
<Grid Grid.Row="3"> <Grid Grid.Row="3">
<Grid <Grid
IsVisible="{Binding AppState.ViewMode^, Converter={StaticResource EqualityConverter},ConverterParameter=RapidTravel}" IsVisible="{Binding AppState.ViewMode^, Converter={StaticResource EqualityConverter}, ConverterParameter=RapidTravel}"
RowDefinitions="1,Auto"> RowDefinitions="1,Auto">
<Rectangle <Rectangle
@@ -502,8 +566,7 @@
</StackPanel> </StackPanel>
</Grid> </Grid>
<Grid <Grid IsVisible="{Binding AppState.PossibleCommands.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
IsVisible="{Binding AppState.PossibleCommands.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="1" /> <RowDefinition Height="1" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
@@ -528,8 +591,9 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Text="{Binding KeysDisplayText}" /> <TextBlock Text="{Binding KeysDisplayText}" />
<TextBlock Grid.Column="1" <TextBlock
Text="{Binding Command, Converter={StaticResource CommandToCommandNameConverter}}" /> Grid.Column="1"
Text="{Binding Command, Converter={StaticResource CommandToCommandNameConverter}}" />
</Grid> </Grid>
</DataTemplate> </DataTemplate>
</ItemsRepeater.ItemTemplate> </ItemsRepeater.ItemTemplate>
@@ -540,15 +604,15 @@
</Grid> </Grid>
<Border <Border
Background="{DynamicResource BarelyTransparentBackgroundColor}"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Background="{DynamicResource BarelyTransparentBackgroundColor}"
IsVisible="{Binding DialogService.ReadInput^, Converter={x:Static ObjectConverters.IsNotNull}}"> IsVisible="{Binding DialogService.ReadInput^, Converter={x:Static ObjectConverters.IsNotNull}}">
<Border <Border
Background="{DynamicResource ContainerBackgroundBrush}"
Padding="20" Padding="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center"> VerticalAlignment="Center"
Background="{DynamicResource ContainerBackgroundBrush}">
<Grid RowDefinitions="Auto,Auto"> <Grid RowDefinitions="Auto,Auto">
<ItemsControl <ItemsControl
@@ -556,22 +620,26 @@
Items="{Binding DialogService.ReadInput^.Inputs}"> Items="{Binding DialogService.ReadInput^.Inputs}">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<Grid MinWidth="500" ColumnDefinitions="250,*" Margin="10,5" x:Name="ItemRoot"> <Grid
x:Name="ItemRoot"
MinWidth="500"
Margin="10,5"
ColumnDefinitions="250,*">
<TextBlock <TextBlock
HorizontalAlignment="Left" HorizontalAlignment="Left"
VerticalAlignment="Top" VerticalAlignment="Top"
Text="{Binding Label}" /> Text="{Binding Label}" />
<Grid Grid.Column="1"> <Grid Grid.Column="1">
<TextBox <TextBox
VerticalAlignment="Top"
x:DataType="interactions:TextInputElement" x:DataType="interactions:TextInputElement"
IsVisible="{Binding Type, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static interactions:InputType.Text}}" IsVisible="{Binding Type, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static interactions:InputType.Text}}"
VerticalAlignment="Top"
Text="{Binding Value, Mode=TwoWay}" /> Text="{Binding Value, Mode=TwoWay}" />
<TextBox <TextBox
VerticalAlignment="Top"
x:DataType="interactions:PasswordInputElement" x:DataType="interactions:PasswordInputElement"
IsVisible="{Binding Type, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static interactions:InputType.Password}}" IsVisible="{Binding Type, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static interactions:InputType.Password}}"
PasswordChar="{Binding PasswordChar}" PasswordChar="{Binding PasswordChar}"
VerticalAlignment="Top"
Text="{Binding Value, Mode=TwoWay}" /> Text="{Binding Value, Mode=TwoWay}" />
<ListBox <ListBox
x:DataType="interactions:IOptionsInputElement" x:DataType="interactions:IOptionsInputElement"
@@ -587,17 +655,17 @@
<StackPanel <StackPanel
DataContext="{Binding DialogService.ReadInput^}" DataContext="{Binding DialogService.ReadInput^}"
Grid.Row="1" Grid.Row="1"
Orientation="Horizontal" Margin="0,10,0,0"
Margin="0,10,0,0"> Orientation="Horizontal">
<Button <Button
HorizontalContentAlignment="Center"
Width="80" Width="80"
HorizontalContentAlignment="Center"
Command="{Binding ProcessCommand}" Command="{Binding ProcessCommand}"
Content="Ok" /> Content="Ok" />
<Button <Button
HorizontalContentAlignment="Center"
Width="80" Width="80"
Margin="10,0,0,0" Margin="10,0,0,0"
HorizontalContentAlignment="Center"
Command="{Binding CancelCommand}" Command="{Binding CancelCommand}"
Content="Cancel" /> Content="Cancel" />
</StackPanel> </StackPanel>
@@ -606,32 +674,32 @@
</Border> </Border>
<Border <Border
Background="{DynamicResource BarelyTransparentBackgroundColor}" DataContext="{Binding DialogService.LastMessageBox^}"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
DataContext="{Binding DialogService.LastMessageBox^}" Background="{DynamicResource BarelyTransparentBackgroundColor}"
IsVisible="{Binding Converter={x:Static ObjectConverters.IsNotNull}, FallbackValue=False}"> IsVisible="{Binding Converter={x:Static ObjectConverters.IsNotNull}, FallbackValue=False}">
<Border <Border
Background="{DynamicResource ContainerBackgroundBrush}" Padding="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center" VerticalAlignment="Center"
Padding="20"> Background="{DynamicResource ContainerBackgroundBrush}">
<Grid RowDefinitions="Auto,Auto"> <Grid RowDefinitions="Auto,Auto">
<TextBlock Text="{Binding Text}" /> <TextBlock Text="{Binding Text}" />
<StackPanel <StackPanel
Grid.Row="1" Grid.Row="1"
Orientation="Horizontal" Margin="0,10,0,0"
Margin="0,10,0,0"> Orientation="Horizontal">
<Button <Button
HorizontalContentAlignment="Center"
Width="80" Width="80"
HorizontalContentAlignment="Center"
Command="{Binding OkCommand}" Command="{Binding OkCommand}"
Content="Yes" /> Content="Yes" />
<Button <Button
HorizontalContentAlignment="Center"
Width="80" Width="80"
Margin="10,0,0,0" Margin="10,0,0,0"
HorizontalContentAlignment="Center"
Command="{Binding CancelCommand}" Command="{Binding CancelCommand}"
Content="No" /> Content="No" />
</StackPanel> </StackPanel>

View File

@@ -232,30 +232,32 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
pointInTime, pointInTime,
exceptions, exceptions,
new ExtensionCollection().AsReadOnly(), new ExtensionCollection().AsReadOnly(),
Observable.FromAsync(async () => await Task.Run(InitChildren)) //Observable.FromAsync(async () => await Task.Run(InitChildrenHelper)
Observable.Return(InitChildren())
); );
Task<IObservable<IChangeSet<AbsolutePath, string>>?> InitChildren() Task<IObservable<IChangeSet<AbsolutePath, string>>?> InitChildrenHelper() => Task.FromResult(InitChildren());
IObservable<IChangeSet<AbsolutePath, string>>? InitChildren()
{ {
if (!initializeChildren) return null;
try try
{ {
var items = initializeChildren ? (List<AbsolutePath>?) GetItemsByContainer(directoryInfo, pointInTime) : null; var items = GetItemsByContainer(directoryInfo, pointInTime);
if (items != null) var result = new SourceCache<AbsolutePath, string>(i => i.Path.Path);
{
var result = new SourceCache<AbsolutePath, string>(i => i.Path.Path);
if (items.Count == 0) return Task.FromResult((IObservable<IChangeSet<AbsolutePath, string>>?) result.Connect().StartWithEmpty()); if (items.Count == 0) return (IObservable<IChangeSet<AbsolutePath, string>>?) result.Connect().StartWithEmpty();
result.AddOrUpdate(items); result.AddOrUpdate(items);
return Task.FromResult((IObservable<IChangeSet<AbsolutePath, string>>?) result.Connect()); return (IObservable<IChangeSet<AbsolutePath, string>>?) result.Connect();
}
} }
catch (Exception e) catch (Exception e)
{ {
exceptions.OnNext(new List<Exception>() {e}); exceptions.OnNext(new List<Exception> {e});
} }
return Task.FromResult((IObservable<IChangeSet<AbsolutePath, string>>?) null); return null;
} }
} }