Пример #1
0
        public VortexInstallerVM(InstallerVM installerVM)
        {
            Parent = installerVM;

            _TargetGame = installerVM.WhenAny(x => x.ModList.SourceModList.GameType)
                          .ToProperty(this, nameof(TargetGame));

            CanInstall = Observable.CombineLatest(
                this.WhenAny(x => x.TargetGame)
                .Select(game => VortexCompiler.IsActiveVortexGame(game)),
                installerVM.WhenAny(x => x.ModListLocation.InError),
                resultSelector: (isVortexGame, modListErr) => isVortexGame && !modListErr);
        }
Пример #2
0
        public MO2InstallerVM(InstallerVM installerVM)
        {
            Parent = installerVM;

            Location = new FilePickerVM()
            {
                ExistCheckOption = FilePickerVM.CheckOptions.Off,
                PathType         = FilePickerVM.PathTypeOptions.Folder,
                PromptTitle      = "Select Installation Directory",
            };
            DownloadLocation = new FilePickerVM()
            {
                ExistCheckOption = FilePickerVM.CheckOptions.Off,
                PathType         = FilePickerVM.PathTypeOptions.Folder,
                PromptTitle      = "Select a location for MO2 downloads",
            };
            DownloadLocation.AdditionalError = this.WhenAny(x => x.DownloadLocation.TargetPath)
                                               .Select(x => Utils.IsDirectoryPathValid(x));
            Location.AdditionalError = Observable.CombineLatest(
                this.WhenAny(x => x.Location.TargetPath),
                this.WhenAny(x => x.DownloadLocation.TargetPath),
                resultSelector: (target, download) => (target, download))
                                       .ObserveOn(RxApp.TaskpoolScheduler)
                                       .Select(i => MO2Installer.CheckValidInstallPath(i.target, i.download))
                                       .ObserveOnGuiThread();

            CanInstall = Observable.CombineLatest(
                this.WhenAny(x => x.Location.InError),
                this.WhenAny(x => x.DownloadLocation.InError),
                installerVM.WhenAny(x => x.ModListLocation.InError),
                resultSelector: (loc, modlist, download) =>
            {
                return(!loc && !download && !modlist);
            });

            // Have Installation location updates modify the downloads location if empty
            this.WhenAny(x => x.Location.TargetPath)
            .Skip(1)     // Don't do it initially
            .Subscribe(installPath =>
            {
                if (string.IsNullOrWhiteSpace(DownloadLocation.TargetPath))
                {
                    DownloadLocation.TargetPath = Path.Combine(installPath, "downloads");
                }
            })
            .DisposeWith(CompositeDisposable);

            // Load settings
            _CurrentSettings = installerVM.WhenAny(x => x.ModListLocation.TargetPath)
                               .Select(path => path == null ? null : installerVM.MWVM.Settings.Installer.Mo2ModlistSettings.TryCreate(path))
                               .ToGuiProperty(this, nameof(CurrentSettings));
            this.WhenAny(x => x.CurrentSettings)
            .Pairwise()
            .Subscribe(settingsPair =>
            {
                SaveSettings(settingsPair.Previous);
                if (settingsPair.Current == null)
                {
                    return;
                }
                Location.TargetPath         = settingsPair.Current.InstallationLocation;
                DownloadLocation.TargetPath = settingsPair.Current.DownloadLocation;
                AutomaticallyOverwrite      = settingsPair.Current.AutomaticallyOverrideExistingInstall;
            })
            .DisposeWith(CompositeDisposable);
            installerVM.MWVM.Settings.SaveSignal
            .Subscribe(_ => SaveSettings(CurrentSettings))
            .DisposeWith(CompositeDisposable);

            // Hook onto user interventions, and intercept MO2 specific ones for customization
            this.WhenAny(x => x.ActiveInstallation)
            .Select(x => x?.LogMessages ?? Observable.Empty <IStatusMessage>())
            .Switch()
            .Subscribe(x =>
            {
                switch (x)
                {
                case ConfirmUpdateOfExistingInstall c:
                    if (AutomaticallyOverwrite)
                    {
                        c.Confirm();
                    }
                    break;

                default:
                    break;
                }
            })
            .DisposeWith(CompositeDisposable);
        }
