public MetricsViewModel(ISourcesCacheProvider cacheProvider) { _model = new MetricsModel { Caption = "Metrics", Cache = cacheProvider.CurrentCache }; Disposables.Add(_model); _model.WhenAnyValue(x => x.Caption, x => x.Online) .Subscribe(v => Title = v.Item1 + (v.Item2 ? " >" : " ||")); var dynamicFilter = _model.SelectedSourcesChanged .Select(_ => Filters.CreateFilterMetricBySources(_model)); var observable = _model.Cache.Metrics .Connect() .Filter(dynamicFilter) .Publish(); _metricsCache = observable .AsObservableCache() .DisposeWith(Disposables); observable .Transform(x => new MetricValueItem { Metric = x }) .Sort(SortExpressionComparer <MetricValueItem> .Ascending(x => x.Metric.Instance.Source.Name) .ThenByAscending(x => x.Metric.Instance.Name) .ThenByAscending(x => x.Metric.Name)) .ObserveOnDispatcher() .Bind(out var metricValuesStatic) .Subscribe() .DisposeWith(Disposables); observable .Connect() .DisposeWith(Disposables); var metricValuesDynamicSource = new SourceList <MetricValueItem>() .DisposeWith(Disposables); metricValuesDynamicSource .Connect() .ObserveOnDispatcher() .Bind(out var metricValuesDynamic) .Subscribe() .DisposeWith(Disposables); _model.WhenAnyValue(x => x.MetricDiagramVisible) .Subscribe(x => MetricValuesList = x ? metricValuesStatic : metricValuesDynamic); _model.SelectedSourcesChanged.Subscribe(_ => UpdateSelectedMetrics()); UpdateSelectedMetrics(); var canStart = _model.WhenAny(x => x.Online, x => !x.Value); StartCommand = ReactiveCommand.Create(OnStart, canStart); var canStop = _model.WhenAny(x => x.Online, x => x.Value); StopCommand = ReactiveCommand.Create(OnStop, canStop); UpdateCommand = ReactiveCommand.Create(OnUpdate, canStop); var mapper = Mappers.Xy <MetricValueItem>() .X(item => (double)item.Interval.Ticks / TimeSpan.FromMinutes(5).Ticks) .Y(item => item.Value); //lets save the mapper globally. Charting.For <MetricValueItem>(mapper); SeriesCollection = new ChartValues <MetricValueItem>(); //lets set how to display the X Labels DateTimeFormatter = value => new DateTime((long)value * TimeSpan.FromMinutes(5).Ticks).ToString("t"); UpdateCommand.Subscribe(results => { if (_model.MetricDiagramVisible) { SeriesCollection.Clear(); if (results != null) { SeriesCollection.AddRange(results); } } else { metricValuesDynamicSource.Edit(innerList => { innerList.Clear(); innerList.AddRange(results); }); } }); }
public NuGetPackageListViewModel(INavigationService navigationService, INuGetPackageService nugetPackageService) { _navigationService = navigationService; _nugetPackageService = nugetPackageService; PackageDetails = ReactiveCommand.CreateFromTask <NuGetPackageViewModel>(ExecutePackageDetails); Refresh = ReactiveCommand.CreateFromTask(ExecuteRefresh); Refresh.ThrownExceptions.Subscribe(exception => this.Log().Warn(exception)); PackageDetails.ThrownExceptions.Subscribe(exception => this.Log().Warn(exception)); var nugetChangeSet = _nugetCache .Connect() .RefCount(); var sortChanged = this.WhenAnyValue(x => x.SelectedTag) .Where(x => !string.IsNullOrEmpty(x)) .Select(selectedTag => SortExpressionComparer <NuGetPackageViewModel> .Ascending(x => x.PackageMetadata.DownloadCount) .ThenByAscending(x => x.PackageMetadata.Tags.Contains(selectedTag))); nugetChangeSet .Transform(x => new NuGetPackageViewModel(x)) .Sort(sortChanged) .Bind(out _searchResults) .DisposeMany() .Subscribe() .DisposeWith(Disposal); _nugetCache .CountChanged .Select(count => count > 0) .ToProperty(this, x => x.HasItems, out _hasItems) .DisposeWith(Disposal); nugetChangeSet .Transform(x => x.Tags.Split(',')) .RemoveKey() .TransformMany(x => x) .DistinctValues(x => x) .Bind(out _tags) .Subscribe() .DisposeWith(Disposal); Search = ReactiveCommand.CreateFromObservable <string, IEnumerable <IPackageSearchMetadata> >(SearchNuGetPackages); Search .Select(packageSearchMetadata => packageSearchMetadata) .Subscribe(packages => _nugetCache.EditDiff(packages, (first, second) => first.Identity.Id == second.Identity.Id)) .DisposeWith(Disposal); this.WhenAnyValue(x => x.SearchText) .Where(x => !string.IsNullOrEmpty(x)) .Throttle(TimeSpan.FromMilliseconds(750), RxApp.TaskpoolScheduler) .Select(term => term.Trim()) .DistinctUntilChanged() .InvokeCommand(this, x => x.Search) .DisposeWith(Disposal); this.WhenAnyValue(x => x.SearchText) .Where(string.IsNullOrEmpty) .Subscribe(_ => _nugetCache.Clear()) .DisposeWith(Disposal); this.WhenAnyObservable(x => x.Refresh.IsExecuting) .StartWith(false) .DistinctUntilChanged() .ToProperty(this, x => x.IsRefreshing, out _isRefreshing) .DisposeWith(Disposal); }
public TailViewModel([NotNull] ILogger logger, [NotNull] ISchedulerProvider schedulerProvider, [NotNull] IEnumerable <IFileWatcher> fileWatcher, [NotNull] ISelectionMonitor selectionMonitor, [NotNull] IClipboardHandler clipboardHandler, [NotNull] ISearchInfoCollection searchInfoCollection, [NotNull] IInlineViewerFactory inlineViewerFactory, [NotNull] ISetting <GeneralOptions> generalOptions, [NotNull] ISearchMetadataCollection searchMetadataCollection, [NotNull] SearchOptionsViewModel searchOptionsViewModel, [NotNull] SearchHints searchHints) { if (logger == null) { throw new ArgumentNullException(nameof(logger)); } if (schedulerProvider == null) { throw new ArgumentNullException(nameof(schedulerProvider)); } if (fileWatcher == null) { throw new ArgumentNullException(nameof(fileWatcher)); } if (selectionMonitor == null) { throw new ArgumentNullException(nameof(selectionMonitor)); } if (clipboardHandler == null) { throw new ArgumentNullException(nameof(clipboardHandler)); } if (searchInfoCollection == null) { throw new ArgumentNullException(nameof(searchInfoCollection)); } if (inlineViewerFactory == null) { throw new ArgumentNullException(nameof(inlineViewerFactory)); } if (generalOptions == null) { throw new ArgumentNullException(nameof(generalOptions)); } if (searchMetadataCollection == null) { throw new ArgumentNullException(nameof(searchMetadataCollection)); } if (searchOptionsViewModel == null) { throw new ArgumentNullException(nameof(searchOptionsViewModel)); } if (searchHints == null) { throw new ArgumentNullException(nameof(searchHints)); } _stateProvider = new TailViewPersister(this); var enumerable = fileWatcher as IFileWatcher[] ?? fileWatcher.ToArray(); Names = enumerable.Select(t => t.FullName); SelectionMonitor = selectionMonitor; SearchOptions = searchOptionsViewModel; SearchHints = searchHints; SearchCollection = new SearchCollection(searchInfoCollection, schedulerProvider); CopyToClipboardCommand = new Command(() => clipboardHandler.WriteToClipboard(selectionMonitor.GetSelectedText())); OpenFileCommand = new Command(() => Process.Start(enumerable[0].FullName)); OpenFolderCommand = new Command(() => Process.Start(enumerable[0].Folder)); UsingDarkTheme = generalOptions.Value .ObserveOn(schedulerProvider.MainThread) .Select(options => options.Theme == Theme.Dark) .ForBinding(); HighlightTail = generalOptions.Value .ObserveOn(schedulerProvider.MainThread) .Select(options => options.HighlightTail) .ForBinding(); HighlightDuration = generalOptions.Value .ObserveOn(schedulerProvider.MainThread) .Select(options => new Duration(TimeSpan.FromSeconds(options.HighlightDuration))) .ForBinding(); //An observable which acts as a scroll command var autoChanged = this.WhenValueChanged(vm => vm.AutoTail); var scroller = _userScrollRequested.CombineLatest(autoChanged, (user, auto) => { var mode = AutoTail ? ScrollReason.Tail : ScrollReason.User; return(new ScrollRequest(mode, user.PageSize, user.FirstIndex)); }) .Do(x => logger.Info("Scrolling to {0}/{1}", x.FirstIndex, x.PageSize)) .DistinctUntilChanged(); FileStatus = enumerable .Select(t => t.Status) .Merge() .Scan(default(FileStatus), (status, fileStatus) => status | fileStatus) .ForBinding(); //command to add the current search to the tail collection var searchInvoker = SearchHints.SearchRequested.Subscribe( request => { searchInfoCollection.Add(request.Text, request.UseRegEx); }); //User feedback to show file size FileSizeText = enumerable .Select(t => t.Latest) .Merge() .Select(t => t.Size) .Scan(0f, (previousSize, currentSize) => previousSize + currentSize / 2f) .Select(t => ((long)t).FormatWithAbbreviation()) .DistinctUntilChanged() .ForBinding(); //tailer is the main object used to tail, scroll and filter in a file var lineScroller = new LineScroller(SearchCollection.Latest.ObserveOn(schedulerProvider.Background), scroller); //load lines into observable collection var lineProxyFactory = new LineProxyFactory(new TextFormatter(searchMetadataCollection), new LineMatches(searchMetadataCollection)); var loader = lineScroller.Lines.Connect() .RecordChanges(logger, "Received") .Transform(lineProxyFactory.Create, new ParallelisationOptions(ParallelType.Ordered, 3)) .Sort(SortExpressionComparer <LineProxy> .Ascending(proxy => proxy)) .ObserveOn(schedulerProvider.MainThread) .Bind(out _data, 100) .DisposeMany() .LogErrors(logger) .Subscribe(); //monitor matching lines and start index, Count = searchInfoCollection.All .GroupBy(t => t) .Select(groupedProvider => groupedProvider.Key.Count) .Scan(0, (i, providerCount) => i + providerCount) .ForBinding(); CountText = searchInfoCollection.All .GroupBy(t => t) .Select(groupedProvider => groupedProvider.Key.Count) .Scan(0, (i, providerCount) => i + providerCount) .Select(latestCount => $"{latestCount.ToString("##,###")} lines") .ForBinding(); //iterate over every items to evaluate the lines' count LatestCount = SearchCollection.Latest .GroupBy(t => t) .Do(Console.WriteLine) .Scan(0, (acc, provider) => { if (provider.Key is IndexCollection && provider.Key.NumberOfPreviousProvider == 0) { acc = 0; } else if (provider.Key is FileSearchResult && provider.Key.NumberOfPreviousProvider == 0) { acc = 0; } return(provider.Key.Count + acc); }) .ForBinding(); ////track first visible index var firstIndexMonitor = lineScroller.Lines.Connect() .Buffer(TimeSpan.FromMilliseconds(25)).FlattenBufferResult() .ToCollection() .Select(lines => lines.Count == 0 ? 0 : lines.Select(l => l.Index).Max() - lines.Count + 1) .ObserveOn(schedulerProvider.MainThread) .Subscribe(first => { FirstIndex = first; }); //Create objects required for inline viewing var isUserDefinedChanged = SearchCollection.WhenValueChanged(sc => sc.Selected) .Where(selected => selected != null) .Select(selected => selected.IsUserDefined) .DistinctUntilChanged() .Replay(1) .RefCount(); var inlineViewerVisible = isUserDefinedChanged.CombineLatest(this.WhenValueChanged(vm => vm.ShowInline), (userDefined, showInline) => userDefined && showInline); CanViewInline = isUserDefinedChanged.ForBinding(); InlineViewerVisible = inlineViewerVisible.ForBinding(); //return an empty line provider unless user is viewing inline - this saves needless trips to the file var inline = searchInfoCollection.All.CombineLatest(inlineViewerVisible, (index, ud) => ud ? index : new EmptyLineProvider()); var firstVisibleRow = _data.ToObservableChangeSet().ToCollection() .Select(collection => collection.FirstOrDefault()); //var itemToSelect = this.WhenValueChanged(vm => vm.SelectedItem) // .CombineLatest(firstVisibleRow, (selected, first) => selected ?? first); //// InlineViewer = inlineViewerFactory.Create(inline, this.WhenValueChanged(vm => vm.SelectedItem), lineProxyFactory); _cleanUp = new CompositeDisposable(lineScroller, loader, firstIndexMonitor, FileStatus, Count, LatestCount, FileSizeText, CanViewInline, InlineViewer, InlineViewerVisible, SearchCollection, searchInfoCollection, HighlightTail, UsingDarkTheme, searchHints, searchMetadataCollection, searchMetadataCollection, SelectionMonitor, SearchOptions, searchInvoker, _userScrollRequested.SetAsComplete()); }
public IObservable <IComparer <T> > GetObservable() => this.WhenAnyValue(x => x.IsDescending) .StartWith(IsDescending) .Select(isDescending => isDescending ? SortExpressionComparer <T> .Descending(Comparer) : SortExpressionComparer <T> .Ascending(Comparer));
public TailViewModel([NotNull] ILogger logger, [NotNull] ISchedulerProvider schedulerProvider, [NotNull] IFileWatcher fileWatcher, [NotNull] ISelectionMonitor selectionMonitor, [NotNull] IClipboardHandler clipboardHandler, [NotNull] ISearchInfoCollection searchInfoCollection, [NotNull] IInlineViewerFactory inlineViewerFactory, [NotNull] GeneralOptionBindings generalOptionBindings, [NotNull] ICombinedSearchMetadataCollection combinedSearchMetadataCollection, [NotNull] IStateBucketService stateBucketService, [NotNull] ITailViewStateRestorer restorer, [NotNull] SearchHints searchHints, [NotNull] ITailViewStateControllerFactory tailViewStateControllerFactory, [NotNull] IThemeProvider themeProvider, [NotNull] SearchCollection searchCollection, [NotNull] ITextFormatter textFormatter, [NotNull] ILineMatches lineMatches, [NotNull] IObjectProvider objectProvider, [NotNull] IDialogCoordinator dialogCoordinator) { if (logger == null) { throw new ArgumentNullException(nameof(logger)); } if (schedulerProvider == null) { throw new ArgumentNullException(nameof(schedulerProvider)); } if (fileWatcher == null) { throw new ArgumentNullException(nameof(fileWatcher)); } if (clipboardHandler == null) { throw new ArgumentNullException(nameof(clipboardHandler)); } if (searchInfoCollection == null) { throw new ArgumentNullException(nameof(searchInfoCollection)); } if (inlineViewerFactory == null) { throw new ArgumentNullException(nameof(inlineViewerFactory)); } if (stateBucketService == null) { throw new ArgumentNullException(nameof(stateBucketService)); } if (themeProvider == null) { throw new ArgumentNullException(nameof(themeProvider)); } if (textFormatter == null) { throw new ArgumentNullException(nameof(textFormatter)); } if (lineMatches == null) { throw new ArgumentNullException(nameof(lineMatches)); } if (objectProvider == null) { throw new ArgumentNullException(nameof(objectProvider)); } if (dialogCoordinator == null) { throw new ArgumentNullException(nameof(dialogCoordinator)); } if (combinedSearchMetadataCollection == null) { throw new ArgumentNullException(nameof(combinedSearchMetadataCollection)); } Name = fileWatcher.FullName; SelectionMonitor = selectionMonitor ?? throw new ArgumentNullException(nameof(selectionMonitor)); GeneralOptionBindings = generalOptionBindings; SearchHints = searchHints ?? throw new ArgumentNullException(nameof(searchHints)); CopyToClipboardCommand = new Command(() => clipboardHandler.WriteToClipboard(selectionMonitor.GetSelectedText())); OpenFileCommand = new Command(() => Process.Start(fileWatcher.FullName)); OpenFolderCommand = new Command(() => Process.Start(fileWatcher.Folder)); CopyPathToClipboardCommand = new Command(() => clipboardHandler.WriteToClipboard(fileWatcher.FullName)); UnClearCommand = new Command(fileWatcher.Reset); ClearCommand = new Command(fileWatcher.Clear); KeyAutoTail = new Command(() => { AutoTail = true; }); OpenSearchOptionsCommand = new Command(async() => { await Task.Run(() => { var content = objectProvider.Get <SearchOptionsViewModel>(new Argument <ICombinedSearchMetadataCollection>(combinedSearchMetadataCollection)); dialogCoordinator.Show(this, content, x => content.Dispose()); }); }); var closeOnDeselect = this.WhenValueChanged(vm => vm.IsSelected, false) .Where(selected => !selected) .Subscribe(_ => dialogCoordinator.Close()); SearchCollection = searchCollection ?? throw new ArgumentNullException(nameof(searchCollection)); SearchMetadataCollection = combinedSearchMetadataCollection.Local; var horizonalScrollArgs = new ReplaySubject <TextScrollInfo>(1); HorizonalScrollChanged = args => horizonalScrollArgs.OnNext(args); _tailViewStateControllerFactory = tailViewStateControllerFactory; //this deals with state when loading the system at start up and at shut-down _persister = new TailViewPersister(this, restorer); FileStatus = fileWatcher.Status.ForBinding(); //command to add the current search to the tail collection var searchInvoker = SearchHints.SearchRequested.Subscribe(request => searchInfoCollection.Add(request.Text, request.UseRegEx)); //An observable which acts as a scroll command var autoChanged = this.WhenValueChanged(vm => vm.AutoTail); var scroller = _userScrollRequested.CombineLatest(autoChanged, (user, auto) => { var mode = AutoTail ? ScrollReason.Tail : ScrollReason.User; return(new ScrollRequest(mode, user.PageSize, user.FirstIndex)); }) .Do(x => logger.Info("Scrolling to {0}/{1}", x.FirstIndex, x.PageSize)) .DistinctUntilChanged(); //User feedback to show file size FileSizeText = fileWatcher.Latest.Select(fn => fn.Size) .Select(size => size.FormatWithAbbreviation()) .DistinctUntilChanged() .ForBinding(); //tailer is the main object used to tail, scroll and filter in a file var selectedProvider = SearchCollection.Latest.ObserveOn(schedulerProvider.Background); var lineScroller = new LineScroller(selectedProvider, scroller); MaximumChars = lineScroller.MaximumLines() .ObserveOn(schedulerProvider.MainThread) .ForBinding(); var lineProxyFactory = new LineProxyFactory(textFormatter, lineMatches, horizonalScrollArgs.DistinctUntilChanged(), themeProvider); var loader = lineScroller.Lines.Connect() .LogChanges(logger, "Received") .Transform(lineProxyFactory.Create) .LogChanges(logger, "Sorting") .Sort(SortExpressionComparer <LineProxy> .Ascending(proxy => proxy)) .ObserveOn(schedulerProvider.MainThread) .Bind(out _data, 100) .LogChanges(logger, "Bound") .DisposeMany() .LogErrors(logger) .Subscribe(); //monitor matching lines and start index, Count = searchInfoCollection.All.Select(latest => latest.Count).ForBinding(); CountText = searchInfoCollection.All.Select(latest => $"{latest.Count:##,###} lines").ForBinding(); LatestCount = SearchCollection.Latest.Select(latest => latest.Count).ForBinding(); ////track first visible index var firstIndexMonitor = lineScroller.Lines.Connect() .Buffer(TimeSpan.FromMilliseconds(25)).FlattenBufferResult() .ToCollection() .Select(lines => lines.Count == 0 ? 0 : lines.Select(l => l.Index).Max() - lines.Count + 1) .ObserveOn(schedulerProvider.MainThread) .Subscribe(first => { FirstIndex = first; }); //Create objects required for inline viewing var isUserDefinedChanged = SearchCollection.WhenValueChanged(sc => sc.Selected) .Where(selected => selected != null) .Select(selected => selected.IsUserDefined) .DistinctUntilChanged() .Replay(1) .RefCount(); var showInline = this.WhenValueChanged(vm => vm.ShowInline); var inlineViewerVisible = isUserDefinedChanged.CombineLatest(showInline, (userDefined, showInlne) => userDefined && showInlne); CanViewInline = isUserDefinedChanged.ForBinding(); InlineViewerVisible = inlineViewerVisible.ForBinding(); //return an empty line provider unless user is viewing inline - this saves needless trips to the file var inline = searchInfoCollection.All.CombineLatest(inlineViewerVisible, (index, ud) => ud ? index : EmptyLineProvider.Instance); InlineViewer = inlineViewerFactory.Create(combinedSearchMetadataCollection, inline, this.WhenValueChanged(vm => vm.SelectedItem)); _cleanUp = new CompositeDisposable(lineScroller, loader, firstIndexMonitor, FileStatus, Count, CountText, LatestCount, FileSizeText, CanViewInline, InlineViewer, InlineViewerVisible, SearchCollection, searchInfoCollection, searchHints, SelectionMonitor, closeOnDeselect, Disposable.Create(dialogCoordinator.Close), searchInvoker, MaximumChars, _stateMonitor, combinedSearchMetadataCollection, horizonalScrollArgs.SetAsComplete(), _userScrollRequested.SetAsComplete()); }
public SearchProxyCollection(ISearchMetadataCollection metadataCollection, Guid id, Action<SearchMetadata> changeScopeAction, ISchedulerProvider schedulerProvider, IColourProvider colourProvider, IIconProvider iconsProvider, ITextAssociationCollection textAssociationCollection, IThemeProvider themeProvider) { var proxyItems = metadataCollection.Metadata.Connect() .WhereReasonsAre(ChangeReason.Add, ChangeReason.Remove) //ignore updates because we update from here .Transform(meta => { return new SearchOptionsProxy(meta, changeScopeAction, colourProvider, themeProvider, new IconSelector(iconsProvider, schedulerProvider), m => metadataCollection.Remove(m.SearchText), iconsProvider.DefaultIconSelector, id); }) .SubscribeMany(so => { //when a value changes, write the original value back to the metadata collection var anyPropertyHasChanged = so.WhenAnyPropertyChanged() .Select(_ => (SearchMetadata) so) .Subscribe(metadataCollection.AddorUpdate); //when an icon or colour has changed we need to record user choice so //the same choice can be used again var iconChanged = so.WhenValueChanged(proxy => proxy.IconKind, false).ToUnit(); var colourChanged = so.WhenValueChanged(proxy => proxy.HighlightHue, false).ToUnit(); var ignoreCaseChanged = so.WhenValueChanged(proxy => proxy.CaseSensitive, false).ToUnit(); var textAssociationChanged = iconChanged.Merge(colourChanged).Merge(ignoreCaseChanged) .Throttle(TimeSpan.FromMilliseconds(250)) .Select(_ => new TextAssociation(so.Text, so.CaseSensitive, so.UseRegex, so.HighlightHue.Swatch, so.IconKind.ToString(), so.HighlightHue.Name, DateTime.UtcNow)) .Subscribe(textAssociationCollection.MarkAsChanged); return new CompositeDisposable(anyPropertyHasChanged, textAssociationChanged); }) .AsObservableCache(); Count = proxyItems.CountChanged.StartWith(0).ForBinding(); var monitor = MonitorPositionalChanges().Subscribe(metadataCollection.Add); //load data onto grid var collection = new ObservableCollectionExtended<SearchOptionsProxy>(); var includedLoader = proxyItems .Connect(proxy => !proxy.IsExclusion) .Sort(SortExpressionComparer<SearchOptionsProxy>.Ascending(proxy => proxy.Position)) .ObserveOn(schedulerProvider.MainThread) //force reset for each new or removed item dues to a bug in the underlying dragablz control which inserts in an incorrect position .Bind(collection, new ObservableCollectionAdaptor<SearchOptionsProxy, string>(0)) .DisposeMany() .Subscribe(); var excludedLoader = proxyItems .Connect(proxy => proxy.IsExclusion) .Sort(SortExpressionComparer<SearchOptionsProxy>.Ascending(proxy => proxy.Text)) .ObserveOn(schedulerProvider.MainThread) //force reset for each new or removed item dues to a bug in the underlying dragablz control which inserts in an incorrect position .Bind(out var excluded) .DisposeMany() .Subscribe(); Excluded = excluded; Included = new ReadOnlyObservableCollection<SearchOptionsProxy>(collection); _cleanUp = new CompositeDisposable(proxyItems, includedLoader, excludedLoader, monitor); }
public InlineViewer([NotNull] IObservable <ILineProvider> lineProvider, [NotNull] IObservable <LineProxy> selectedChanged, [NotNull] IClipboardHandler clipboardHandler, [NotNull] ISchedulerProvider schedulerProvider, [NotNull] ISelectionMonitor selectionMonitor, [NotNull] ILogger logger, [NotNull] IThemeProvider themeProvider, [NotNull] ITextFormatter textFormatter, [NotNull] ILineMatches lineMatches) { if (lineProvider == null) { throw new ArgumentNullException(nameof(lineProvider)); } if (selectedChanged == null) { throw new ArgumentNullException(nameof(selectedChanged)); } if (clipboardHandler == null) { throw new ArgumentNullException(nameof(clipboardHandler)); } if (schedulerProvider == null) { throw new ArgumentNullException(nameof(schedulerProvider)); } if (selectionMonitor == null) { throw new ArgumentNullException(nameof(selectionMonitor)); } if (themeProvider == null) { throw new ArgumentNullException(nameof(themeProvider)); } SelectionMonitor = selectionMonitor; CopyToClipboardCommand = new Command(() => clipboardHandler.WriteToClipboard(selectionMonitor.GetSelectedText())); _isSettingScrollPosition = false; var pageSize = this.WhenValueChanged(vm => vm.PageSize); //if use selection is null, tail the file var scrollSelected = selectedChanged .CombineLatest(lineProvider, pageSize, (proxy, lp, pge) => proxy == null ? new ScrollRequest(pge, 0) : new ScrollRequest(pge, proxy.Start)) .DistinctUntilChanged(); var horizonalScrollArgs = new ReplaySubject <TextScrollInfo>(1); HorizonalScrollChanged = hargs => { horizonalScrollArgs.OnNext(hargs); }; var scrollUser = _userScrollRequested .Where(x => !_isSettingScrollPosition) .Select(request => new ScrollRequest(ScrollReason.User, request.PageSize, request.FirstIndex)); var scroller = scrollSelected.Merge(scrollUser) .ObserveOn(schedulerProvider.Background) .DistinctUntilChanged(); var lineScroller = new LineScroller(lineProvider, scroller); Count = lineProvider.Select(lp => lp.Count).ForBinding(); MaximumChars = lineScroller.MaximumLines() .ObserveOn(schedulerProvider.MainThread) .ForBinding(); var proxyFactory = new LineProxyFactory(textFormatter, lineMatches, horizonalScrollArgs.DistinctUntilChanged(), themeProvider); //load lines into observable collection var loader = lineScroller.Lines.Connect() .Transform(proxyFactory.Create, new ParallelisationOptions(ParallelType.Ordered, 3)) .Sort(SortExpressionComparer <LineProxy> .Ascending(proxy => proxy)) .ObserveOn(schedulerProvider.MainThread) .Bind(out _data) .DisposeMany() .LogErrors(logger) .Subscribe(); // track first visible index [required to set scroll extent] var firstIndexMonitor = lineScroller.Lines.Connect() .Buffer(TimeSpan.FromMilliseconds(250)).FlattenBufferResult() .ToCollection() .Select(lines => lines.Count == 0 ? 0 : lines.Select(l => l.Index).Max() - lines.Count + 1) .ObserveOn(schedulerProvider.MainThread) .Subscribe(first => { try { _isSettingScrollPosition = true; FirstIndex = first; } finally { _isSettingScrollPosition = false; } }); _cleanUp = new CompositeDisposable(lineScroller, loader, Count, firstIndexMonitor, SelectionMonitor, MaximumChars, horizonalScrollArgs.SetAsComplete(), _userScrollRequested.SetAsComplete()); }
public InlineViewer([NotNull] InlineViewerArgs args, [NotNull] IClipboardHandler clipboardHandler, [NotNull] ISchedulerProvider schedulerProvider, [NotNull] ISelectionMonitor selectionMonitor) { if (args == null) { throw new ArgumentNullException(nameof(args)); } if (clipboardHandler == null) { throw new ArgumentNullException(nameof(clipboardHandler)); } if (schedulerProvider == null) { throw new ArgumentNullException(nameof(schedulerProvider)); } if (selectionMonitor == null) { throw new ArgumentNullException(nameof(selectionMonitor)); } SelectionMonitor = selectionMonitor; CopyToClipboardCommand = new Command(() => clipboardHandler.WriteToClipboard(selectionMonitor.GetSelectedText())); _isSettingScrollPosition = false; var lineProvider = args.LineProvider; var selectedChanged = args.SelectedChanged; var pageSize = this.WhenValueChanged(vm => vm.PageSize); var scrollSelected = selectedChanged.Where(proxy => proxy != null) .CombineLatest(lineProvider, pageSize, (proxy, lp, pge) => new ScrollRequest(pge, (int)proxy.Start, true)); var scrollUser = _userScrollRequested .Where(x => !_isSettingScrollPosition) .Select(request => new ScrollRequest(ScrollReason.User, request.PageSize, request.FirstIndex)); var scroller = scrollSelected.Merge(scrollUser) .ObserveOn(schedulerProvider.Background) .DistinctUntilChanged(); var lineScroller = new LineScroller(lineProvider, scroller); Count = lineProvider.Select(lp => lp.Count).ForBinding(); //load lines into observable collection var loader = lineScroller.Lines.Connect() .Transform(line => new LineProxy(line)) .Sort(SortExpressionComparer <LineProxy> .Ascending(proxy => proxy)) .ObserveOn(schedulerProvider.MainThread) .Bind(out _data) .Subscribe(); // track first visible index[required to set scroll extent] var firstIndexMonitor = lineScroller.Lines.Connect() .Buffer(TimeSpan.FromMilliseconds(250)).FlattenBufferResult() .ToCollection() .Select(lines => lines.Count == 0 ? 0 : lines.Select(l => l.Index).Max() - lines.Count + 1) .ObserveOn(schedulerProvider.MainThread) .Subscribe(first => { try { _isSettingScrollPosition = true; FirstIndex = first; } finally { _isSettingScrollPosition = false; } }); _cleanUp = new CompositeDisposable(lineScroller, loader, Count, firstIndexMonitor, SelectionMonitor, Disposable.Create(() => { _userScrollRequested.OnCompleted(); })); }
public CreateViewModel(INorthwindRepository northwindRepository) { this.northwindRepository = northwindRepository; products = new SourceCache <ProductOnStore, int>(p => p.ProductID); productsInOrder = new SourceCache <ProductInOrder, int>(p => p.ProductID); employees = new SourceList <Employee>(); customers = new SourceList <Customer>(); var canRemoveAllExecute = productsInOrder.CountChanged. Select(currentCountOfItems => { if (currentCountOfItems == 0) { return(false); } else { return(true); } }); productsInOrder.CountChanged.Subscribe(currentCount => { CountOfProductsInOrder = currentCount; }); var canCreateOrderExecute = this.WhenAnyValue(vm => vm.SelectedCustomer, vm => vm.SelectedEmployee, vm => vm.CountOfProductsInOrder). Select(x => { if (x.Item1 == null || x.Item2 == null || x.Item3 == 0) { return(false); } return(true); }); this.WhenAnyValue(vm => vm.SelectedCustomer, vm => vm.SelectedEmployee, vm => vm.CountOfProductsInOrder). Subscribe(x => { if (x.Item1 != null || x.Item2 != null || x.Item3 != 0) { OrderDate = DateTime.Now.ToLongDateString(); } else { OrderDate = ""; } }); products.Connect(). OnItemAdded(a => MakeSubscribtion(a)). Filter(x => x.UnitsInStock != 0). Sort(SortExpressionComparer <ProductOnStore> .Ascending(item => item.ProductID)). ObserveOnDispatcher(). Bind(out _productsInStore). Subscribe(); productsInOrder.Connect(). Sort(SortExpressionComparer <ProductInOrder> .Ascending(x => x.ProductID)). ObserveOnDispatcher(). Bind(out _productsInOrder). OnItemAdded(x => SubscribeToChanges(x)). OnItemRemoved(y => RemoveProduct(y)). Subscribe(); employees.Connect(). ObserveOnDispatcher(). Bind(out _employees). Subscribe(); customers.Connect(). ObserveOnDispatcher(). Bind(out _customers). Subscribe(); RemoveAllCommand = ReactiveCommand.Create(RemoveAllCommandExecute, canRemoveAllExecute); CreateOrderCommand = ReactiveCommand.Create(CreateOrderExecute, canCreateOrderExecute); }
public MainViewModel( ProviderViewModelFactory providerFactory, AuthViewModelFactory authFactory, IProviderStorage storage, IScheduler current, IScheduler main) { _storage = storage; _refresh = ReactiveCommand.CreateFromTask( storage.Refresh, outputScheduler: main); var providers = storage.Read(); providers.Transform(x => providerFactory(x, authFactory(x))) .Sort(SortExpressionComparer <IProviderViewModel> .Descending(x => x.Created)) .ObserveOn(RxApp.MainThreadScheduler) .StartWithEmpty() .Bind(out _providers) .Subscribe(); _isLoading = _refresh .IsExecuting .ToProperty(this, x => x.IsLoading, scheduler: current); _isReady = _refresh .IsExecuting .Skip(1) .Select(executing => !executing) .ToProperty(this, x => x.IsReady, scheduler: current); providers.Where(changes => changes.Any()) .ObserveOn(RxApp.MainThreadScheduler) .OnItemAdded(x => SelectedProvider = Providers.FirstOrDefault()) .OnItemRemoved(x => SelectedProvider = null) .Subscribe(); var canRemove = this .WhenAnyValue(x => x.SelectedProvider) .Select(provider => provider != null); _remove = ReactiveCommand.CreateFromTask( () => storage.Remove(SelectedProvider.Id), canRemove); var canAddProvider = this .WhenAnyValue(x => x.SelectedSupportedType) .Select(type => !string.IsNullOrWhiteSpace(type)); _add = ReactiveCommand.CreateFromTask( () => storage.Add(SelectedSupportedType), canAddProvider); _welcomeScreenVisible = this .WhenAnyValue(x => x.SelectedProvider) .Select(provider => provider == null) .ToProperty(this, x => x.WelcomeScreenVisible); _welcomeScreenCollapsed = this .WhenAnyValue(x => x.WelcomeScreenVisible) .Select(visible => !visible) .ToProperty(this, x => x.WelcomeScreenCollapsed); var canUnSelect = this .WhenAnyValue(x => x.SelectedProvider) .Select(provider => provider != null); _unselect = ReactiveCommand.Create( () => { SelectedProvider = null; }, canUnSelect); Activator = new ViewModelActivator(); this.WhenActivated(disposables => { SelectedSupportedType = SupportedTypes.FirstOrDefault(); _refresh.Execute() .Subscribe() .DisposeWith(disposables); }); }
public MainWindowViewModel() { List <IDto> dtos = new List <IDto>(); AddressDto address1 = new AddressDto(1); QuotationDto quotation1 = new QuotationDto(1, 1); QuotationDto quotation2 = new QuotationDto(1, 2); ProjectDto project1 = new ProjectDto(1, 1, "1st"); AddressDto address2 = new AddressDto(2); QuotationDto quotation3 = new QuotationDto(2, 3); QuotationDto quotation4 = new QuotationDto(2, 4); ProjectDto project2 = new ProjectDto(2, 2, "2nd"); FolderDto quotFolder1 = new FolderDto(1, DtoType.Quotation); FolderDto projFolder1 = new FolderDto(1, DtoType.Project); FolderDto quotFolder2 = new FolderDto(2, DtoType.Quotation); FolderDto projFolder2 = new FolderDto(2, DtoType.Project); #region add to list dtos.Add(address1); dtos.Add(quotation1); dtos.Add(quotation2); dtos.Add(project1); dtos.Add(address2); dtos.Add(quotation3); dtos.Add(quotation4); dtos.Add(project2); dtos.Add(quotFolder1); dtos.Add(quotFolder2); dtos.Add(projFolder1); dtos.Add(projFolder2); #endregion Source.AddOrUpdate(dtos); bool DefaultPredicate(Node <IDto, string> node) => node.IsRoot; //bool DefaultPredicate(Node<IDto, string> node) => node.Item.DtoType == DtoType.Folder; var tree = Source.Connect() .TransformToTree(dto => dto.GetMyParentId(), Observable.Return((Func <Node <IDto, string>, bool>)DefaultPredicate)) .Transform(n => new MyNode(n)) .Sort(SortExpressionComparer <MyNode> .Ascending(node => { switch (node.Item.DtoType) { case DtoType.Address: return(Convert.ToInt32((((int)node.Item.DtoType).ToString() + node.Item.AddressId.ToString()))); case DtoType.Project: return(Convert.ToInt32((((int)node.Item.DtoType).ToString() + node.Item.AddressId.ToString() + (node.Item as ProjectDto).ProjectId.ToString()))); case DtoType.Quotation: return(Convert.ToInt32((((int)node.Item.DtoType).ToString() + node.Item.AddressId.ToString() + (node.Item as QuotationDto).QuotationId.ToString()))); default: return(node.Item.AddressId); } })) .Bind(out _mainItems) .DisposeMany() .Subscribe(); AddSomethingCommand = new Command(AddSomethingNew); MoveCommand = new Command(MoveProjectQuotation); }
public StatisticService(INorthwindRepository northwindRepository) { this.northwindRepository = northwindRepository; productsList = new SourceList <Product>(); customersList = new SourceList <Customer>(); orderDetailsList = new SourceList <Order_Detail>(); ordersList = new SourceList <Order>(); #region Customers statistics customersByCountries = customersList.Connect(). GroupOn(customer => customer.Country). Transform(customersGroup => new CustomersByCountry() { CountryName = customersGroup.GroupKey, CustomersCount = customersGroup.List.Count }). ObserveOnDispatcher(). Bind(out _customersByCountries); purchasesByCustomers = orderDetailsList.Connect(). Transform(orderDetail => new { CompanyName = orderDetail.Order.Customer.CompanyName, PurchaseByOrderDetail = orderDetail.UnitPrice * orderDetail.Quantity }). GroupOn(orderDetail => orderDetail.CompanyName). Transform(groupOfOrderDetails => new PurchasesByCustomers() { CompanyName = groupOfOrderDetails.GroupKey, Purchases = groupOfOrderDetails.List.Items.Sum(a => a.PurchaseByOrderDetail) }). Sort(SortExpressionComparer <PurchasesByCustomers> .Descending(a => a.Purchases)). Top(10). ObserveOnDispatcher(). Bind(out _purchasesByCustomers); #endregion #region Employees statistics salesByEmployees = orderDetailsList.Connect(). Transform(orderDetail => new { LastName = orderDetail.Order.Employee.LastName, SaleByOrderDetail = orderDetail.UnitPrice * orderDetail.Quantity }). GroupOn(orderDetail => orderDetail.LastName). Transform(groupOfOrderDetail => new EmployeeSales() { LastName = groupOfOrderDetail.GroupKey, Sales = groupOfOrderDetail.List.Items.Sum(a => a.SaleByOrderDetail) }). ObserveOnDispatcher(). Sort(SortExpressionComparer <EmployeeSales> .Ascending(a => a.Sales)). Bind(out _salesByEmployees); #endregion #region Products statistics productsByCategories = productsList.Connect(). GroupOn(product => product.Category.CategoryName). Transform(groupOfProducts => new ProductsByCateogries() { CategoryName = groupOfProducts.GroupKey, NumberOfProducts = groupOfProducts.List.Count }). ObserveOnDispatcher(). Bind(out _productsByCategories); #endregion #region Orders statistics ordersByCountries = ordersList.Connect(). GroupOn(order => order.Customer.Country). Transform(groupOfOrders => new OrdersByCountry() { Country = groupOfOrders.GroupKey, NumberOfOrders = groupOfOrders.List.Count }). ObserveOnDispatcher(). Top(10). Bind(out _ordersByCountries); salesByCountries = orderDetailsList.Connect().Transform(orderDetail => new { Country = orderDetail.Order.Customer.Country, SaleByOrderDetail = orderDetail.UnitPrice * orderDetail.Quantity }). GroupOn(orderDetail => orderDetail.Country). Transform(groupOfOrderDetails => new SalesByCountry() { Country = groupOfOrderDetails.GroupKey, Sales = groupOfOrderDetails.List.Items.Sum(a => a.SaleByOrderDetail) }). Sort(SortExpressionComparer <SalesByCountry> .Ascending(a => a.Sales)). ObserveOnDispatcher(). Bind(out _salesByCountries); salesByCategories = orderDetailsList.Connect(). Transform(orderDetail => new { Category = orderDetail.Product.Category.CategoryName, SaleByOrderDetail = orderDetail.UnitPrice * orderDetail.Quantity }). GroupOn(orderDetail => orderDetail.Category). Transform(groupOfOrderDeatils => new SalesByCategory() { Category = groupOfOrderDeatils.GroupKey, Sales = groupOfOrderDeatils.List.Items.Sum(a => a.SaleByOrderDetail) }). ObserveOnDispatcher(). Bind(out _salesByCategories); #endregion FillCollections(); }
public void DoesNotThrow2() { var cache = new SourceCache <Data, int>(d => d.Id); var disposable = cache.Connect().Sort(new BehaviorSubject <IComparer <Data> >(SortExpressionComparer <Data> .Ascending(d => d.Id))).Subscribe(); disposable.Dispose(); }
public LogEntryViewer(ILogEntryService logEntryService) { //build an observable filter var filter = this.WhenAnyValue(x => x.SearchText) .Throttle(TimeSpan.FromMilliseconds(250)) .Select(BuildFilter); //filter, sort and populate reactive list. var shared = logEntryService.Items.Connect() .Transform(le => new LogEntryProxy(le)) .DelayRemove(TimeSpan.FromSeconds(0.75), proxy => proxy.FlagForRemove()) .Publish(); var loader = shared.Filter(filter) .Sort(SortExpressionComparer <LogEntryProxy> .Descending(le => le.TimeStamp).ThenByDescending(l => l.Key), SortOptions.UseBinarySearch) .ObserveOn(RxApp.MainThreadScheduler) .Bind(Data) .DisposeMany() .Subscribe(); //aggregate total items var summariser = shared .QueryWhenChanged(items => { var debug = items.Count(le => le.Level == LogLevel.Debug); var info = items.Count(le => le.Level == LogLevel.Info); var warn = items.Count(le => le.Level == LogLevel.Warning); var error = items.Count(le => le.Level == LogLevel.Error); return(new LogEntrySummary(debug, info, warn, error)); }) .Subscribe(s => Summary = s); //manage user selection, delete items command var selectedItems = _selectionController.SelectedItems.Connect().Publish(); //Build a message from selected items _deleteItemsText = selectedItems.QueryWhenChanged(query => { if (query.Count == 0) { return("Select log entries to delete"); } if (query.Count == 1) { return("Delete selected log entry?"); } return($"Delete {query.Count} log entries?"); }) .ToProperty(this, viewmodel => viewmodel.DeleteItemsText, "Select log entries to delete"); //make a command out of selected items - enabling the command when there is a selection DeleteCommand = ReactiveCommand.Create(() => { var toRemove = _selectionController.SelectedItems.Items.Select(proxy => proxy.Original).ToArray(); _selectionController.Clear(); logEntryService.Remove(toRemove); }, selectedItems.QueryWhenChanged(query => query.Count > 0)); var connected = selectedItems.Connect(); var connectedItems = shared.Connect(); _cleanUp = Disposable.Create(() => { loader.Dispose(); connected.Dispose(); _deleteItemsText.Dispose(); DeleteCommand.Dispose(); _selectionController.Dispose(); summariser.Dispose(); connectedItems.Dispose(); }); }
public FileTailerViewModel(ILogger logger, ISchedulerProvider schedulerProvider, FileInfo fileInfo) { if (logger == null) { throw new ArgumentNullException(nameof(logger)); } if (schedulerProvider == null) { throw new ArgumentNullException(nameof(schedulerProvider)); } if (fileInfo == null) { throw new ArgumentNullException(nameof(fileInfo)); } var filterRequest = this.WhenValueChanged(vm => vm.SearchText).Throttle(TimeSpan.FromMilliseconds(125)); var autoChanged = this.WhenValueChanged(vm => vm.AutoTail); var scroller = _userScrollRequested .CombineLatest(autoChanged, (user, auto) => { var mode = AutoTail ? ScrollingMode.Tail : ScrollingMode.User; return(new ScrollRequest(mode, user.PageSize, user.FirstIndex)); }) .Sample(TimeSpan.FromMilliseconds(150)) .DistinctUntilChanged(); var tailer = new FileTailer(fileInfo, filterRequest, scroller); //create user display for count line count var lineCounter = tailer.TotalLines.CombineLatest(tailer.MatchedLines, (total, matched) => { return(total == matched ? $"File has {total.ToString("#,###")} lines" : $"Showing {matched.ToString("#,###")} of {total.ToString("#,###")} lines"); }) .Subscribe(text => LineCountText = text); //load lines into observable collection var loader = tailer.Lines.Connect() .Transform(line => new LineProxy(line)) .Sort(SortExpressionComparer <LineProxy> .Ascending(proxy => proxy.Number)) .ObserveOn(schedulerProvider.MainThread) .Bind(out _data) .Subscribe(changes => logger.Info($"Rows changed {changes.Adds} adds, {changes.Removes} removed"), ex => logger.Error(ex, "There is a problem with bind data")); //monitor matching lines and start index, var matchedLinesMonitor = tailer.MatchedLines .Subscribe(matched => MatchedLineCount = matched); //track first visible index var firstIndexMonitor = tailer.Lines.Connect() .QueryWhenChanged(lines => lines.Count == 0 ? 0 : lines.Select(l => l.Index).Min()) .Subscribe(first => FirstIndex = first); _cleanUp = new CompositeDisposable(tailer, lineCounter, loader, firstIndexMonitor, matchedLinesMonitor, Disposable.Create(() => { _userScrollRequested.OnCompleted(); })); }
private static IComparer <EntryModel> GetSorting(Func <EntryModel, IComparable> f) { return(SortExpressionComparer <EntryModel> .Ascending(f)); }
public FileTailerViewModel([NotNull] ILogger logger, [NotNull] ISchedulerProvider schedulerProvider, [NotNull] IFileWatcher fileWatcher, [NotNull] ISelectionMonitor selectionMonitor, [NotNull] IClipboardHandler clipboardHandler, [NotNull] ISearchInfoCollection searchInfoCollection, [NotNull] IInlineViewerFactory inlineViewerFactory, [NotNull] ISetting <GeneralOptions> generalOptions, [NotNull] IRecentSearchCollection recentSearchCollection, [NotNull] SearchHints searchHints) { if (logger == null) { throw new ArgumentNullException(nameof(logger)); } if (schedulerProvider == null) { throw new ArgumentNullException(nameof(schedulerProvider)); } if (fileWatcher == null) { throw new ArgumentNullException(nameof(fileWatcher)); } if (selectionMonitor == null) { throw new ArgumentNullException(nameof(selectionMonitor)); } if (clipboardHandler == null) { throw new ArgumentNullException(nameof(clipboardHandler)); } if (searchInfoCollection == null) { throw new ArgumentNullException(nameof(searchInfoCollection)); } if (inlineViewerFactory == null) { throw new ArgumentNullException(nameof(inlineViewerFactory)); } if (generalOptions == null) { throw new ArgumentNullException(nameof(generalOptions)); } if (searchHints == null) { throw new ArgumentNullException(nameof(searchHints)); } SelectionMonitor = selectionMonitor; SearchHints = searchHints; CopyToClipboardCommand = new Command(() => clipboardHandler.WriteToClipboard(selectionMonitor.GetSelectedText())); SelectedItemsCount = selectionMonitor.Selected.Connect().QueryWhenChanged(collection => collection.Count).ForBinding(); SearchCollection = new SearchCollection(searchInfoCollection, schedulerProvider); UsingDarkTheme = generalOptions.Value .ObserveOn(schedulerProvider.MainThread) .Select(options => options.Theme == Theme.Dark) .ForBinding(); HighlightTail = generalOptions.Value .ObserveOn(schedulerProvider.MainThread) .Select(options => options.HighlightTail) .ForBinding(); HighlightDuration = generalOptions.Value .ObserveOn(schedulerProvider.MainThread) .Select(options => new Duration(TimeSpan.FromSeconds(options.HighlightDuration))) .ForBinding(); //An observable which acts as a scroll command var autoChanged = this.WhenValueChanged(vm => vm.AutoTail); var scroller = _userScrollRequested.CombineLatest(autoChanged, (user, auto) => { var mode = AutoTail ? ScrollReason.Tail : ScrollReason.User; return(new ScrollRequest(mode, user.PageSize, user.FirstIndex)); }) .ObserveOn(schedulerProvider.Background) .DistinctUntilChanged(); //tailer is the main object used to tail, scroll and filter in a file var lineScroller = new LineScroller(SearchCollection.Latest.ObserveOn(schedulerProvider.Background), scroller); //Add a complete file display [No search info here] var indexed = fileWatcher.Latest.Index() .Replay(1).RefCount(); IsLoading = indexed.Take(1).Select(_ => false).StartWith(true).ForBinding(); searchInfoCollection.Add("<All>", indexed, SearchType.All); //command to add the current search to the tail collection KeepSearchCommand = new Command(() => { var text = SearchHints.SearchText; var latest = fileWatcher.Latest .Search(s => s.Contains(text, StringComparison.OrdinalIgnoreCase)) .Replay(1).RefCount(); searchInfoCollection.Add(text, latest); recentSearchCollection.Add(new RecentSearch(text)); SearchHints.SearchText = string.Empty; }, () => SearchHints.SearchText.IsLongerThanOrEqualTo(3)); //User feedback to show file size FileSizeText = fileWatcher.Latest.Select(fn => fn.Size) .Select(size => size.FormatWithAbbreviation()) .DistinctUntilChanged() .ForBinding(); //User feedback to guide them whilst typing SearchHint = this.WhenValueChanged(vm => vm.SearchHints.SearchText) .Select(text => { if (string.IsNullOrEmpty(text)) { return("Type to search"); } return(text.Length < 3 ? "Enter at least 3 characters" : "Hit enter to search"); }).ForBinding(); //load lines into observable collection var lineProxyFactory = new LineProxyFactory(new TextFormatter(searchInfoCollection)); var loader = lineScroller.Lines.Connect() .Transform(lineProxyFactory.Create, new ParallelisationOptions(ParallelType.Ordered, 5)) .Sort(SortExpressionComparer <LineProxy> .Ascending(proxy => proxy)) .ObserveOn(schedulerProvider.MainThread) .Bind(out _data) .DisposeMany() .Subscribe(changes => logger.Info($"Rows changed. {changes.Adds} adds, {changes.Removes} removed"), ex => logger.Error(ex, "There is a problem with bind data")); //monitor matching lines and start index, Count = indexed.Select(latest => latest.Count).ForBinding(); CountText = indexed.Select(latest => $"{latest.Count.ToString("##,###")} lines").ForBinding(); LatestCount = SearchCollection.Latest.Select(latest => latest.Count).ForBinding(); //track first visible index var firstIndexMonitor = lineScroller.Lines.Connect() .Buffer(TimeSpan.FromMilliseconds(250)).FlattenBufferResult() .ToCollection() .Select(lines => lines.Count == 0 ? 0 : lines.Select(l => l.Index).Max() - lines.Count + 1) .ObserveOn(schedulerProvider.MainThread) .Subscribe(first => FirstIndex = first); //Create objects required for inline viewing var isUserDefinedChanged = SearchCollection.WhenValueChanged(sc => sc.Selected) .Select(selected => selected.IsUserDefined) .DistinctUntilChanged(); var inlineViewerVisible = isUserDefinedChanged.CombineLatest(this.WhenValueChanged(vm => vm.ShowInline), (userDefined, showInline) => userDefined && showInline); CanViewInline = isUserDefinedChanged.ForBinding(); InlineViewerVisible = inlineViewerVisible.ForBinding(); //return an empty line provider unless user is viewing inline - this saves needless trips to the file var inline = indexed.CombineLatest(inlineViewerVisible, (index, ud) => ud ? index : new EmptyLineProvider()); InlineViewer = inlineViewerFactory.Create(inline, this.WhenValueChanged(vm => vm.SelectedItem), lineProxyFactory); _cleanUp = new CompositeDisposable(lineScroller, loader, firstIndexMonitor, IsLoading, Count, LatestCount, FileSizeText, SearchHint, SelectedItemsCount, CanViewInline, InlineViewer, InlineViewerVisible, SearchCollection, searchInfoCollection, HighlightTail, UsingDarkTheme, searchHints, Disposable.Create(() => { _userScrollRequested.OnCompleted(); SelectionMonitor?.Dispose(); })); }
public SearchOptionsViewModel(ISearchMetadataCollection metadataCollection, ISchedulerProvider schedulerProvider, SearchHints searchHints) { SearchHints = searchHints; //TODO: options for colour var swatches = new SwatchesProvider().Swatches; bool binding = false; var orderChanged = Observable.FromEventPattern <OrderChangedEventArgs>( h => PositionMonitor.OrderChanged += h, h => PositionMonitor.OrderChanged -= h) .Throttle(TimeSpan.FromMilliseconds(125)) .Select(evt => evt.EventArgs) .Where(args => args.PreviousOrder != null && args.NewOrder.Length == args.PreviousOrder.Length) .Select(positionChangedArgs => { //reprioritise filters and highlights return(positionChangedArgs.NewOrder .OfType <SearchOptionsProxy>() .Select((item, index) => new { Meta = (SearchMetadata)item, index }) //.Where(x => x.index != x.Meta.Position) .Select(x => new SearchMetadata(x.Meta, x.index)) .ToArray()); }) .Subscribe(positionChangedArgs => { positionChangedArgs.ForEach(metadataCollection.AddorUpdate); }); ReadOnlyObservableCollection <SearchOptionsProxy> data; var userOptions = metadataCollection.Metadata.Connect() .WhereReasonsAre(ChangeReason.Add, ChangeReason.Remove) //ignore updates because we update from here .Transform(meta => new SearchOptionsProxy(meta, swatches, m => metadataCollection.Remove(m.SearchText))) .SubscribeMany(so => { //when a value changes, write the original value back to the cache return(so.WhenAnyPropertyChanged() .Select(_ => new SearchMetadata(so.Position, so.Text, so.Filter, so.Highlight, so.UseRegex, so.IgnoreCase)) .Subscribe(metadataCollection.AddorUpdate)); }) .Sort(SortExpressionComparer <SearchOptionsProxy> .Ascending(proxy => proxy.Position)) .ObserveOn(schedulerProvider.MainThread) .Bind(out data) .Subscribe(); Data = data; //command to add the current search to the tail collection var searchInvoker = SearchHints.SearchRequested.Subscribe(request => { schedulerProvider.Background.Schedule(() => { metadataCollection.AddorUpdate(new SearchMetadata(metadataCollection.NextIndex(), request.Text, false, true, request.UseRegEx, true)); }); }); _cleanUp = new CompositeDisposable(searchInvoker, userOptions, searchInvoker, orderChanged); }
public Indexer([NotNull] IObservable <FileSegmentCollection> fileSegments, int compression = 10, int tailSize = 1000000, int sizeOfFileAtWhichThereIsAbsolutelyNoPointInIndexing = 250000000, Encoding encoding = null, IScheduler scheduler = null) { if (fileSegments == null) { throw new ArgumentNullException(nameof(fileSegments)); } //TODO: When File segment has got smaller => roll-over [do something about it] scheduler = scheduler ?? Scheduler.Default; var shared = fileSegments.Replay(1).RefCount(); //1. Get information from segment info var infoSubscriber = shared.Select(segments => segments.Info) .Take(1) .Subscribe(info => { Info = info; Encoding = encoding ?? info.GetEncoding(); }); //2. create a resulting index object from the collection of index fragments Result = _indicies .Connect() .Sort(SortExpressionComparer <Index> .Ascending(si => si.Start)) .ToCollection() .Scan((IndexCollection)null, (previous, notification) => new IndexCollection(notification, previous, Info, Encoding)) .Replay(1).RefCount(); //3. Scan the tail so results can be returned quickly var tailScanner = shared.Select(segments => segments.Tail).DistinctUntilChanged() .Scan((Index)null, (previous, current) => { if (previous == null) { var initial = Scan(current.Start, -1, 1); return(initial ?? new Index(0, 0, 0, 0, IndexType.Tail)); } var latest = Scan(previous.End, -1, 1); return(latest == null ? null : new Index(latest, previous)); }) .Where(tail => tail != null) .Replay(1).RefCount(); //4. estimate = var tailSubscriber = tailScanner.Subscribe(tail => { _indicies.Edit(innerList => { var existing = innerList.FirstOrDefault(si => si.Type == IndexType.Tail); if (existing != null) { innerList.Remove(existing); } innerList.Add(tail); }); }); //Scan the remainer of the file var headSubscriber = tailScanner.FirstAsync() .Subscribe(tail => { if (tail.Start == 0) { return; } //Need iterate one at a time through the var estimateLines = EstimateNumberOfLines(tail, Info); var estimate = new Index(0, tail.Start, compression, estimateLines, IndexType.Page); _indicies.Add(estimate); //keep it as an estimate for files over 250 meg [for now] if (tail.Start > sizeOfFileAtWhichThereIsAbsolutelyNoPointInIndexing) { return; } //todo: index first and last segment for large sized file scheduler.Schedule(() => { var actual = Scan(0, tail.Start, compression); _indicies.Edit(innerList => { innerList.Remove(estimate); innerList.Add(actual); }); }); }); _cleanUp = new CompositeDisposable(infoSubscriber, _indicies, tailSubscriber, tailSubscriber, headSubscriber); }
public SearchOptionsViewModel(ISearchMetadataCollection metadataCollection, ISearchMetadataFactory searchMetadataFactory, ISchedulerProvider schedulerProvider, IColourProvider colourProvider, IIconProvider iconsProvider, SearchHints searchHints) { SearchHints = searchHints; var proxyItems = metadataCollection.Metadata.Connect() .WhereReasonsAre(ChangeReason.Add, ChangeReason.Remove) //ignore updates because we update from here .Transform(meta => { return(new SearchOptionsProxy(meta, colourProvider, new IconSelector(iconsProvider, schedulerProvider), m => metadataCollection.Remove(m.SearchText), iconsProvider.DefaultIconSelector, Id)); }) .SubscribeMany(so => { //when a value changes, write the original value back to the cache return(so.WhenAnyPropertyChanged() .Select(_ => (SearchMetadata)so) .Subscribe(metadataCollection.AddorUpdate)); }) .AsObservableCache(); var monitor = MonitorPositionalChanges() .Subscribe(metadataCollection.Add); //load data onto grid var collection = new ObservableCollectionExtended <SearchOptionsProxy>(); var userOptions = proxyItems.Connect() .Sort(SortExpressionComparer <SearchOptionsProxy> .Ascending(proxy => proxy.Position)) .ObserveOn(schedulerProvider.MainThread) //force reset for each new or removed item dues to a bug in the underlying dragablz control which inserts in an incorrect position .Bind(collection, new ObservableCollectionAdaptor <SearchOptionsProxy, string>(0)) .DisposeMany() .Subscribe(); Data = new ReadOnlyObservableCollection <SearchOptionsProxy>(collection); //command to add the current search to the tail collection var searchInvoker = SearchHints.SearchRequested.Subscribe(request => { schedulerProvider.Background.Schedule(() => { var meta = searchMetadataFactory.Create(request.Text, request.UseRegEx, metadataCollection.NextIndex(), false); metadataCollection.AddorUpdate(meta); }); }); _cleanUp = new CompositeDisposable(searchInvoker, userOptions, searchInvoker, monitor, SearchHints); }
public void RebuildGrid() { Monik?.ApplicationVerbose("Matrix.RebuildGrid started"); MainGrid.Children.Clear(); if (Rows == null || Columns == null || Rows.Count == 0 || Columns.Count == 0) { Monik?.ApplicationVerbose("Matrix.RebuildGrid skip func"); return; } var columns = Columns.ToList(); int columnCount = Columns.Count; var rows = Rows.ToList(); int rowCount = Rows.Count; ////////////////// // 1. Fill columns ////////////////// MainGrid.ColumnDefinitions.Clear(); // rows header MainGrid.ColumnDefinitions.Add( new ColumnDefinition { Width = new GridLength(30, GridUnitType.Pixel) }); // columns for (int i = 0; i < columnCount; i++) { var it = columns[i]; Monik?.ApplicationVerbose($"Matrix.RebuildGrid add column {it.Id}::{it.Name}::{it.Order}"); var cd = new ColumnDefinition(); cd.DataContext = it; cd.Width = new GridLength(it.Size / 10.0, GridUnitType.Star); MainGrid.ColumnDefinitions.Add(cd); PropertyDescriptor pd = DependencyPropertyDescriptor.FromProperty(ColumnDefinition.WidthProperty, typeof(ColumnDefinition)); pd.AddValueChanged(cd, new EventHandler(ColumnWidthPropertyChanged)); ContentControl cc = new ContentControl(); cc.Content = it; cc.MouseMove += Head_MouseMove; cc.ContextMenu = HeadContextMenu; cc.ContentTemplate = (DataTemplate)this.Resources["DefaultHorizontalHeaderTemplate"]; MainGrid.Children.Add(cc); // Update number of Cards in Column CardsObservable .Filter(x => x.ColumnDeterminant == it.Id) .ToCollection() .Subscribe(x => it.CurNumberOfCards = x.Count()); // dont draw excess splitter if (i < columnCount - 1) { MainGrid.Children.Add(BuildVerticalSpliter(i, rowCount)); } Grid.SetColumn(cc, i + 1); Grid.SetRow(cc, 0); } /////////////// // 2. Fill rows /////////////// MainGrid.RowDefinitions.Clear(); // columns header MainGrid.RowDefinitions.Add( new RowDefinition { Height = new GridLength(30, GridUnitType.Pixel) }); // rows for (int i = 0; i < rowCount; i++) { var it = rows[i]; Monik?.ApplicationVerbose($"Matrix.RebuildGrid add row {it.Id}::{it.Name}::{it.Order}"); var rd = new RowDefinition(); rd.DataContext = it; rd.Height = new GridLength(it.Size / 10.0, GridUnitType.Star); MainGrid.RowDefinitions.Add(rd); PropertyDescriptor pd = DependencyPropertyDescriptor.FromProperty(RowDefinition.HeightProperty, typeof(RowDefinition)); pd.AddValueChanged(rd, new EventHandler(RowWidthPropertyChanged)); ContentControl cc = new ContentControl(); cc.Content = it; cc.MouseMove += Head_MouseMove; cc.ContextMenu = HeadContextMenu; cc.ContentTemplate = (DataTemplate)this.Resources["DefaulVerticalHeaderTemplate"]; MainGrid.Children.Add(cc); // Update number of Cards in Row CardsObservable .Filter(x => x.RowDeterminant == it.Id) .ToCollection() .Subscribe(x => it.CurNumberOfCards = x.Count()); // dont draw excess splitter if (i < rowCount - 1) { MainGrid.Children.Add(BuildHorizontalSpliter(i, columnCount)); } Grid.SetColumn(cc, 0); Grid.SetRow(cc, i + 1); } //////////////////////// // 3. Fill Intersections //////////////////////// for (int i = 0; i < Columns.Count; i++) { for (int j = 0; j < Rows.Count; j++) { int colDet = columns[i].Id; int rowDet = rows[j].Id; CardsObservable .Filter(x => x.ColumnDeterminant == colDet && x.RowDeterminant == rowDet) .Sort(SortExpressionComparer <ICard> .Ascending(c => c.Order)) .ObserveOnDispatcher() .Bind(out ReadOnlyObservableCollection <ICard> intersectionCards) .Subscribe(); Intersection cell = new Intersection(this) { DataContext = this, ColumnDeterminant = colDet, RowDeterminant = rowDet, SelfCards = intersectionCards }; MainGrid.Children.Add(cell); Grid.SetColumn(cell, i + 1); Grid.SetColumnSpan(cell, 1); Grid.SetRow(cell, j + 1); Grid.SetRowSpan(cell, 1); } } Monik?.ApplicationVerbose("Matrix.RebuildGrid finished"); }
public SearchListViewModel(IDrinkService drinkService) { _drinkDataService = drinkService; Func <ItemViewModel, bool> search(string searchTerm) => viewModel => { if (string.IsNullOrEmpty(searchTerm)) { return(true); } var lower = searchTerm.ToLower(); return(viewModel.Title.ToLower().Contains(lower) || (viewModel.Description?.ToLower().Contains(lower) ?? false)); }; var searchChanged = this.WhenAnyValue(x => x.SearchText) .Throttle(TimeSpan.FromMilliseconds(800), RxApp.TaskpoolScheduler) .DistinctUntilChanged() .Select(search); var items = _drinkDataService .ChangeSet .Transform(x => new ItemViewModel { Id = x.Id, Title = x.Title, Type = x.Type, Description = x.Description }); items .MergeMany((item, id) => item.Remove) .InvokeCommand(this, x => x.Remove) .DisposeWith(Garbage); var filter = items .AutoRefresh(x => x.Id) .DeferUntilLoaded() .Filter(searchChanged); filter .Sort(SortExpressionComparer <ItemViewModel> .Descending(x => x.Type) .ThenByAscending(x => x.Id)) .Bind(out _items) .DisposeMany() .Subscribe() .DisposeWith(Garbage); Add = ReactiveCommand.CreateFromObservable <EventArgs, Unit>(ExecuteAdd); Refresh = ReactiveCommand.CreateFromObservable <EventArgs, Unit>(ExecuteRefresh); Remove = ReactiveCommand.CreateFromObservable(ExecuteRemove, Observable.Return(true)); this.WhenAnyObservable(x => x.Refresh.IsExecuting) .StartWith(false) .DistinctUntilChanged() .ToProperty(this, nameof(IsRefreshing), out _isRefreshing) .DisposeWith(Garbage); }
private void InitializeBindings() { #region Log views var logViewsSharedSource = LogViewsSourceCache.Connect(); logViewsSharedSource .AutoRefresh(l => l.DateLastOpened) .Sort(SortExpressionComparer <LogView> .Descending(l => l.DateLastOpened)) .ObserveOnDispatcher() .Bind(out var logViews) .Subscribe(); logViewsSharedSource .AutoRefresh(l => l.DateLastOpened) .Sort(SortExpressionComparer <LogView> .Descending(l => l.DateLastOpened)) .Top(_numberInRecents) .ObserveOnDispatcher() .Bind(out var recentlyUsedLogViews) .Subscribe(); logViewsSharedSource .AutoRefresh(l => l.DateLastOpened) .AutoRefresh(l => l.IsPinned) .Filter(l => l.IsPinned) .Sort(SortExpressionComparer <LogView> .Descending(l => l.DateLastOpened)) .ObserveOnDispatcher() .Bind(out var pinnedLogViews) .Subscribe(); LogViews = logViews; RecentlyUsedLogViews = recentlyUsedLogViews; PinnedLogViews = pinnedLogViews; #endregion #region Log files var logFilesSharedSource = LogFilesSourceCache.Connect(); logFilesSharedSource .AutoRefresh(l => l.DateLastOpened) .Sort(SortExpressionComparer <LogFile> .Descending(l => l.DateLastOpened)) .ObserveOnDispatcher() .Bind(out var allLogFiles) .Subscribe(); logFilesSharedSource .AutoRefresh(l => l.DateLastOpened) .Sort(SortExpressionComparer <LogFile> .Descending(l => l.DateLastOpened)) .Top(_numberInRecents) .ObserveOnDispatcher() .Bind(out var recentlyUsedLogFiles) .Subscribe(); logFilesSharedSource .AutoRefresh(l => l.IsPinned) .AutoRefresh(l => l.DateLastOpened) .Filter(l => l.IsPinned) .Sort(SortExpressionComparer <LogFile> .Descending(l => l.DateLastOpened)) .ObserveOnDispatcher() .Bind(out var pinnedLogFiles) .Subscribe(); LogFiles = allLogFiles; RecentlyUsedLogFiles = recentlyUsedLogFiles; PinnedLogFiles = pinnedLogFiles; #endregion #region Databases var databasesSharedSource = DatabasesSourceCache.Connect(); databasesSharedSource .ObserveOnDispatcher() .Bind(out var databases) .Subscribe(); databasesSharedSource .AutoRefresh(d => d.DateLastOpened) .Sort(SortExpressionComparer <Database> .Descending(d => d.DateLastOpened)) .Top(_numberInRecents) .ObserveOnDispatcher() .Bind(out var recentlyUsedDatabases) .Subscribe(); databasesSharedSource .AutoRefresh(l => l.IsPinned) .AutoRefresh(d => d.DateLastOpened) .Filter(l => l.IsPinned) .Sort(SortExpressionComparer <Database> .Descending(d => d.DateLastOpened)) .ObserveOnDispatcher() .Bind(out var pinnedDatabases) .Subscribe(); Databases = databases; RecentlyUsedDatabases = recentlyUsedDatabases; PinnedDatabases = pinnedDatabases; #endregion #region Searchable openable objects //TODO: Right now if item added to source caches, this source cache won't see it. Maybe do a join? //TODO: Need to open search popup when user clicks on search box if there's text in the search box. AllOpenableObjectsSourceCache.AddOrUpdate(LogViewsSourceCache.Items); AllOpenableObjectsSourceCache.AddOrUpdate(LogFilesSourceCache.Items); AllOpenableObjectsSourceCache.AddOrUpdate(DatabasesSourceCache.Items); AllOpenableObjectsSourceCache.Connect() .AutoRefresh(d => SearchTerm) .Filter(o => string.IsNullOrEmpty(SearchTerm) || o.Identifier.ToLower().Contains(SearchTerm.ToLower())) .ObserveOnDispatcher() .Bind(out var searchableOpenableObjects) .Subscribe(); SearchableOpenableObjects = searchableOpenableObjects; #endregion }
public SearchOptionsViewModel(ISearchMetadataCollection metadataCollection, ISearchMetadataFactory searchMetadataFactory, ISchedulerProvider schedulerProvider, IColourProvider colourProvider, IIconProvider iconsProvider, ITextAssociationCollection textAssociationCollection, SearchHints searchHints) { SearchHints = searchHints; var proxyItems = metadataCollection.Metadata.Connect() .WhereReasonsAre(ChangeReason.Add, ChangeReason.Remove) //ignore updates because we update from here .Transform(meta => { return(new SearchOptionsProxy(meta, colourProvider, new IconSelector(iconsProvider, schedulerProvider), m => metadataCollection.Remove(m.SearchText), iconsProvider.DefaultIconSelector, Id)); }) .SubscribeMany(so => { //when a value changes, write the original value back to the metadata collection var anyPropertyHasChanged = so.WhenAnyPropertyChanged() .Select(_ => (SearchMetadata)so) .Subscribe(metadataCollection.AddorUpdate); //when an icon or colour has changed we need to record user choice so //the same choice can be used again var iconChanged = so.WhenValueChanged(proxy => proxy.IconKind, false).ToUnit(); var colourChanged = so.WhenValueChanged(proxy => proxy.HighlightHue, false).ToUnit(); var ignoreCaseChanged = so.WhenValueChanged(proxy => proxy.IgnoreCase, false).ToUnit(); var textAssociationChanged = iconChanged.Merge(colourChanged).Merge(ignoreCaseChanged) .Throttle(TimeSpan.FromMilliseconds(250)) .Select(_ => new TextAssociation(so.Text, so.IgnoreCase, so.UseRegex, so.HighlightHue.Swatch, so.IconKind.ToString(), so.HighlightHue.Name, DateTime.Now)) .Subscribe(textAssociationCollection.MarkAsChanged); return(new CompositeDisposable(anyPropertyHasChanged, textAssociationChanged)); }) .AsObservableCache(); var monitor = MonitorPositionalChanges() .Subscribe(metadataCollection.Add); //load data onto grid var collection = new ObservableCollectionExtended <SearchOptionsProxy>(); var userOptions = proxyItems.Connect() .Sort(SortExpressionComparer <SearchOptionsProxy> .Ascending(proxy => proxy.Position)) .ObserveOn(schedulerProvider.MainThread) //force reset for each new or removed item dues to a bug in the underlying dragablz control which inserts in an incorrect position .Bind(collection, new ObservableCollectionAdaptor <SearchOptionsProxy, string>(0)) .DisposeMany() .Subscribe(); Data = new ReadOnlyObservableCollection <SearchOptionsProxy>(collection); //command to add the current search to the tail collection var searchInvoker = SearchHints.SearchRequested .ObserveOn(schedulerProvider.Background) .Subscribe(request => { var meta = searchMetadataFactory.Create(request.Text, request.UseRegEx, metadataCollection.NextIndex(), false); metadataCollection.AddorUpdate(meta); }); _cleanUp = new CompositeDisposable(searchInvoker, userOptions, searchInvoker, monitor, SearchHints); }
public LogEntryViewer(ILogEntryService logEntryService) { _logEntryService = logEntryService; //apply filter when search text has changed var filterApplier = this.WhenAnyValue(x => x.SearchText) .Throttle(TimeSpan.FromMilliseconds(250)) .Select(BuildFilter) .Subscribe(_filter.Change); //filter, sort and populate reactive list. var loader = logEntryService.Items.Connect() .Transform(le => new LogEntryProxy(le)) .DelayRemove(TimeSpan.FromSeconds(0.75), proxy => { proxy.FlagForRemove(); _selectionController.DeSelect(proxy); }) .Filter(_filter) .Sort(SortExpressionComparer <LogEntryProxy> .Descending(le => le.TimeStamp).ThenByDescending(l => l.Key), SortOptimisations.ComparesImmutableValuesOnly) .ObserveOn(RxApp.MainThreadScheduler) .Bind(_data) .DisposeMany() .Subscribe(); //aggregate total items var summariser = logEntryService.Items.Connect() .QueryWhenChanged(query => { var items = query.Items.ToList(); var debug = items.Count(le => le.Level == LogLevel.Debug); var info = items.Count(le => le.Level == LogLevel.Info); var warn = items.Count(le => le.Level == LogLevel.Warning); var error = items.Count(le => le.Level == LogLevel.Error); return(new LogEntrySummary(debug, info, warn, error)); }) .Subscribe(s => Summary = s); //manage user selection, delete items command var selectedItems = _selectionController.SelectedItems.Connect().Publish(); //Build a message from selected items _deleteItemsText = selectedItems.QueryWhenChanged(query => { if (query.Count == 0) { return("Select log entries to delete"); } if (query.Count == 1) { return("Delete selected log entry?"); } return(string.Format("Delete {0} log entries?", query.Count)); }) .ToProperty(this, viewmodel => viewmodel.DeleteItemsText, "Select log entries to delete"); //make a command out of selected items - enabling the command when there is a selection _deleteCommand = selectedItems .QueryWhenChanged(query => query.Count > 0) .ToCommand(); //Assign action when the command is invoked var commandInvoker = this.WhenAnyObservable(x => x.DeleteCommand) .Subscribe(_ => { var toRemove = _selectionController.SelectedItems.Items.Select(proxy => proxy.Key).ToArray(); _logEntryService.Remove(toRemove); }); var connected = selectedItems.Connect(); _cleanUp = Disposable.Create(() => { loader.Dispose(); filterApplier.Dispose(); _filter.Dispose(); connected.Dispose(); _deleteItemsText.Dispose(); _deleteCommand.Dispose(); commandInvoker.Dispose(); _selectionController.Dispose(); summariser.Dispose(); }); }
// TODO: use some LINQ to make more fluent OR use IObservable<IComparer> to use .Sort overload protected IObservable <ISortedChangeSet <TrackViewModel, uint> > Sort(IObservable <IChangeSet <TrackViewModel, uint> > trackViewModelsChangesFlow) { return(trackViewModelsChangesFlow.Sort(SortExpressionComparer <TrackViewModel> .Descending(vm => vm.AddedToLibraryDateTime))); }
public TailViewModel([NotNull] ILogger logger, [NotNull] ISchedulerProvider schedulerProvider, [NotNull] IFileWatcher fileWatcher, [NotNull] ISelectionMonitor selectionMonitor, [NotNull] IClipboardHandler clipboardHandler, [NotNull] ISearchInfoCollection searchInfoCollection, [NotNull] IInlineViewerFactory inlineViewerFactory, [NotNull] ISetting <GeneralOptions> generalOptions, [NotNull] ISearchMetadataCollection searchMetadataCollection, [NotNull] IStateBucketService stateBucketService, [NotNull] SearchOptionsViewModel searchOptionsViewModel, //ISearchStateToMetadataMapper metadataMapper, [NotNull] SearchHints searchHints, [NotNull] ITailViewStateControllerFactory tailViewStateControllerFactory) { if (logger == null) { throw new ArgumentNullException(nameof(logger)); } if (schedulerProvider == null) { throw new ArgumentNullException(nameof(schedulerProvider)); } if (fileWatcher == null) { throw new ArgumentNullException(nameof(fileWatcher)); } if (selectionMonitor == null) { throw new ArgumentNullException(nameof(selectionMonitor)); } if (clipboardHandler == null) { throw new ArgumentNullException(nameof(clipboardHandler)); } if (searchInfoCollection == null) { throw new ArgumentNullException(nameof(searchInfoCollection)); } if (inlineViewerFactory == null) { throw new ArgumentNullException(nameof(inlineViewerFactory)); } if (generalOptions == null) { throw new ArgumentNullException(nameof(generalOptions)); } if (searchMetadataCollection == null) { throw new ArgumentNullException(nameof(searchMetadataCollection)); } if (stateBucketService == null) { throw new ArgumentNullException(nameof(stateBucketService)); } if (searchOptionsViewModel == null) { throw new ArgumentNullException(nameof(searchOptionsViewModel)); } if (searchHints == null) { throw new ArgumentNullException(nameof(searchHints)); } Name = fileWatcher.FullName; SelectionMonitor = selectionMonitor; SearchOptions = searchOptionsViewModel; SearchHints = searchHints; SearchCollection = new SearchCollection(searchInfoCollection, schedulerProvider); CopyToClipboardCommand = new Command(() => clipboardHandler.WriteToClipboard(selectionMonitor.GetSelectedText())); OpenFileCommand = new Command(() => Process.Start(fileWatcher.FullName)); OpenFolderCommand = new Command(() => Process.Start(fileWatcher.Folder)); SearchMetadataCollection = searchMetadataCollection; UsingDarkTheme = generalOptions.Value .ObserveOn(schedulerProvider.MainThread) .Select(options => options.Theme == Theme.Dark) .ForBinding(); HighlightTail = generalOptions.Value .ObserveOn(schedulerProvider.MainThread) .Select(options => options.HighlightTail) .ForBinding(); HighlightDuration = generalOptions.Value .ObserveOn(schedulerProvider.MainThread) .Select(options => new Duration(TimeSpan.FromSeconds(options.HighlightDuration))) .ForBinding(); _stateProvider = new TailViewPersister(this); //this controller responsible for loading and persisting user search stuff var stateController = tailViewStateControllerFactory.Create(this); //An observable which acts as a scroll command var autoChanged = this.WhenValueChanged(vm => vm.AutoTail); var scroller = _userScrollRequested.CombineLatest(autoChanged, (user, auto) => { var mode = AutoTail ? ScrollReason.Tail : ScrollReason.User; return(new ScrollRequest(mode, user.PageSize, user.FirstIndex)); }) .Do(x => logger.Info("Scrolling to {0}/{1}", x.FirstIndex, x.PageSize)) .DistinctUntilChanged(); FileStatus = fileWatcher.Status.ForBinding(); //command to add the current search to the tail collection var searchInvoker = SearchHints.SearchRequested.Subscribe(request => { searchInfoCollection.Add(request.Text, request.UseRegEx); }); //User feedback to show file size FileSizeText = fileWatcher.Latest.Select(fn => fn.Size) .Select(size => size.FormatWithAbbreviation()) .DistinctUntilChanged() .ForBinding(); //tailer is the main object used to tail, scroll and filter in a file var lineScroller = new LineScroller(SearchCollection.Latest.ObserveOn(schedulerProvider.Background), scroller); //load lines into observable collection var lineProxyFactory = new LineProxyFactory(new TextFormatter(searchMetadataCollection), new LineMatches(searchMetadataCollection)); var loader = lineScroller.Lines.Connect() .LogChanges(logger, "Received") .Transform(lineProxyFactory.Create, new ParallelisationOptions(ParallelType.Ordered, 3)) .LogChanges(logger, "Sorting") .Sort(SortExpressionComparer <LineProxy> .Ascending(proxy => proxy)) .ObserveOn(schedulerProvider.MainThread) .Bind(out _data, 100) .LogChanges(logger, "Bound") .DisposeMany() .LogErrors(logger) .Subscribe(); //monitor matching lines and start index, Count = searchInfoCollection.All.Select(latest => latest.Count).ForBinding(); CountText = searchInfoCollection.All.Select(latest => $"{latest.Count.ToString("##,###")} lines").ForBinding(); LatestCount = SearchCollection.Latest.Select(latest => latest.Count).ForBinding(); ////track first visible index var firstIndexMonitor = lineScroller.Lines.Connect() .Buffer(TimeSpan.FromMilliseconds(25)).FlattenBufferResult() .ToCollection() .Select(lines => lines.Count == 0 ? 0 : lines.Select(l => l.Index).Max() - lines.Count + 1) .ObserveOn(schedulerProvider.MainThread) .Subscribe(first => { FirstIndex = first; }); //Create objects required for inline viewing var isUserDefinedChanged = SearchCollection.WhenValueChanged(sc => sc.Selected) .Where(selected => selected != null) .Select(selected => selected.IsUserDefined) .DistinctUntilChanged() .Replay(1) .RefCount(); var inlineViewerVisible = isUserDefinedChanged.CombineLatest(this.WhenValueChanged(vm => vm.ShowInline), (userDefined, showInline) => userDefined && showInline); CanViewInline = isUserDefinedChanged.ForBinding(); InlineViewerVisible = inlineViewerVisible.ForBinding(); //return an empty line provider unless user is viewing inline - this saves needless trips to the file var inline = searchInfoCollection.All.CombineLatest(inlineViewerVisible, (index, ud) => ud ? index : new EmptyLineProvider()); InlineViewer = inlineViewerFactory.Create(inline, this.WhenValueChanged(vm => vm.SelectedItem), lineProxyFactory); _cleanUp = new CompositeDisposable(lineScroller, loader, firstIndexMonitor, FileStatus, Count, LatestCount, FileSizeText, CanViewInline, InlineViewer, InlineViewerVisible, SearchCollection, searchInfoCollection, HighlightTail, UsingDarkTheme, searchHints, searchMetadataCollection, SelectionMonitor, SearchOptions, searchInvoker, stateController, _userScrollRequested.SetAsComplete()); }
public MainViewModel(MainState state, IProviderFactory factory, ProviderViewModelFactory createViewModel) { _factory = factory; Refresh = ReactiveCommand.Create(state.Providers.Refresh); _isLoading = Refresh .IsExecuting .ToProperty(this, x => x.IsLoading); _isReady = Refresh .IsExecuting .Select(executing => !executing) .ToProperty(this, x => x.IsReady); state.Providers.Connect() .Transform(ps => createViewModel(ps, factory.CreateProvider(ps.Parameters))) .Sort(SortExpressionComparer <IProviderViewModel> .Descending(x => x.Created)) .ObserveOn(RxApp.MainThreadScheduler) .Bind(out _providers) .Subscribe(); var canRemove = this .WhenAnyValue(x => x.SelectedProvider) .Select(provider => provider != null); Remove = ReactiveCommand.Create( () => state.Providers.RemoveKey(SelectedProvider.Id), canRemove); var canAddProvider = this .WhenAnyValue(x => x.SelectedSupportedType) .Select(type => Enum.IsDefined(typeof(ProviderType), type)); Add = ReactiveCommand.Create( () => state.Providers.AddOrUpdate(new ProviderState { Type = SelectedSupportedType }), canAddProvider); _welcomeScreenVisible = this .WhenAnyValue(x => x.SelectedProvider) .Select(provider => provider == null) .ToProperty(this, x => x.WelcomeScreenVisible); _welcomeScreenCollapsed = this .WhenAnyValue(x => x.WelcomeScreenVisible) .Select(visible => !visible) .ToProperty(this, x => x.WelcomeScreenCollapsed); var canUnselect = this .WhenAnyValue(x => x.SelectedProvider) .Select(provider => provider != null); Unselect = ReactiveCommand.Create(() => Unit.Default, canUnselect); Unselect.Subscribe(unit => SelectedProvider = null); var outputCollectionChanges = Providers .ToObservableChangeSet(x => x.Id) .Publish() .RefCount(); outputCollectionChanges .Filter(provider => provider.Id == state.SelectedProviderId) .ObserveOn(RxApp.MainThreadScheduler) .OnItemAdded(provider => SelectedProvider = provider) .Subscribe(); outputCollectionChanges .OnItemRemoved(provider => SelectedProvider = null) .Subscribe(); this.WhenAnyValue(x => x.SelectedProvider) .Skip(1) .Select(provider => provider?.Id ?? Guid.Empty) .Subscribe(id => state.SelectedProviderId = id); SelectedSupportedType = state.SelectedSupportedType ?? SupportedTypes.First(); this.WhenAnyValue(x => x.SelectedSupportedType) .Subscribe(type => state.SelectedSupportedType = type); }
.Sort(SortExpressionComparer <WalletViewModelBase> .Descending(i => i.WalletState).ThenByDescending(i => i.IsLoggedIn).ThenByAscending(i => i.Title))
public MainWindowVM(RunMode mode, string source, MainWindow mainWindow) { this.Mode = mode; this.MainWindow = mainWindow; this._Installer = new Lazy <InstallerVM>(() => new InstallerVM(this)); this._Compiler = new Lazy <CompilerVM>(() => new CompilerVM(this, source)); // Set up logging _logSubj .ToObservableChangeSet() .Buffer(TimeSpan.FromMilliseconds(250)) .Where(l => l.Count > 0) .FlattenBufferResult() .Top(5000) .ObserveOn(RxApp.MainThreadScheduler) .Bind(this.Log) .Subscribe() .DisposeWith(this.CompositeDisposable); Utils.SetLoggerFn(s => _logSubj.OnNext(s)); Utils.SetStatusFn((msg, progress) => WorkQueue.Report(msg, progress)); // Wire mode to drive the active pane. // Note: This is currently made into a derivative property driven by mode, // but it can be easily changed into a normal property that can be set from anywhere if needed this._ActivePane = this.WhenAny(x => x.Mode) .Select <RunMode, ViewModel>(m => { switch (m) { case RunMode.Compile: return(this._Compiler.Value); case RunMode.Install: return(this._Installer.Value); default: return(default); } }) .ToProperty(this, nameof(this.ActivePane)); this.WhenAny(x => x.ActivePane) .ObserveOn(RxApp.TaskpoolScheduler) .WhereCastable <ViewModel, InstallerVM>() .Subscribe(vm => vm.ModListPath = source) .DisposeWith(this.CompositeDisposable); // Initialize work queue WorkQueue.Init( report_function: (id, msg, progress) => this._statusSubject.OnNext(new CPUStatus() { ID = id, Msg = msg, Progress = progress }), report_queue_size: (max, current) => this.SetQueueSize(max, current)); // Compile progress updates and populate ObservableCollection this._statusSubject .ObserveOn(RxApp.TaskpoolScheduler) .ToObservableChangeSet(x => x.ID) .Batch(TimeSpan.FromMilliseconds(250)) .EnsureUniqueChanges() .ObserveOn(RxApp.MainThreadScheduler) .Sort(SortExpressionComparer <CPUStatus> .Ascending(s => s.ID), SortOptimisations.ComparesImmutableValuesOnly) .Bind(this.StatusList) .Subscribe() .DisposeWith(this.CompositeDisposable); }