Use IDeclarativeProperty instead of IObservable
This commit is contained in:
@@ -1,11 +1,13 @@
|
|||||||
|
using DeclarativeProperty;
|
||||||
|
|
||||||
namespace FileTime.App.Core.ViewModels.Timeline;
|
namespace FileTime.App.Core.ViewModels.Timeline;
|
||||||
|
|
||||||
public interface ICommandTimeStateViewModel
|
public interface ICommandTimeStateViewModel
|
||||||
{
|
{
|
||||||
IObservable<int> TotalProgress { get; }
|
IDeclarativeProperty<int> TotalProgress { get; }
|
||||||
IObservable<int> CurrentProgress { get; }
|
IDeclarativeProperty<int> CurrentProgress { get; }
|
||||||
IObservable<string> DisplayLabel { get; }
|
IDeclarativeProperty<string> DisplayLabel { get; }
|
||||||
IObservable<string> DisplayDetailLabel { get; }
|
IDeclarativeProperty<string> DisplayDetailLabel { get; }
|
||||||
IObservable<bool> IsSelected { get; }
|
IDeclarativeProperty<bool> IsSelected { get; }
|
||||||
void Cancel();
|
void Cancel();
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Reactive.Subjects;
|
using System.Reactive.Subjects;
|
||||||
|
using DeclarativeProperty;
|
||||||
using FileTime.Core.Timeline;
|
using FileTime.Core.Timeline;
|
||||||
|
|
||||||
namespace FileTime.App.Core.ViewModels.Timeline;
|
namespace FileTime.App.Core.ViewModels.Timeline;
|
||||||
@@ -6,13 +7,13 @@ namespace FileTime.App.Core.ViewModels.Timeline;
|
|||||||
public class CommandTimeStateViewModel : ICommandTimeStateViewModel
|
public class CommandTimeStateViewModel : ICommandTimeStateViewModel
|
||||||
{
|
{
|
||||||
private readonly CommandTimeState _commandTimeState;
|
private readonly CommandTimeState _commandTimeState;
|
||||||
public IObservable<int> TotalProgress { get; }
|
public IDeclarativeProperty<int> TotalProgress { get; }
|
||||||
public IObservable<int> CurrentProgress { get; }
|
public IDeclarativeProperty<int> CurrentProgress { get; }
|
||||||
|
|
||||||
public IObservable<string> DisplayLabel { get; }
|
public IDeclarativeProperty<string> DisplayLabel { get; }
|
||||||
public IObservable<string> DisplayDetailLabel { get; }
|
public IDeclarativeProperty<string> DisplayDetailLabel { get; }
|
||||||
|
|
||||||
public IObservable<bool> IsSelected { get; }
|
public IDeclarativeProperty<bool> IsSelected { get; }
|
||||||
|
|
||||||
public CommandTimeStateViewModel(CommandTimeState commandTimeState)
|
public CommandTimeStateViewModel(CommandTimeState commandTimeState)
|
||||||
{
|
{
|
||||||
@@ -22,7 +23,7 @@ public class CommandTimeStateViewModel : ICommandTimeStateViewModel
|
|||||||
TotalProgress = commandTimeState.Command.TotalProgress;
|
TotalProgress = commandTimeState.Command.TotalProgress;
|
||||||
CurrentProgress = commandTimeState.Command.CurrentProgress;
|
CurrentProgress = commandTimeState.Command.CurrentProgress;
|
||||||
//TODO
|
//TODO
|
||||||
IsSelected = new BehaviorSubject<bool>(false);
|
IsSelected = new DeclarativeProperty<bool>(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Cancel()
|
public void Cancel()
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
|
using DeclarativeProperty;
|
||||||
using FileTime.Core.Timeline;
|
using FileTime.Core.Timeline;
|
||||||
|
|
||||||
namespace FileTime.Core.Command;
|
namespace FileTime.Core.Command;
|
||||||
|
|
||||||
public interface ICommand
|
public interface ICommand
|
||||||
{
|
{
|
||||||
IObservable<string> DisplayLabel { get; }
|
IDeclarativeProperty<string> DisplayLabel { get; }
|
||||||
IObservable<string> DisplayDetailLabel { get; }
|
IDeclarativeProperty<string> DisplayDetailLabel { get; }
|
||||||
IObservable<int> TotalProgress { get; }
|
IDeclarativeProperty<int> TotalProgress { get; }
|
||||||
IObservable<int> CurrentProgress { get; }
|
IDeclarativeProperty<int> CurrentProgress { get; }
|
||||||
|
|
||||||
Task<CanCommandRun> CanRun(PointInTime currentTime);
|
Task<CanCommandRun> CanRun(PointInTime currentTime);
|
||||||
Task<PointInTime> SimulateCommand(PointInTime currentTime);
|
Task<PointInTime> SimulateCommand(PointInTime currentTime);
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using System.Reactive.Subjects;
|
using System.Reactive.Subjects;
|
||||||
|
using DeclarativeProperty;
|
||||||
using FileTime.Core.Timeline;
|
using FileTime.Core.Timeline;
|
||||||
|
|
||||||
namespace FileTime.Core.Command;
|
namespace FileTime.Core.Command;
|
||||||
|
|
||||||
public abstract class CommandBase : ICommand
|
public abstract class CommandBase : ICommand
|
||||||
{
|
{
|
||||||
private readonly BehaviorSubject<string> _displayLabel;
|
private readonly DeclarativeProperty<string> _displayLabel;
|
||||||
private readonly BehaviorSubject<string> _displayDetailLabel;
|
private readonly DeclarativeProperty<string> _displayDetailLabel;
|
||||||
private readonly BehaviorSubject<int> _totalProgress;
|
private readonly DeclarativeProperty<int> _totalProgress;
|
||||||
private readonly BehaviorSubject<int> _currentProgress;
|
private readonly DeclarativeProperty<int> _currentProgress;
|
||||||
|
|
||||||
public IObservable<string> DisplayLabel { get; }
|
public IDeclarativeProperty<string> DisplayLabel { get; }
|
||||||
public IObservable<string> DisplayDetailLabel { get; }
|
public IDeclarativeProperty<string> DisplayDetailLabel { get; }
|
||||||
public IObservable<int> TotalProgress { get; }
|
public IDeclarativeProperty<int> TotalProgress { get; }
|
||||||
public IObservable<int> CurrentProgress { get; }
|
public IDeclarativeProperty<int> CurrentProgress { get; }
|
||||||
|
|
||||||
protected CommandBase(string displayLabel = "", string displayDetailLabel = "", int totalProgress = 0, int currentProgress = 0)
|
protected CommandBase(string displayLabel = "", string displayDetailLabel = "", int totalProgress = 0, int currentProgress = 0)
|
||||||
{
|
{
|
||||||
@@ -23,32 +24,32 @@ public abstract class CommandBase : ICommand
|
|||||||
_totalProgress = new(totalProgress);
|
_totalProgress = new(totalProgress);
|
||||||
_currentProgress = new(currentProgress);
|
_currentProgress = new(currentProgress);
|
||||||
|
|
||||||
DisplayLabel = _displayLabel.AsObservable();
|
DisplayLabel = _displayLabel;
|
||||||
DisplayDetailLabel = _displayDetailLabel.AsObservable();
|
DisplayDetailLabel = _displayDetailLabel;
|
||||||
TotalProgress = _totalProgress.AsObservable();
|
TotalProgress = _totalProgress;
|
||||||
CurrentProgress = _currentProgress.AsObservable();
|
CurrentProgress = _currentProgress;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Task<CanCommandRun> CanRun(PointInTime currentTime);
|
public abstract Task<CanCommandRun> CanRun(PointInTime currentTime);
|
||||||
public abstract Task<PointInTime> SimulateCommand(PointInTime currentTime);
|
public abstract Task<PointInTime> SimulateCommand(PointInTime currentTime);
|
||||||
public abstract void Cancel();
|
public abstract void Cancel();
|
||||||
|
|
||||||
protected void SetDisplayLabel(string? displayLabel) => _displayLabel.OnNext(displayLabel ?? string.Empty);
|
protected async Task SetDisplayLabelAsync(string? displayLabel) => await _displayLabel.SetValue(displayLabel ?? string.Empty);
|
||||||
protected void SetDisplayDetailLabel(string? displayLabel) => _displayDetailLabel.OnNext(displayLabel ?? string.Empty);
|
protected async Task SetDisplayDetailLabel(string? displayLabel) => await _displayDetailLabel.SetValue(displayLabel ?? string.Empty);
|
||||||
|
|
||||||
protected void SetTotalProgress(int totalProgress) => _totalProgress.OnNext(totalProgress);
|
protected async Task SetTotalProgress(int totalProgress) => await _totalProgress.SetValue(totalProgress);
|
||||||
|
|
||||||
protected void SetCurrentProgress(int currentProgress) => _currentProgress.OnNext(currentProgress);
|
protected async Task SetCurrentProgress(int currentProgress) => await _currentProgress.SetValue(currentProgress);
|
||||||
|
|
||||||
protected IDisposable TrackProgress(IEnumerable<OperationProgress> operationProgresses) =>
|
protected IDisposable TrackProgress(IEnumerable<OperationProgress> operationProgresses) =>
|
||||||
operationProgresses
|
operationProgresses
|
||||||
.Select(op => op.Progress.Select(p => (Progress: p, TotalProgress: op.TotalCount)))
|
.Select(op => op.Progress.Map(p => (Progress: p, TotalProgress: op.TotalCount)))
|
||||||
.CombineLatest()
|
.CombineAll(data =>
|
||||||
.Select(data =>
|
|
||||||
{
|
{
|
||||||
var total = data.Sum(d => d.TotalProgress);
|
var dataList = data.ToList();
|
||||||
if (total == 0) return 0;
|
var total = dataList.Sum(d => d.TotalProgress);
|
||||||
return (int) (data.Sum(d => d.Progress) * 100 / total);
|
if (total == 0) return Task.FromResult(0);
|
||||||
|
return Task.FromResult((int)(dataList.Sum(d => d.Progress) * 100 / total));
|
||||||
})
|
})
|
||||||
.Subscribe(SetTotalProgress);
|
.Subscribe(async (p, _) => await SetTotalProgress(p));
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@ public class CopyCommand : CommandBase, ITransportationCommand
|
|||||||
private readonly CancellationTokenSource _cancellationTokenSource = new();
|
private readonly CancellationTokenSource _cancellationTokenSource = new();
|
||||||
|
|
||||||
private readonly List<OperationProgress> _operationProgresses = new();
|
private readonly List<OperationProgress> _operationProgresses = new();
|
||||||
private readonly BehaviorSubject<OperationProgress?> _currentOperationProgress = new(null);
|
private readonly DeclarativeProperty<OperationProgress?> _currentOperationProgress = new();
|
||||||
|
|
||||||
private long _recentTotalSum;
|
private long _recentTotalSum;
|
||||||
private readonly DeclarativeProperty<long> _recentTotalProcessed = new();
|
private readonly DeclarativeProperty<long> _recentTotalProcessed = new();
|
||||||
@@ -41,22 +41,21 @@ public class CopyCommand : CommandBase, ITransportationCommand
|
|||||||
ArgumentNullException.ThrowIfNull(sources);
|
ArgumentNullException.ThrowIfNull(sources);
|
||||||
ArgumentNullException.ThrowIfNull(mode);
|
ArgumentNullException.ThrowIfNull(mode);
|
||||||
ArgumentNullException.ThrowIfNull(targetFullName);
|
ArgumentNullException.ThrowIfNull(targetFullName);
|
||||||
|
|
||||||
_timelessContentProvider = timelessContentProvider;
|
_timelessContentProvider = timelessContentProvider;
|
||||||
_commandSchedulerNotifier = commandSchedulerNotifier;
|
_commandSchedulerNotifier = commandSchedulerNotifier;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_currentOperationProgress
|
_currentOperationProgress
|
||||||
.Select(p =>
|
.Map(p =>
|
||||||
{
|
{
|
||||||
if (p is null) return Observable.Never<int>();
|
return p?.Progress.Map(currentProgress =>
|
||||||
return p.Progress.Select(currentProgress =>
|
|
||||||
p.TotalCount == 0
|
p.TotalCount == 0
|
||||||
? 0
|
? 0
|
||||||
: (int) (currentProgress * 100 / p.TotalCount)
|
: (int) (currentProgress * 100 / p.TotalCount)
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.Switch()
|
.Switch()
|
||||||
.Subscribe(SetCurrentProgress);
|
.Subscribe(async (p, _) => await SetCurrentProgress(p));
|
||||||
|
|
||||||
Sources = new List<FullName>(sources).AsReadOnly();
|
Sources = new List<FullName>(sources).AsReadOnly();
|
||||||
TransportMode = mode;
|
TransportMode = mode;
|
||||||
@@ -75,7 +74,7 @@ public class CopyCommand : CommandBase, ITransportationCommand
|
|||||||
|
|
||||||
recentSpeed
|
recentSpeed
|
||||||
.Debounce(TimeSpan.FromMilliseconds(500))
|
.Debounce(TimeSpan.FromMilliseconds(500))
|
||||||
.Subscribe(SetDisplayDetailLabel);
|
.Subscribe(async (l, _) => await SetDisplayDetailLabel(l));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task<CanCommandRun> CanRun(PointInTime currentTime)
|
public override Task<CanCommandRun> CanRun(PointInTime currentTime)
|
||||||
@@ -145,20 +144,21 @@ public class CopyCommand : CommandBase, ITransportationCommand
|
|||||||
|
|
||||||
if (Sources.Count == 1)
|
if (Sources.Count == 1)
|
||||||
{
|
{
|
||||||
SetDisplayLabel($"Copy - {Sources[0].GetName()}");
|
await SetDisplayLabelAsync($"Copy - {Sources[0].GetName()}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_operationProgresses
|
_operationProgresses
|
||||||
.Select(o => o.IsDone)
|
.Select(o => o.IsDone)
|
||||||
.CombineLatest()
|
.CombineAll(statuses =>
|
||||||
.Subscribe(statuses =>
|
|
||||||
{
|
{
|
||||||
var done = statuses.Count(s => s) + 1;
|
var statusList = statuses.ToList();
|
||||||
if (done > statuses.Count) done = statuses.Count;
|
var done = statusList.Count(s => s) + 1;
|
||||||
|
if (done > statusList.Count) done = statusList.Count;
|
||||||
|
|
||||||
SetDisplayLabel($"Copy - {done} / {statuses.Count}");
|
return Task.FromResult($"Copy - {done} / {statusList.Count}");
|
||||||
});
|
})
|
||||||
|
.Subscribe(async (v, _) => await SetDisplayLabelAsync(v));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@ public class CopyCommand : CommandBase, ITransportationCommand
|
|||||||
var newElementPath = target.GetChild(newElementName, AbsolutePathType.Element);
|
var newElementPath = target.GetChild(newElementName, AbsolutePathType.Element);
|
||||||
|
|
||||||
var currentProgress = _operationProgresses.Find(o => o.Key == element.FullName!.Path);
|
var currentProgress = _operationProgresses.Find(o => o.Key == element.FullName!.Path);
|
||||||
_currentOperationProgress.OnNext(currentProgress);
|
await _currentOperationProgress.SetValue(currentProgress);
|
||||||
|
|
||||||
await copyOperation.CopyAsync(new AbsolutePath(_timelessContentProvider, element), newElementPath, new CopyCommandContext(UpdateProgress, currentProgress, _cancellationTokenSource.Token));
|
await copyOperation.CopyAsync(new AbsolutePath(_timelessContentProvider, element), newElementPath, new CopyCommandContext(UpdateProgress, currentProgress, _cancellationTokenSource.Token));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
x:DataType="vm:IMainWindowViewModelBase"
|
x:DataType="vm:IMainWindowViewModelBase"
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:appCoreModels="using:FileTime.App.Core.Models"
|
xmlns:appCoreModels="using:FileTime.App.Core.Models"
|
||||||
xmlns:appInteractions="using:FileTime.App.Core.Interactions"
|
|
||||||
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:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity"
|
xmlns:i="clr-namespace:Avalonia.Xaml.Interactivity;assembly=Avalonia.Xaml.Interactivity"
|
||||||
@@ -346,7 +345,7 @@
|
|||||||
VerticalAlignment="Top">
|
VerticalAlignment="Top">
|
||||||
<Grid ColumnDefinitions="*, *" RowDefinitions="Auto, Auto">
|
<Grid ColumnDefinitions="*, *" RowDefinitions="Auto, Auto">
|
||||||
<Grid ColumnDefinitions="*, Auto" Grid.ColumnSpan="2">
|
<Grid ColumnDefinitions="*, Auto" Grid.ColumnSpan="2">
|
||||||
<TextBlock Text="{Binding DisplayLabel^}" />
|
<TextBlock Text="{Binding DisplayLabel.Value}" />
|
||||||
|
|
||||||
<Button Command="{Binding Cancel}" Grid.Column="1">
|
<Button Command="{Binding Cancel}" Grid.Column="1">
|
||||||
<Image
|
<Image
|
||||||
@@ -362,25 +361,25 @@
|
|||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Margin="0,5,5,5">
|
Margin="0,5,5,5">
|
||||||
<TextBlock Text="{Binding DisplayDetailLabel^}" TextAlignment="Right" />
|
<TextBlock Text="{Binding DisplayDetailLabel.Value}" TextAlignment="Right" />
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
Margin="0,5,0,0"
|
Margin="0,5,0,0"
|
||||||
Maximum="100"
|
Maximum="100"
|
||||||
MinWidth="50"
|
MinWidth="50"
|
||||||
Value="{Binding CurrentProgress^}" />
|
Value="{Binding CurrentProgress.Value}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Margin="5,5,0,5">
|
Margin="5,5,0,5">
|
||||||
<TextBlock Text="{Binding TotalProgress^, StringFormat={}{0}%}" TextAlignment="Right" />
|
<TextBlock Text="{Binding TotalProgress.Value, StringFormat={}{0}%}" TextAlignment="Right" />
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
Margin="0,5,0,0"
|
Margin="0,5,0,0"
|
||||||
Maximum="100"
|
Maximum="100"
|
||||||
MinWidth="50"
|
MinWidth="50"
|
||||||
Value="{Binding TotalProgress^}" />
|
Value="{Binding TotalProgress.Value}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|||||||
36
src/Library/DeclarativeProperty/CombineAllProperty.cs
Normal file
36
src/Library/DeclarativeProperty/CombineAllProperty.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
namespace DeclarativeProperty;
|
||||||
|
|
||||||
|
public class CombineAllProperty<T, TResult> : DeclarativePropertyBase<TResult>
|
||||||
|
{
|
||||||
|
private readonly List<IDeclarativeProperty<T>> _sources;
|
||||||
|
private readonly Func<IEnumerable<T>, Task<TResult?>> _combiner;
|
||||||
|
|
||||||
|
public CombineAllProperty(
|
||||||
|
IEnumerable<IDeclarativeProperty<T>> sources,
|
||||||
|
Func<IEnumerable<T>, Task<TResult?>> combiner,
|
||||||
|
Action<TResult?>? setValueHook = null) : base(setValueHook)
|
||||||
|
{
|
||||||
|
var sourcesList = sources.ToList();
|
||||||
|
_sources = sourcesList;
|
||||||
|
_combiner = combiner;
|
||||||
|
|
||||||
|
var initialValueTask = _combiner(sourcesList.Select(p => p.Value));
|
||||||
|
initialValueTask.Wait();
|
||||||
|
SetNewValueSync(initialValueTask.Result);
|
||||||
|
|
||||||
|
foreach (var declarativeProperty in sourcesList)
|
||||||
|
{
|
||||||
|
AddDisposable(declarativeProperty.Subscribe(OnSourceChanged));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnSourceChanged(T? arg1, CancellationToken arg2) => await Update();
|
||||||
|
|
||||||
|
private async Task Update()
|
||||||
|
{
|
||||||
|
var values = _sources.Select(p => p.Value);
|
||||||
|
var result = await _combiner(values);
|
||||||
|
|
||||||
|
await SetNewValueAsync(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -93,4 +93,10 @@ public static class DeclarativePropertyExtensions
|
|||||||
|
|
||||||
public static IDeclarativeProperty<T> Switch<T>(this IDeclarativeProperty<IDeclarativeProperty<T>?> from)
|
public static IDeclarativeProperty<T> Switch<T>(this IDeclarativeProperty<IDeclarativeProperty<T>?> from)
|
||||||
=> new SwitchProperty<T>(from);
|
=> new SwitchProperty<T>(from);
|
||||||
|
|
||||||
|
public static IDeclarativeProperty<TResult?> CombineAll<T, TResult>(
|
||||||
|
this IEnumerable<IDeclarativeProperty<T>> sources,
|
||||||
|
Func<IEnumerable<T>, Task<TResult>> combiner,
|
||||||
|
Action<TResult?>? setValueHook = null)
|
||||||
|
=> new CombineAllProperty<T,TResult?>(sources, combiner, setValueHook);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user