Пример #3
0
        public SlideShow(InstallerVM appState)
        {
            Installer = appState;

            // Wire target slideshow index
            var intervalSeconds = 10;
            // Compile all the sources that trigger a slideshow update, any of which trigger a counter update
            var selectedIndex = Observable.Merge(
                // If user requests one manually
                SlideShowNextItemCommand.StartingExecution(),
                // If the natural timer fires
                Observable.Merge(
                    // Start with an initial timer
                    Observable.Return(Observable.Interval(TimeSpan.FromSeconds(intervalSeconds))),
                    // but reset timer if user requests one
                    SlideShowNextItemCommand.StartingExecution()
                    .Select(_ => Observable.Interval(TimeSpan.FromSeconds(intervalSeconds))))
                // When a new timer comes in, swap to it
                .Switch()
                .Unit())
                                // When filter switch enabled, fire an initial signal
                                .StartWith(Unit.Default)
                                // Only subscribe to slideshow triggers if enabled and installing
                                .FlowSwitch(
                Observable.CombineLatest(
                    this.WhenAny(x => x.Enable),
                    this.WhenAny(x => x.Installer.Installing),
                    resultSelector: (enabled, installing) => enabled && installing))
                                // Block spam
                                .Debounce(TimeSpan.FromMilliseconds(250), RxApp.MainThreadScheduler)
                                .Scan(
                seed: 0,
                accumulator: (i, _) => i + 1)
                                .Publish()
                                .RefCount();

            // Dynamic list changeset of mod VMs to display
            var modVMs = this.WhenAny(x => x.Installer.ModList)
                         // Whenever modlist changes, grab the list of its slides
                         .Select(modList =>
            {
                if (modList?.SourceModList?.Archives == null)
                {
                    return(Observable.Empty <NexusDownloader.State>()
                           .ToObservableChangeSet(x => x.ModID));
                }
                return(modList.SourceModList.Archives
                       .Select(m => m.State)
                       .OfType <NexusDownloader.State>()
                       // Shuffle it
                       .Shuffle(_random)
                       .AsObservableChangeSet(x => x.ModID));
            })
                         // Switch to the new list after every ModList change
                         .Switch()
                         .Transform(nexus => new ModVM(nexus))
                         .DisposeMany()
                         // Filter out any NSFW slides if we don't want them
                         .AutoRefreshOnObservable(slide => this.WhenAny(x => x.ShowNSFW))
                         .Filter(slide => !slide.IsNSFW || ShowNSFW)
                         .RefCount();

            // Find target mod to display by combining dynamic list with currently desired index
            _targetMod = Observable.CombineLatest(
                modVMs.QueryWhenChanged(),
                selectedIndex,
                resultSelector: (query, selected) =>
            {
                var index = selected % (query.Count == 0 ? 1 : query.Count);
                return(query.Items.ElementAtOrDefault(index));
            })
                         .StartWith(default(ModVM))
                         .ToGuiProperty(this, nameof(TargetMod));

            // Mark interest and materialize image of target mod
            _image = this.WhenAny(x => x.TargetMod)
                     // We want to Switch here, not SelectMany, as we want to hotswap to newest target without waiting on old ones
                     .Select(x => x?.ImageObservable ?? Observable.Return(default(BitmapImage)))
                     .Switch()
                     .ToGuiProperty(this, nameof(Image));

            VisitNexusSiteCommand = ReactiveCommand.Create(
                execute: () =>
            {
                Process.Start(TargetMod.ModURL);
                return(Unit.Default);
            },
                canExecute: this.WhenAny(x => x.TargetMod.ModURL)
                .Select(x => x?.StartsWith("https://") ?? false)
                .ObserveOnGuiThread());

            // Preload upcoming images
            var list = Observable.CombineLatest(
                modVMs.QueryWhenChanged(),
                selectedIndex,
                resultSelector: (query, selected) =>
            {
                // Retrieve the mods that should be preloaded
                var index        = selected % (query.Count == 0 ? 1 : query.Count);
                var amountToTake = Math.Min(query.Count - index, PreloadAmount);
                return(query.Items.Skip(index).Take(amountToTake).ToObservable());
            })
                       .Select(i => i.ToObservableChangeSet())
                       .Switch()
                       .Transform(mod => mod.ImageObservable.Subscribe())
                       .DisposeMany()
                       .AsObservableList();
        }
