public CliPatcherInitVM(ProfileVM profile) : base(profile) { Patcher = new CliPatcherVM(profile); _CanCompleteConfiguration = Patcher.WhenAnyValue(x => x.PathToExecutable.ErrorState) .Cast <ErrorResponse, ErrorResponse>() .ToGuiProperty(this, nameof(CanCompleteConfiguration), ErrorResponse.Success); }
public PatcherVM(ProfileVM parent, PatcherSettings?settings) { DisplayedObject = this; InternalID = Interlocked.Increment(ref NextID); ErrorVM = new ErrorVM("Error", backAction: () => { DisplayedObject = this; }); Profile = parent; _IsSelected = this.WhenAnyValue(x => x.Profile.SelectedPatcher) .Select(x => x == this) .ToGuiProperty(this, nameof(IsSelected)); // Set to settings IsOn = settings?.On ?? false; Nickname = settings?.Nickname ?? string.Empty; DeleteCommand = ReactiveCommand.Create(() => { parent.Config.MainVM.TargetConfirmation = new ConfirmationActionVM( "Confirm", $"Are you sure you want to delete {DisplayName}?", Delete); }); this.WhenAnyValue(x => x.IsSelected) .DistinctUntilChanged() .Where(x => x) .Subscribe(_ => { DisplayedObject = this; }) .DisposeWith(this); this.WhenAnyValue(x => x.State.RunnableState) .Subscribe(state => { if (state.Failed) { ErrorVM.String = state.Reason; } else { ErrorVM.String = null; } }) .DisposeWith(this); }
public SolutionPatcherInitVM(ProfileVM profile) : base(profile) { MVM = profile.Config.MainVM; OpenCodeAfter = profile.Config.MainVM.Settings.OpenIdeAfterCreating; New.ParentDirPath.TargetPath = profile.Config.MainVM.Settings.MainRepositoryFolder; var initializer = this.WhenAnyValue(x => x.SelectedIndex) .Select <int, ASolutionInitializer>(x => { return(((SolutionInitType)x) switch { SolutionInitType.ExistingSolution => ExistingSolution, SolutionInitType.New => New, SolutionInitType.ExistingProject => ExistingProject, _ => throw new NotImplementedException(), }); })
public CliPatcherVM(ProfileVM parent, CliPatcherSettings?settings = null) : base(parent, settings) { CopyInSettings(settings); _DisplayName = this.WhenAnyValue( x => x.Nickname, x => x.PathToExecutable.TargetPath, (Nickname, PathToExecutable) => (Nickname, PathToExecutable)) .Select(x => { if (string.IsNullOrWhiteSpace(x.Nickname)) { try { return(Path.GetFileNameWithoutExtension(x.PathToExecutable)); } catch (Exception) { return("<Naming Error>"); } } else { return(x.Nickname); } }) .ToGuiProperty <string>(this, nameof(DisplayName)); _State = this.WhenAnyValue(x => x.PathToExecutable.ErrorState) .Select(e => { return(new ConfigurationState() { IsHaltingError = !e.Succeeded, RunnableState = e }); }) .ToGuiProperty <ConfigurationState>(this, nameof(State), new ConfigurationState(ErrorResponse.Fail("Evaluating")) { IsHaltingError = false }); }
public NewProfileVM(ConfigurationVM config, Action <ProfileVM> postRun) { ReleaseOptions.AddRange(EnumExt.GetValues <GameRelease>()); this.WhenAnyValue(x => x.SelectedGame) .Subscribe(game => { if (game == null) { return; } var profile = new ProfileVM(config, game.Value) { Nickname = Nickname }; config.Profiles.AddOrUpdate(profile); postRun(profile); }) .DisposeWith(this); }
public CodeSnippetPatcherVM(ProfileVM parent, CodeSnippetPatcherSettings?settings = null) : base(parent, settings) { CopyInSettings(settings); _DisplayName = this.WhenAnyValue(x => x.Nickname) .Select(x => { if (string.IsNullOrWhiteSpace(x)) { return("<No Name>"); } else { return(x); } }) .ToGuiProperty <string>(this, nameof(DisplayName), string.Empty); IObservable <(MemoryStream?AssemblyStream, EmitResult?CompileResults, Exception?Exception)> compileResults = Observable.Merge( // Anytime code changes, wipe and mark as "compiling" this.WhenAnyValue(x => x.Code) .Select(_ => (default(MemoryStream?), default(EmitResult?), default(Exception?))), // Start actual compiling task, this.WhenAnyValue(x => x.Code) // Throttle input .Throttle(TimeSpan.FromMilliseconds(350), RxApp.MainThreadScheduler) // Stick on background thread .ObserveOn(RxApp.TaskpoolScheduler) .Select(code => { CancellationTokenSource cancel = new CancellationTokenSource(); try { var emit = CodeSnippetPatcherRun.Compile( parent.Release, ID, code, cancel.Token, out var assembly); return(emit.Success ? assembly : default, emit, default(Exception?));
public PatcherVM(ProfileVM parent, PatcherSettings?settings) { InternalID = Interlocked.Increment(ref NextID); Profile = parent; _IsSelected = this.WhenAnyValue(x => x.Profile.Config.SelectedPatcher) .Select(x => x == this) .ToGuiProperty(this, nameof(IsSelected)); // Set to settings IsOn = settings?.On ?? false; Nickname = settings?.Nickname ?? string.Empty; DeleteCommand = ReactiveCommand.Create(() => { parent.Config.MainVM.ActiveConfirmation = new ConfirmationActionVM( "Confirm", $"Are you sure you want to delete {DisplayName}?", Delete); }); }
public SolutionPatcherVM(ProfileVM parent, SolutionPatcherSettings?settings = null) : base(parent, settings) { CopyInSettings(settings); SolutionPath.Filters.Add(new CommonFileDialogFilter("Solution", ".sln")); SelectedProjectPath.Filters.Add(new CommonFileDialogFilter("Project", ".csproj")); _DisplayName = Observable.CombineLatest( this.WhenAnyValue(x => x.Nickname), this.WhenAnyValue(x => x.SelectedProjectPath.TargetPath) .StartWith(settings?.ProjectSubpath ?? string.Empty), (nickname, path) => { if (!string.IsNullOrWhiteSpace(nickname)) { return(nickname); } try { var name = Path.GetFileName(Path.GetDirectoryName(path)); if (string.IsNullOrWhiteSpace(name)) { return(string.Empty); } return(name); } catch (Exception) { return(string.Empty); } }) .ToProperty(this, nameof(DisplayName), Nickname); AvailableProjects = SolutionPatcherConfigLogic.AvailableProject( this.WhenAnyValue(x => x.SolutionPath.TargetPath)) .ObserveOnGui() .ToObservableCollection(this); var projPath = SolutionPatcherConfigLogic.ProjectPath( solutionPath: this.WhenAnyValue(x => x.SolutionPath.TargetPath), projectSubpath: this.WhenAnyValue(x => x.ProjectSubpath)); projPath .Subscribe(p => SelectedProjectPath.TargetPath = p) .DisposeWith(this); _State = Observable.CombineLatest( this.WhenAnyValue(x => x.SolutionPath.ErrorState), this.WhenAnyValue(x => x.SelectedProjectPath.ErrorState), this.WhenAnyValue(x => x.Profile.Config.MainVM) .Select(x => x.DotNetSdkInstalled) .Switch(), (sln, proj, dotnet) => { if (sln.Failed) { return(new ConfigurationState(sln)); } if (!dotnet.Acceptable) { return(new ConfigurationState(ErrorResponse.Fail("No dotnet SDK installed"))); } return(new ConfigurationState(proj)); }) .ToGuiProperty <ConfigurationState>(this, nameof(State), new ConfigurationState(ErrorResponse.Fail("Evaluating")) { IsHaltingError = false }); OpenSolutionCommand = ReactiveCommand.Create( canExecute: this.WhenAnyValue(x => x.SolutionPath.InError) .Select(x => !x), execute: () => { try { Process.Start(new ProcessStartInfo(SolutionPath.TargetPath) { UseShellExecute = true, }); } catch (Exception ex) { Log.Logger.Error(ex, $"Error opening solution: {SolutionPath.TargetPath}"); } }); var metaPath = this.WhenAnyValue(x => x.SelectedProjectPath.TargetPath) .Select(projPath => { try { return(Path.Combine(Path.GetDirectoryName(projPath) !, Constants.MetaFileName)); } catch (Exception) { return(string.Empty); } }) .Replay(1) .RefCount(); // Set up meta file sync metaPath .Select(path => { return(Noggog.ObservableExt.WatchFile(path) .StartWith(Unit.Default) .Throttle(TimeSpan.FromMilliseconds(500), RxApp.MainThreadScheduler) .Select(_ => { if (!File.Exists(path)) { return default; } try { return JsonConvert.DeserializeObject <PatcherCustomization>( File.ReadAllText(path), Execution.Constants.JsonSettings); } catch (Exception ex) { Logger.Error(ex, "Error reading in meta"); } return default(PatcherCustomization?); })); }) .Switch() .DistinctUntilChanged() .ObserveOnGui() .Subscribe(info => { if (info == null) { return; } if (info.Nickname != null) { this.Nickname = info.Nickname; } this.LongDescription = info.LongDescription ?? string.Empty; this.ShortDescription = info.OneLineDescription ?? string.Empty; this.Visibility = info.Visibility; this.Versioning = info.PreferredAutoVersioning; this.RequiredMods.SetTo(info.RequiredMods .SelectWhere(x => TryGet <ModKey> .Create(ModKey.TryFromNameAndExtension(x, out var modKey), modKey)) .Select(m => new ModKeyItemViewModel(m))); }) .DisposeWith(this); Observable.CombineLatest( this.WhenAnyValue(x => x.DisplayName), this.WhenAnyValue(x => x.ShortDescription), this.WhenAnyValue(x => x.LongDescription), this.WhenAnyValue(x => x.Visibility), this.WhenAnyValue(x => x.Versioning), this.RequiredMods .ToObservableChangeSet() .Transform(x => x.ModKey) .AddKey(x => x) .Sort(ModKey.Alphabetical, SortOptimisations.ComparesImmutableValuesOnly, resetThreshold: 0) .QueryWhenChanged() .Select(x => x.Items) .StartWith(Enumerable.Empty <ModKey>()), metaPath, (nickname, shortDesc, desc, visibility, versioning, reqMods, meta) => (nickname, shortDesc, desc, visibility, versioning, reqMods: reqMods.Select(x => x.FileName).OrderBy(x => x).ToArray(), meta)) .DistinctUntilChanged() .Throttle(TimeSpan.FromMilliseconds(200), RxApp.MainThreadScheduler) .Skip(1) .Subscribe(x => { try { if (string.IsNullOrWhiteSpace(x.meta)) { return; } File.WriteAllText(x.meta, JsonConvert.SerializeObject( new PatcherCustomization() { OneLineDescription = x.shortDesc, LongDescription = x.desc, Visibility = x.visibility, Nickname = x.nickname, PreferredAutoVersioning = x.versioning, RequiredMods = x.reqMods }, Formatting.Indented, Execution.Constants.JsonSettings)); } catch (Exception ex) { Logger.Error(ex, "Error writing out meta"); } }) .DisposeWith(this); ReloadAutogeneratedSettingsCommand = ReactiveCommand.Create(() => { }); PatcherSettings = new PatcherSettingsVM( Logger, this, projPath .Merge(ReloadAutogeneratedSettingsCommand.EndingExecution() .WithLatestFrom(projPath, (_, p) => p)) .Select(p => (GetResponse <string> .Succeed(p), default(string?))), needBuild: true) .DisposeWith(this); }
public GitPatcherInitVM(ProfileVM profile) : base(profile) { Patcher = new GitPatcherVM(profile); _CanCompleteConfiguration = this.WhenAnyValue(x => x.Patcher.RepoClonesValid) .Select(x => ErrorResponse.Create(x)) .ToGuiProperty(this, nameof(CanCompleteConfiguration), ErrorResponse.Success); PatcherRepos = Observable.Return(Unit.Default) .ObserveOn(RxApp.TaskpoolScheduler) .SelectTask(async _ => { try { var localRepoPath = await GitUtility.CheckOrCloneRepo( GetResponse <string> .Succeed("https://github.com/Mutagen-Modding/Synthesis.Registry"), Paths.RegistryFolder, Log.Logger.Error, CancellationToken.None); if (localRepoPath.Failed) { Error = localRepoPath; return(Observable.Empty <IChangeSet <PatcherStoreListingVM> >()); } using var repo = new Repository(localRepoPath.Value.Local); var master = repo.Branches.Where(b => b.IsCurrentRepositoryHead).FirstOrDefault(); if (master == null) { Error = ErrorResponse.Fail("Could not find master branch"); Log.Logger.Error(Error.Reason); return(Observable.Empty <IChangeSet <PatcherStoreListingVM> >()); } repo.Reset(ResetMode.Hard, repo.Branches[$"{master.RemoteName}/{master.FriendlyName}"].Tip); var listingPath = Path.Combine(repo.Info.WorkingDirectory, Constants.AutomaticListingFileName); if (!File.Exists(listingPath)) { Error = ErrorResponse.Fail("Could not locate listing file"); Log.Logger.Error(Error.Reason); return(Observable.Empty <IChangeSet <PatcherStoreListingVM> >()); } var settings = new JsonSerializerOptions(); settings.Converters.Add(new JsonStringEnumConverter()); var customization = JsonSerializer.Deserialize <MutagenPatchersListing>(File.ReadAllText(listingPath), settings) !; return(customization.Repositories .NotNull() .SelectMany(repo => { var repoVM = new RepositoryStoreListingVM(repo); return repo.Patchers .Select(p => { return new PatcherStoreListingVM(this, p, repoVM); }); }) .AsObservableChangeSet()); } catch (Exception ex) { Log.Logger.Error(ex, "Error downloading patcher listing"); Error = ErrorResponse.Fail(ex); } return(Observable.Empty <IChangeSet <PatcherStoreListingVM> >()); }) .Switch() .Sort(Comparer <PatcherStoreListingVM> .Create((x, y) => x.Name.CompareTo(y.Name))) .Filter(this.WhenAnyValue(x => x.ShowAll) .DistinctUntilChanged() .Select(show => new Func <PatcherStoreListingVM, bool>( (p) => { if (p.Raw.Customization?.Visibility is VisibilityOptions.Visible) { return(true); } else if (p.Raw.Customization?.Visibility is VisibilityOptions.IncludeButHide) { return(show); } else if (p.Raw.Customization?.Visibility is VisibilityOptions.Exclude) { return(false); // just in case. } else { return(true); } }))) .Filter(this.WhenAnyValue(x => x.Search) .Debounce(TimeSpan.FromMilliseconds(350), RxApp.MainThreadScheduler) .Select(x => x.Trim()) .DistinctUntilChanged() .Select(search => { if (string.IsNullOrWhiteSpace(search)) { return(new Func <PatcherStoreListingVM, bool>(_ => true)); } return(new Func <PatcherStoreListingVM, bool>( (p) => { if (p.Name.Contains(search, StringComparison.OrdinalIgnoreCase)) { return true; } if (p.Raw.Customization?.OneLineDescription?.Contains(search, StringComparison.OrdinalIgnoreCase) ?? false) { return true; } return false; })); })) .ToObservableCollection(this); OpenPopulationInfoCommand = ReactiveCommand.Create(() => Utility.NavigateToPath(Constants.ListingRepositoryAddress)); ClearSearchCommand = ReactiveCommand.Create(() => Search = string.Empty); }
public PatcherInitVM(ProfileVM profile) { Profile = profile; }
public PatchersRunVM(ConfigurationVM parent, ProfileVM profile) { Config = parent; RunningProfile = profile; Patchers.AddOrUpdate(RunningProfile.Patchers.Items .Where(x => x.IsOn) .Select(p => p.ToRunner(this))); PatchersDisplay = Patchers.Connect() .ToObservableCollection(this); if (parent.SelectedPatcher != null && Patchers.TryGetValue(parent.SelectedPatcher.InternalID, out var run)) { SelectedPatcher = run; } BackCommand = ReactiveCommand.Create(() => { parent.SelectedPatcher = SelectedPatcher?.Config; parent.MainVM.ActivePanel = parent; }, canExecute: this.WhenAnyValue(x => x.Running) .Select(running => !running)); CancelCommand = ReactiveCommand.CreateFromTask( execute: Cancel, canExecute: this.WhenAnyValue(x => x.Running)); _reporter.Overall .ObserveOnGui() .Subscribe(ex => { Log.Logger.Error(ex, "Error while running patcher pipeline"); ResultError = ex; }) .DisposeWith(this); _reporter.PrepProblem .Select(data => (data, type: "prepping")) .Merge(_reporter.RunProblem .Select(data => (data, type: "running"))) .ObserveOnGui() .Subscribe(i => { var vm = Patchers.Get(i.data.Key); vm.State = GetResponse <RunState> .Fail(RunState.Error, i.data.Error); SelectedPatcher = vm; Log.Logger .ForContext(nameof(PatcherVM.DisplayName), i.data.Run.Name) .Error(i.data.Error, $"Error while prepping {i.type}"); }) .DisposeWith(this); _reporter.Starting .ObserveOnGui() .Subscribe(i => { var vm = Patchers.Get(i.Key); vm.State = GetResponse <RunState> .Succeed(RunState.Started); Log.Logger .ForContext(nameof(PatcherVM.DisplayName), i.Run.Name) .Information($"Starting"); }) .DisposeWith(this); _reporter.RunSuccessful .ObserveOnGui() .Subscribe(i => { var vm = Patchers.Get(i.Key); vm.State = GetResponse <RunState> .Succeed(RunState.Finished); Log.Logger .ForContext(nameof(PatcherVM.DisplayName), i.Run.Name) .Information("Finished {RunTime}", vm.RunTime); }) .DisposeWith(this); _reporter.Output .Subscribe(s => { Log.Logger .ForContextIfNotNull(nameof(PatcherVM.DisplayName), s.Run?.Name) .Information(s.String); }) .DisposeWith(this); _reporter.Error .Subscribe(s => { Log.Logger .ForContextIfNotNull(nameof(PatcherVM.DisplayName), s.Run?.Name) .Error(s.String); }) .DisposeWith(this); // Clear selected patcher on showing error this.ShowOverallErrorCommand.StartingExecution() .Subscribe(_ => this.SelectedPatcher = null) .DisposeWith(this); _DetailDisplay = Observable.Merge( this.WhenAnyValue(x => x.SelectedPatcher) .Select(i => i as object), this.ShowOverallErrorCommand.EndingExecution() .Select(_ => ResultError == null ? null : new OverallErrorVM(ResultError))) .ToGuiProperty(this, nameof(DetailDisplay)); }
public SolutionPatcherVM(ProfileVM parent, SolutionPatcherSettings?settings = null) : base(parent, settings) { CopyInSettings(settings); SolutionPath.Filters.Add(new CommonFileDialogFilter("Solution", ".sln")); SelectedProjectPath.Filters.Add(new CommonFileDialogFilter("Project", ".csproj")); _DisplayName = Observable.CombineLatest( this.WhenAnyValue(x => x.Nickname), this.WhenAnyValue(x => x.SelectedProjectPath.TargetPath) .StartWith(settings?.ProjectSubpath ?? string.Empty), (nickname, path) => { if (!string.IsNullOrWhiteSpace(nickname)) { return(nickname); } try { var name = Path.GetFileName(Path.GetDirectoryName(path)); if (string.IsNullOrWhiteSpace(name)) { return(string.Empty); } return(name); } catch (Exception) { return(string.Empty); } }) .ToProperty(this, nameof(DisplayName), Nickname); AvailableProjects = SolutionPatcherConfigLogic.AvailableProject( this.WhenAnyValue(x => x.SolutionPath.TargetPath)) .ObserveOnGui() .ToObservableCollection(this); var projPath = SolutionPatcherConfigLogic.ProjectPath( solutionPath: this.WhenAnyValue(x => x.SolutionPath.TargetPath), projectSubpath: this.WhenAnyValue(x => x.ProjectSubpath)); projPath .Subscribe(p => SelectedProjectPath.TargetPath = p) .DisposeWith(this); _State = Observable.CombineLatest( this.WhenAnyValue(x => x.SolutionPath.ErrorState), this.WhenAnyValue(x => x.SelectedProjectPath.ErrorState), this.WhenAnyValue(x => x.Profile.Config.MainVM) .Select(x => x.DotNetSdkInstalled) .Switch(), (sln, proj, dotnet) => { if (sln.Failed) { return(new ConfigurationState(sln)); } if (dotnet == null) { return(new ConfigurationState(ErrorResponse.Fail("No dotnet SDK installed"))); } return(new ConfigurationState(proj)); }) .ToGuiProperty <ConfigurationState>(this, nameof(State), new ConfigurationState(ErrorResponse.Fail("Evaluating")) { IsHaltingError = false }); OpenSolutionCommand = ReactiveCommand.Create( canExecute: this.WhenAnyValue(x => x.SolutionPath.InError) .Select(x => !x), execute: () => { try { Process.Start(new ProcessStartInfo(SolutionPath.TargetPath) { UseShellExecute = true, }); } catch (Exception ex) { Log.Logger.Error(ex, $"Error opening solution: {SolutionPath.TargetPath}"); } }); var metaPath = this.WhenAnyValue(x => x.SelectedProjectPath.TargetPath) .Select(projPath => { try { return(Path.Combine(Path.GetDirectoryName(projPath) !, Constants.MetaFileName)); } catch (Exception) { return(string.Empty); } }) .Replay(1) .RefCount(); // Set up meta file sync metaPath .Select(path => { return(Noggog.ObservableExt.WatchFile(path) .StartWith(Unit.Default) .Select(_ => { try { return JsonConvert.DeserializeObject <PatcherCustomization>( File.ReadAllText(path), Execution.Constants.JsonSettings); } catch (Exception ex) { Logger.Error(ex, "Error reading in meta"); } return default(PatcherCustomization?); })); }) .Switch() .DistinctUntilChanged() .ObserveOnGui() .Subscribe(info => { if (info == null) { return; } if (info.Nickname != null) { this.Nickname = info.Nickname; } this.LongDescription = info.LongDescription ?? string.Empty; this.ShortDescription = info.OneLineDescription ?? string.Empty; this.Visibility = info.Visibility; this.Versioning = info.PreferredAutoVersioning; }) .DisposeWith(this); Observable.CombineLatest( this.WhenAnyValue(x => x.DisplayName), this.WhenAnyValue(x => x.ShortDescription), this.WhenAnyValue(x => x.LongDescription), this.WhenAnyValue(x => x.Visibility), this.WhenAnyValue(x => x.Versioning), metaPath, (nickname, shortDesc, desc, visibility, versioning, meta) => (nickname, shortDesc, desc, visibility, versioning, meta)) .DistinctUntilChanged() .Throttle(TimeSpan.FromMilliseconds(200), RxApp.MainThreadScheduler) .Skip(1) .Subscribe(x => { try { if (string.IsNullOrWhiteSpace(x.meta)) { return; } File.WriteAllText(x.meta, JsonConvert.SerializeObject( new PatcherCustomization() { OneLineDescription = x.shortDesc, LongDescription = x.desc, Visibility = x.visibility, Nickname = x.nickname, PreferredAutoVersioning = x.versioning }, Formatting.Indented, Execution.Constants.JsonSettings)); } catch (Exception ex) { Logger.Error(ex, "Error writing out meta"); } }) .DisposeWith(this); }
public GitPatcherInitVM(ProfileVM profile) : base(profile) { Patcher = new GitPatcherVM(profile); this.CompositeDisposable.Add(Patcher); _CanCompleteConfiguration = this.WhenAnyValue(x => x.Patcher.State) .Select(x => x.RunnableState) .ToGuiProperty(this, nameof(CanCompleteConfiguration), ErrorResponse.Success); PatcherRepos = Observable.Return(Unit.Default) .ObserveOn(RxApp.TaskpoolScheduler) .SelectTask(async _ => { try { var gitHubClient = new GitHubClient(new ProductHeaderValue("Synthesis")); gitHubClient.Credentials = new Credentials("9b58542a2ca303d7ced129cc404191f8eea519f7"); var content = await gitHubClient.Repository.Content.GetAllContents("Noggog", "Synthesis.Registry", Constants.AutomaticListingFileName); if (content.Count != 1) { return(Observable.Empty <IChangeSet <PatcherStoreListingVM> >()); } var customization = JsonSerializer.Deserialize <MutagenPatchersListing>(content[0].Content); return(customization.Repositories .NotNull() .SelectMany(repo => { var repoVM = new RepositoryStoreListingVM(repo); return repo.Patchers .Select(p => { return new PatcherStoreListingVM(this, p, repoVM); }); }) .AsObservableChangeSet()); } catch (Exception ex) { Log.Logger.Error(ex, "Error downloading patcher listing"); Error = ErrorResponse.Fail(ex); } return(Observable.Empty <IChangeSet <PatcherStoreListingVM> >()); }) .Switch() .Sort(Comparer <PatcherStoreListingVM> .Create((x, y) => x.Name.CompareTo(y.Name))) .Filter(this.WhenAnyValue(x => x.ShowAll) .DistinctUntilChanged() .Select(show => new Func <PatcherStoreListingVM, bool>( (p) => { if (p.Raw.Customization?.Visibility is VisibilityOptions.Visible) { return(true); } else if (p.Raw.Customization?.Visibility is VisibilityOptions.IncludeButHide) { return(show); } else if (p.Raw.Customization?.Visibility is VisibilityOptions.Exclude) { return(false); // just in case. } else { return(true); } }))) .Filter(this.WhenAnyValue(x => x.Search) .Debounce(TimeSpan.FromMilliseconds(350), RxApp.MainThreadScheduler) .Select(x => x.Trim()) .DistinctUntilChanged() .Select(search => { if (string.IsNullOrWhiteSpace(search)) { return(new Func <PatcherStoreListingVM, bool>(_ => true)); } return(new Func <PatcherStoreListingVM, bool>( (p) => { if (p.Name.Contains(search, StringComparison.OrdinalIgnoreCase)) { return true; } if (p.Raw.Customization?.OneLineDescription?.Contains(search, StringComparison.OrdinalIgnoreCase) ?? false) { return true; } return false; })); })) .ToObservableCollection(this); OpenPopulationInfoCommand = ReactiveCommand.Create(() => Utility.NavigateToPath(Constants.ListingRepositoryAddress)); ClearSearchCommand = ReactiveCommand.Create(() => Search = string.Empty); }