Пример #4
0
        public SlideShow(InstallerVM appState)
        {
            this.Installer = appState;

            // Wire target slideshow index
            var intervalSeconds = 10;
            // Compile all the sources that trigger a slideshow update, any of which trigger a counter update
            var selectedIndex = Observable.Merge(
                // If user requests one manually
                this.SlideShowNextItemCommand.StartingExecution(),
                // If the natural timer fires
                Observable.Merge(
                    // Start with an initial timer
                    Observable.Return(Observable.Interval(TimeSpan.FromSeconds(intervalSeconds))),
                    // but reset timer if user requests one
                    this.SlideShowNextItemCommand.StartingExecution()
                    .Select(_ => Observable.Interval(TimeSpan.FromSeconds(intervalSeconds))))
                // When a new timer comes in, swap to it
                .Switch()
                .Unit())
                                // When filter switch enabled, fire an initial signal
                                .StartWith(Unit.Default)
                                // Only subscribe to slideshow triggers if enabled and installing
                                .FilterSwitch(
                Observable.CombineLatest(
                    this.WhenAny(x => x.Enable),
                    this.WhenAny(x => x.Installer.Installing),
                    resultSelector: (enabled, installing) => enabled && installing))
                                // Block spam
                                .Debounce(TimeSpan.FromMilliseconds(250))
                                .Scan(
                seed: 0,
                accumulator: (i, _) => i + 1)
                                .Publish()
                                .RefCount();

            // Dynamic list changeset of mod VMs to display
            var modVMs = this.WhenAny(x => x.Installer.ModList)
                         // Whenever modlist changes, grab the list of its slides
                         .Select(modList =>
            {
                if (modList == null)
                {
                    return(Observable.Empty <ModVM>()
                           .ToObservableChangeSet(x => x.ModID));
                }
                return(modList.SourceModList.Archives
                       .Select(m => m.State)
                       .OfType <NexusDownloader.State>()
                       .Select(nexus => new ModVM(nexus))
                       // Shuffle it
                       .Shuffle(this._random)
                       .AsObservableChangeSet(x => x.ModID));
            })
                         // Switch to the new list after every modlist change
                         .Switch()
                         // Filter out any NSFW slides if we don't want them
                         .AutoRefreshOnObservable(slide => this.WhenAny(x => x.ShowNSFW))
                         .Filter(slide => !slide.IsNSFW || this.ShowNSFW)
                         .RefCount();

            // Find target mod to display by combining dynamic list with currently desired index
            this._TargetMod = Observable.CombineLatest(
                modVMs.QueryWhenChanged(),
                selectedIndex,
                resultSelector: (query, selected) => query.Items.ElementAtOrDefault(selected % query.Count))
                              .StartWith(default(ModVM))
                              .ObserveOn(RxApp.MainThreadScheduler)
                              .ToProperty(this, nameof(this.TargetMod));

            // Mark interest and materialize image of target mod
            this._Image = this.WhenAny(x => x.TargetMod)
                          // We want to Switch here, not SelectMany, as we want to hotswap to newest target without waiting on old ones
                          .Select(x => x?.ImageObservable ?? Observable.Return(default(BitmapImage)))
                          .Switch()
                          .ToProperty(this, nameof(this.Image));

            this.VisitNexusSiteCommand = ReactiveCommand.Create(
                execute: () => Process.Start(this.TargetMod.ModURL),
                canExecute: this.WhenAny(x => x.TargetMod.ModURL)
                .Select(x => x?.StartsWith("https://") ?? false)
                .ObserveOnGuiThread());

            // ToDo
            // Can maybe add "preload" systems to prep upcoming images
            // This would entail subscribing to modVMs, narrowing it down to Top(X) or Page() somehow.
            // The result would not be used anywhere, just simply expressing interest in those mods'
            // images will implicitly cache them
            //
            // Page would be really clever to use, but it's not exactly right as its "window" won't follow the current index,
            // so at the boundary of a page, the next image won't be cached.  Need like a Page() /w an offset parameter, or something.
        }