diff --git a/ui/src/app/components/install-wizard/complete/complete.component.ts b/ui/src/app/components/install-wizard/complete/complete.component.ts index def647c31..46f4f8a17 100644 --- a/ui/src/app/components/install-wizard/complete/complete.component.ts +++ b/ui/src/app/components/install-wizard/complete/complete.component.ts @@ -3,7 +3,7 @@ import { BehaviorSubject, from, Subject } from 'rxjs' import { takeUntil } from 'rxjs/operators' import { markAsLoadingDuring$ } from 'src/app/services/loader.service' import { capitalizeFirstLetter } from 'src/app/util/misc.util' -import { Colorable, Loadable } from '../loadable' +import { Loadable } from '../loadable' import { WizardAction } from '../wizard-types' @Component({ @@ -11,7 +11,7 @@ import { WizardAction } from '../wizard-types' templateUrl: './complete.component.html', styleUrls: ['../install-wizard.component.scss'], }) -export class CompleteComponent implements OnInit, Loadable, Colorable { +export class CompleteComponent implements OnInit, Loadable { @Input() params: { action: WizardAction verb: string //loader verb: '*stopping* ...' @@ -20,7 +20,13 @@ export class CompleteComponent implements OnInit, Loadable, Colorable { skipCompletionDialogue?: boolean } - @Input() finished: (info: { error?: Error, cancelled?: true, final?: true }) => Promise + @Input() transitions: { + cancel: () => void + next: () => void + final: () => void + error: (e: Error) => void + } + $loading$ = new BehaviorSubject(false) $color$ = new BehaviorSubject('medium') @@ -32,8 +38,8 @@ export class CompleteComponent implements OnInit, Loadable, Colorable { load () { markAsLoadingDuring$(this.$loading$, from(this.params.executeAction())).pipe(takeUntil(this.$cancel$)).subscribe( - { error: e => this.finished({ error: new Error(`${this.params.action} failed: ${e.message || e}`) }), - complete: () => this.params.skipCompletionDialogue && this.finished( { final: true} ), + { error: e => this.transitions.error(new Error(`${this.params.action} failed: ${e.message || e}`)), + complete: () => this.params.skipCompletionDialogue && this.transitions.final(), }, ) } diff --git a/ui/src/app/components/install-wizard/dependencies/dependencies.component.ts b/ui/src/app/components/install-wizard/dependencies/dependencies.component.ts index 6341f155f..ec9aa0815 100644 --- a/ui/src/app/components/install-wizard/dependencies/dependencies.component.ts +++ b/ui/src/app/components/install-wizard/dependencies/dependencies.component.ts @@ -5,7 +5,7 @@ import { AppStatus } from 'src/app/models/app-model' import { AppDependency, DependencyViolationSeverity, getViolationSeverity } from 'src/app/models/app-types' import { displayEmver } from 'src/app/pipes/emver.pipe' import { InformationPopoverComponent } from '../../information-popover/information-popover.component' -import { Colorable, Loadable } from '../loadable' +import { Loadable } from '../loadable' import { WizardAction } from '../wizard-types' @Component({ @@ -13,7 +13,7 @@ import { WizardAction } from '../wizard-types' templateUrl: './dependencies.component.html', styleUrls: ['../install-wizard.component.scss'], }) -export class DependenciesComponent implements OnInit, Loadable, Colorable { +export class DependenciesComponent implements OnInit, Loadable { @Input() params: { action: WizardAction, title: string, diff --git a/ui/src/app/components/install-wizard/dependents/dependents.component.ts b/ui/src/app/components/install-wizard/dependents/dependents.component.ts index 14fac4be8..cf549bf72 100644 --- a/ui/src/app/components/install-wizard/dependents/dependents.component.ts +++ b/ui/src/app/components/install-wizard/dependents/dependents.component.ts @@ -4,7 +4,7 @@ import { takeUntil, tap } from 'rxjs/operators' import { DependentBreakage } from 'src/app/models/app-types' import { markAsLoadingDuring$ } from 'src/app/services/loader.service' import { capitalizeFirstLetter } from 'src/app/util/misc.util' -import { Colorable, Loadable } from '../loadable' +import { Loadable } from '../loadable' import { WizardAction } from '../wizard-types' @Component({ @@ -12,7 +12,7 @@ import { WizardAction } from '../wizard-types' templateUrl: './dependents.component.html', styleUrls: ['../install-wizard.component.scss'], }) -export class DependentsComponent implements OnInit, Loadable, Colorable { +export class DependentsComponent implements OnInit, Loadable { @Input() params: { title: string, action: WizardAction, //Are you sure you want to *uninstall*..., @@ -20,7 +20,12 @@ export class DependentsComponent implements OnInit, Loadable, Colorable { fetchBreakages: () => Promise, skipConfirmationDialogue?: boolean } - @Input() finished: (info: { error?: Error, cancelled?: true, final?: true }) => Promise + @Input() transitions: { + cancel: () => void + next: () => void + final: () => void + error: (e: Error) => void + } dependentBreakages: DependentBreakage[] @@ -46,13 +51,13 @@ export class DependentsComponent implements OnInit, Loadable, Colorable { this.longMessage = `${capitalizeFirstLetter(this.params.verb)} ${this.params.title} will cause the following services to STOP running. Starting them again will require additional actions.` this.$color$.next('warning') } else if (this.params.skipConfirmationDialogue) { - this.finished({ }) + this.transitions.next() } else { this.longMessage = `No other services installed on your Embassy will be affected by this action.` this.$color$.next('success') } }, - error: (e: Error) => this.finished({ error: new Error(`Fetching dependent service information failed: ${e.message || e}`) }), + error: (e: Error) => this.transitions.error(new Error(`Fetching dependent service information failed: ${e.message || e}`)), }, ) } diff --git a/ui/src/app/components/install-wizard/install-wizard.component.html b/ui/src/app/components/install-wizard/install-wizard.component.html index 5995967dc..4b732da43 100644 --- a/ui/src/app/components/install-wizard/install-wizard.component.html +++ b/ui/src/app/components/install-wizard/install-wizard.component.html @@ -9,11 +9,12 @@ - - - - - + + + + + + @@ -34,20 +35,31 @@ - - + + + + {{t}} - + {{t}} - {{nextButton}} - {{finishButton}} + + + + {{next}} + + + + + {{finish}} + + - Dismiss + Dismiss diff --git a/ui/src/app/components/install-wizard/install-wizard.component.ts b/ui/src/app/components/install-wizard/install-wizard.component.ts index c824f5f26..680be84c6 100644 --- a/ui/src/app/components/install-wizard/install-wizard.component.ts +++ b/ui/src/app/components/install-wizard/install-wizard.component.ts @@ -1,15 +1,15 @@ import { Component, Input, NgZone, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core' import { IonContent, IonSlides, ModalController } from '@ionic/angular' -import { BehaviorSubject, combineLatest, Subscription } from 'rxjs' -import { map } from 'rxjs/operators' +import { BehaviorSubject, from, Observable, of } from 'rxjs' import { Cleanup } from 'src/app/util/cleanup' import { capitalizeFirstLetter, pauseFor } from 'src/app/util/misc.util' import { CompleteComponent } from './complete/complete.component' import { DependenciesComponent } from './dependencies/dependencies.component' import { DependentsComponent } from './dependents/dependents.component' import { NotesComponent } from './notes/notes.component' -import { Colorable, Loadable } from './loadable' +import { Loadable } from './loadable' import { WizardAction } from './wizard-types' +import { concatMap, switchMap } from 'rxjs/operators' @Component({ selector: 'install-wizard', @@ -18,36 +18,35 @@ import { WizardAction } from './wizard-types' }) export class InstallWizardComponent extends Cleanup implements OnInit { @Input() params: { - // defines the slideshow in the html + // defines each slide along with bottom bar slideDefinitions: SlideDefinition[] toolbar: TopbarParams } - // containers + // content container so we can scroll to top between slide transitions @ViewChild(IonContent) contentContainer: IonContent + // slide container gives us hook into transitioning slides @ViewChild(IonSlides) slideContainer: IonSlides - //don't use this, use slideComponents instead. + //a slide component gives us hook into a slide. Allows us to call load when slide comes into view @ViewChildren('components') - public slideComponentsQL: QueryList + slideComponentsQL: QueryList + get slideComponents (): Loadable[] { return this.slideComponentsQL.toArray() } - //don't use this, use currentSlide instead. - slideIndex = 0 - - get slideComponents (): (Loadable & Colorable)[] { - return this.slideComponentsQL.toArray() - } - - get currentSlide (): (Loadable & Colorable) { + private slideIndex = 0 + get currentSlide (): Loadable { return this.slideComponents[this.slideIndex] } - - get currentSlideDef (): SlideDefinition { - return this.params.slideDefinitions[this.slideIndex] + get currentSlideLoading$ (): Observable { + return this.$initializing$.pipe(switchMap( + i => i ? of(true) : this.currentSlide.$loading$, + )) + } + get currentBottomBar (): SlideDefinition['bottomBar'] { + return this.params.slideDefinitions[this.slideIndex].bottomBar } - $anythingLoading$: BehaviorSubject = new BehaviorSubject(true) - $currentColor$: BehaviorSubject = new BehaviorSubject('medium') + $initializing$ = new BehaviorSubject(true) $error$ = new BehaviorSubject(undefined) constructor (private readonly modalController: ModalController, private readonly zone: NgZone) { super() } @@ -60,26 +59,28 @@ export class InstallWizardComponent extends Cleanup implements OnInit { } ionViewDidEnter () { - this.cleanup( - combineLatest(this.slideComponents.map(component => component.$loading$)).pipe( - map(loadings => !loadings.every(p => !p)), - ).subscribe(this.$anythingLoading$), - combineLatest(this.slideComponents.map(component => component.$color$)).pipe( - map(colors => colors[this.slideIndex]), - ).subscribe(this.$currentColor$), - ) + this.$initializing$.next(false) } - finished = (info: { error?: Error, cancelled?: true, final?: true }) => { + // process bottom bar buttons + private transition = (info: { error?: Error, cancelled?: true, final?: true }) => { if (info.cancelled) this.currentSlide.$cancel$.next() if (info.final || info.cancelled) return this.modalController.dismiss(info) if (info.error) return this.$error$.next(capitalizeFirstLetter(info.error.message)) - this.slide() + this.moveToNextSlide() } - private async slide () { - if (this.slideComponents[this.slideIndex + 1] === undefined) { return this.finished({ final: true }) } + // bottom bar button callbacks + transitions = { + cancel: () => this.transition({ cancelled: true }), + next: () => this.transition({ }), + final: () => this.transition({ final: true }), + error: e => this.transition({ error: e }), + } + + private async moveToNextSlide () { + if (this.slideComponents[this.slideIndex + 1] === undefined) { return this.transition({ final: true }) } this.zone.run(async () => { this.slideComponents[this.slideIndex + 1].load() await pauseFor(50) @@ -92,33 +93,23 @@ export class InstallWizardComponent extends Cleanup implements OnInit { } } -export interface SlideCommon { - selector: string // component http selector - cancelButton: { - // indicates the existence of a cancel button, and whether to have text or an icon 'x' by default. - afterLoading?: { text?: string }, - whileLoading?: { text?: string } +export interface SlideDefinition { + slide: + { selector: 'dependencies', params: DependenciesComponent['params'] } | + { selector: 'dependents', params: DependentsComponent['params'] } | + { selector: 'complete', params: CompleteComponent['params'] } | + { selector: 'notes', params: NotesComponent['params'] } + bottomBar: { + cancel: { + // indicates the existence of a cancel button, and whether to have text or an icon 'x' by default. + afterLoading?: { text?: string }, + whileLoading?: { text?: string } + } + next?: string + finish?: string } - nextButton?: string, // existence and content of next button - finishButton?: string // existence and content of finish button } -export type SlideDefinition = SlideCommon & ( - { - selector: 'dependencies', - params: DependenciesComponent['params'] - } | { - selector: 'dependents', - params: DependentsComponent['params'] - } | { - selector: 'complete', - params: CompleteComponent['params'] - } | { - selector: 'notes', - params: NotesComponent['params'] - } -) - export type TopbarParams = { action: WizardAction, title: string, version: string } export async function wizardModal ( diff --git a/ui/src/app/components/install-wizard/loadable.ts b/ui/src/app/components/install-wizard/loadable.ts index f2f69d6b6..e4ecbfd77 100644 --- a/ui/src/app/components/install-wizard/loadable.ts +++ b/ui/src/app/components/install-wizard/loadable.ts @@ -1,11 +1,9 @@ import { BehaviorSubject, Subject } from 'rxjs' export interface Loadable { - load: () => void - $loading$: BehaviorSubject //will be true during load function - $cancel$: Subject //will cancel load function + load: (prevResult?: any) => void + result?: any // fill this variable on slide 1 to get passed into the load on slide 2. If this variable is falsey, it will skip the next slide. + $loading$: BehaviorSubject // will be true during load function + $cancel$: Subject // will cancel load function } -export interface Colorable { - $color$: BehaviorSubject -} diff --git a/ui/src/app/components/install-wizard/notes/notes.component.html b/ui/src/app/components/install-wizard/notes/notes.component.html index 7704f99b3..670177ad7 100644 --- a/ui/src/app/components/install-wizard/notes/notes.component.html +++ b/ui/src/app/components/install-wizard/notes/notes.component.html @@ -1,7 +1,7 @@
- + {{params.title}}
diff --git a/ui/src/app/components/install-wizard/notes/notes.component.ts b/ui/src/app/components/install-wizard/notes/notes.component.ts index d7200ccba..5fca514cd 100644 --- a/ui/src/app/components/install-wizard/notes/notes.component.ts +++ b/ui/src/app/components/install-wizard/notes/notes.component.ts @@ -1,27 +1,24 @@ import { Component, Input, OnInit } from '@angular/core' import { BehaviorSubject, Subject } from 'rxjs' -import { Colorable, Loadable } from '../loadable' -import { WizardAction } from '../wizard-types' +import { Loadable } from '../loadable' @Component({ selector: 'notes', templateUrl: './notes.component.html', styleUrls: ['../install-wizard.component.scss'], }) -export class NotesComponent implements OnInit, Loadable, Colorable { +export class NotesComponent implements OnInit, Loadable { @Input() params: { - action: WizardAction notes: string title: string titleColor: string } $loading$ = new BehaviorSubject(false) - $color$ = new BehaviorSubject('light') $cancel$ = new Subject() load () { } constructor () { } - ngOnInit () { this.$color$.next(this.params.titleColor) } + ngOnInit () { } } diff --git a/ui/src/app/components/install-wizard/prebaked-wizards.ts b/ui/src/app/components/install-wizard/prebaked-wizards.ts index 1b0a70094..4d9b45098 100644 --- a/ui/src/app/components/install-wizard/prebaked-wizards.ts +++ b/ui/src/app/components/install-wizard/prebaked-wizards.ts @@ -5,13 +5,14 @@ import { exists } from 'src/app/util/misc.util' import { AppDependency, DependentBreakage, AppInstalledPreview } from '../../models/app-types' import { ApiService } from '../../services/api/api.service' import { InstallWizardComponent, SlideDefinition, TopbarParams } from './install-wizard.component' +import { WizardAction } from './wizard-types' @Injectable({ providedIn: 'root' }) export class WizardBaker { constructor ( private readonly apiService: ApiService, private readonly updateService: OsUpdateService, - private readonly appModel: AppModel + private readonly appModel: AppModel, ) { } install (values: { @@ -28,17 +29,40 @@ export class WizardBaker { const toolbar: TopbarParams = { action, title, version } const slideDefinitions: SlideDefinition[] = [ - installAlert ? { selector: 'notes', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Next', params: { - action, notes: installAlert, title: 'Warning', titleColor: 'warning', - }} : undefined, - { selector: 'dependencies', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Install', params: { - action, title, version, serviceRequirements, - }}, - { selector: 'complete', finishButton: 'Dismiss', cancelButton: { whileLoading: { } }, params: { - action, verb: 'beginning installation for', title, executeAction: () => this.apiService.installApp(id, version).then(app => { - this.appModel.add({ ...app, status: AppStatus.INSTALLING }) - }), - }}, + installAlert ? { + slide: { + selector: 'notes', + params: { notes: installAlert, title: 'Warning', titleColor: 'warning' }, + }, + bottomBar: { + cancel: { afterLoading: { text: 'Cancel' } }, next: 'Next', + }, + } : undefined, + { + slide: { + selector: 'dependencies', + params: { action, title, version, serviceRequirements }, + }, + bottomBar: { + cancel: { afterLoading: { text: 'Cancel' } }, + next: 'Install', + }, + }, + { + slide: { + selector: 'complete', + params: { + action, + verb: 'beginning installation for', + title, + executeAction: () => this.apiService.installApp(id, version).then(app => { this.appModel.add({ ...app, status: AppStatus.INSTALLING })}), + }, + }, + bottomBar: { + cancel: { whileLoading: { } }, + finish: 'Dismiss', + }, + }, ] return { toolbar, slideDefinitions: slideDefinitions.filter(exists) } } @@ -57,20 +81,49 @@ export class WizardBaker { const toolbar: TopbarParams = { action, title, version } const slideDefinitions: SlideDefinition[] = [ - installAlert ? { selector: 'notes', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Next', params: { - action, notes: installAlert, title: 'Warning', titleColor: 'warning', - }} : undefined, - { selector: 'dependencies', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Update', params: { - action, title, version, serviceRequirements, - }}, - { selector: 'dependents', cancelButton: { whileLoading: { }, afterLoading: { text: 'Cancel' } }, nextButton: 'Update Anyways', params: { - skipConfirmationDialogue: true, action, verb: 'updating', title, fetchBreakages: () => this.apiService.installApp(id, version, true).then( ({ breakages }) => breakages ), - }}, - { selector: 'complete', finishButton: 'Dismiss', cancelButton: { whileLoading: { } }, params: { - action, verb: 'beginning update for', title, executeAction: () => this.apiService.installApp(id, version).then(app => { - this.appModel.update({ id: app.id, status: AppStatus.INSTALLING }) - }), - }}, + installAlert ? { + slide: { + selector: 'notes', + params: { notes: installAlert, title: 'Warning', titleColor: 'warning'}, + }, + bottomBar: { + cancel: { afterLoading: { text: 'Cancel' } }, + next: 'Next', + }, + } : undefined, + { slide: { + selector: 'dependencies', + params: { action, title, version, serviceRequirements }, + }, + bottomBar: { + cancel: { afterLoading: { text: 'Cancel' } }, + next: 'Update', + }, + }, + { slide: { + selector: 'dependents', + params: { + skipConfirmationDialogue: true, action, verb: 'updating', title, fetchBreakages: () => this.apiService.installApp(id, version, true).then( ({ breakages }) => breakages ), + }, + }, + bottomBar: { + cancel: { afterLoading: { text: 'Cancel' } }, + next: 'Update Anyways', + }, + }, + { slide: { + selector: 'complete', + params: { + action, verb: 'beginning update for', title, executeAction: () => this.apiService.installApp(id, version).then(app => { + this.appModel.update({ id: app.id, status: AppStatus.INSTALLING }) + }), + }, + }, + bottomBar: { + cancel: { whileLoading: { } }, + finish: 'Dismiss', + }, + }, ] return { toolbar, slideDefinitions: slideDefinitions.filter(exists) } } @@ -85,12 +138,25 @@ export class WizardBaker { const toolbar: TopbarParams = { action, title, version } const slideDefinitions: SlideDefinition[] = [ - { selector: 'notes', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Update OS', params: { - action, notes: releaseNotes || 'No release notes for this version', title: 'Release Notes', titleColor: 'dark', - }}, - { selector: 'complete', finishButton: 'Dismiss', cancelButton: { whileLoading: { } }, params: { - action, verb: 'beginning update for', title, executeAction: () => this.updateService.updateEmbassyOS(version), - }}, + { slide : { + selector: 'notes', + params: { notes: releaseNotes, title: 'Release Notes', titleColor: 'dark' }, + }, + bottomBar: { + cancel: { afterLoading: { text: 'Cancel' } }, next: 'Update OS', + }, + }, + { slide: { + selector: 'complete', + params: { + action, verb: 'beginning update for', title, executeAction: () => this.updateService.updateEmbassyOS(version), + }, + }, + bottomBar: { + cancel: { whileLoading: { }}, + finish: 'Dismiss', + }, + }, ] return { toolbar, slideDefinitions: slideDefinitions.filter(exists) } } @@ -109,20 +175,45 @@ export class WizardBaker { const toolbar: TopbarParams = { action, title, version } const slideDefinitions: SlideDefinition[] = [ - installAlert ? { selector: 'notes', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Next', params: { - action, notes: installAlert, title: 'Warning', titleColor: 'warning', - }} : undefined, - { selector: 'dependencies', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Downgrade', params: { - action, title, version, serviceRequirements, - }}, - { selector: 'dependents', cancelButton: { whileLoading: { }, afterLoading: { text: 'Cancel' } }, nextButton: 'Downgrade Anyways', params: { - skipConfirmationDialogue: true, action, verb: 'downgrading', title, fetchBreakages: () => this.apiService.installApp(id, version, true).then( ({ breakages }) => breakages ), - }}, - { selector: 'complete', finishButton: 'Dismiss', cancelButton: { whileLoading: { } }, params: { - action, verb: 'beginning downgrade for', title, executeAction: () => this.apiService.installApp(id, version).then(app => { - this.appModel.update({ id: app.id, status: AppStatus.INSTALLING }) - }), - }}, + installAlert ? { + slide: { + selector: 'notes', + params: { notes: installAlert, title: 'Warning', titleColor: 'warning' }, + }, + bottomBar: { cancel: { afterLoading: { text: 'Cancel' } }, next: 'Next' }, + } : undefined, + { slide: { + selector: 'dependencies', + params: { action, title, version, serviceRequirements }, + }, + bottomBar: { + cancel: { afterLoading: { text: 'Cancel' } }, + next: 'Downgrade', + }, + }, + { slide: { + selector: 'dependents', + params: { + skipConfirmationDialogue: true, action, verb: 'downgrading', title, fetchBreakages: () => this.apiService.installApp(id, version, true).then( ({ breakages }) => breakages ), + }, + }, + bottomBar: { + cancel: { whileLoading: { }, afterLoading: { text: 'Cancel' } }, next: 'Downgrade Anyways', + }, + }, + { slide: { + selector: 'complete', + params: { + action, verb: 'beginning downgrade for', title, executeAction: () => this.apiService.installApp(id, version).then(app => { + this.appModel.update({ id: app.id, status: AppStatus.INSTALLING }) + }), + }, + }, + bottomBar: { + cancel: { whileLoading: { } }, + finish: 'Dismiss', + }, + }, ] return { toolbar, slideDefinitions: slideDefinitions.filter(exists) } } @@ -136,19 +227,36 @@ export class WizardBaker { validate(title, exists, 'missing title') validate(version, exists, 'missing version') - const action = 'uninstall' + const action = 'uninstall' as WizardAction const toolbar: TopbarParams = { action, title, version } const slideDefinitions: SlideDefinition[] = [ - { selector: 'notes', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Continue', params: { - action, notes: uninstallAlert || defaultUninstallationWarning(title), title: 'Warning', titleColor: 'warning' }, + { slide: { + selector: 'notes', + params: { + notes: uninstallAlert || defaultUninstallationWarning(title), + title: 'Warning', + titleColor: 'warning', + }, + }, + bottomBar: { cancel: { afterLoading: { text: 'Cancel' } }, next: 'Continue' }, + }, + { slide: { + selector: 'dependents', + params: { + action, verb: 'uninstalling', title, fetchBreakages: () => this.apiService.uninstallApp(id, true).then( ({ breakages }) => breakages ), + }, + }, + bottomBar: { cancel: { whileLoading: { }, afterLoading: { text: 'Cancel' } }, next: 'Uninstall' }, + }, + { slide: { + selector: 'complete', + params: { + action, verb: 'uninstalling', title, executeAction: () => this.apiService.uninstallApp(id).then(() => this.appModel.delete(id)), + }, + }, + bottomBar: { finish: 'Dismiss', cancel: { whileLoading: { } } }, }, - { selector: 'dependents', cancelButton: { whileLoading: { }, afterLoading: { text: 'Cancel' } }, nextButton: 'Uninstall', params: { - action, verb: 'uninstalling', title, fetchBreakages: () => this.apiService.uninstallApp(id, true).then( ({ breakages }) => breakages ), - }}, - { selector: 'complete', finishButton: 'Dismiss', cancelButton: { whileLoading: { } }, params: { - action, verb: 'uninstalling', title, executeAction: () => this.apiService.uninstallApp(id).then(() => this.appModel.delete(id)), - }}, ] return { toolbar, slideDefinitions: slideDefinitions.filter(exists) } } @@ -166,9 +274,14 @@ export class WizardBaker { const toolbar: TopbarParams = { action, title, version } const slideDefinitions: SlideDefinition[] = [ - { selector: 'dependents', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Stop Anyways', params: { - action, verb: 'stopping', title, fetchBreakages: () => Promise.resolve(breakages), - }}, + { slide: { + selector: 'dependents', + params: { + action, verb: 'stopping', title, fetchBreakages: () => Promise.resolve(breakages), + }, + }, + bottomBar: { cancel: { afterLoading: { text: 'Cancel' } }, next: 'Stop Anyways' }, + }, ] return { toolbar, slideDefinitions } } @@ -182,9 +295,14 @@ export class WizardBaker { const toolbar: TopbarParams = { action, title, version } const slideDefinitions: SlideDefinition[] = [ - { selector: 'dependents', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Save Config Anyways', params: { - action, verb: 'saving config for', title, fetchBreakages: () => Promise.resolve(breakages), - }}, + { slide: { + selector: 'dependents', + params: { + action, verb: 'saving config for', title, fetchBreakages: () => Promise.resolve(breakages), + }, + }, + bottomBar: { cancel: { afterLoading: { text: 'Cancel' } }, next: 'Save Config Anyways' }, + }, ] return { toolbar, slideDefinitions } } diff --git a/ui/src/app/pages/apps-routes/app-available-show/app-available-show.page.ts b/ui/src/app/pages/apps-routes/app-available-show/app-available-show.page.ts index ad36eb014..227244a79 100644 --- a/ui/src/app/pages/apps-routes/app-available-show/app-available-show.page.ts +++ b/ui/src/app/pages/apps-routes/app-available-show/app-available-show.page.ts @@ -15,6 +15,7 @@ import { Cleanup } from 'src/app/util/cleanup' import { InformationPopoverComponent } from 'src/app/components/information-popover/information-popover.component' import { Emver } from 'src/app/services/emver.service' import { displayEmver } from 'src/app/pipes/emver.pipe' +import { pauseFor } from 'src/app/util/misc.util' @Component({ selector: 'app-available-show', @@ -160,6 +161,7 @@ export class AppAvailableShowPage extends Cleanup { }), ) if (cancelled) return + await pauseFor(250) this.navCtrl.back() } @@ -174,18 +176,16 @@ export class AppAvailableShowPage extends Cleanup { installAlert: app.installAlert, } - switch (action) { - case 'update': - return wizardModal( - this.modalCtrl, - this.wizardBaker.update(value), - ).then(({ cancelled }) => cancelled || this.navCtrl.back()) - case 'downgrade': - return wizardModal( - this.modalCtrl, - this.wizardBaker.downgrade(value), - ).then(({ cancelled }) => cancelled || this.navCtrl.back()) - } + const { cancelled } = await wizardModal( + this.modalCtrl, + action === 'update' ? + this.wizardBaker.update(value) : + this.wizardBaker.downgrade(value), + ) + + if (cancelled) return + await pauseFor(250) + this.navCtrl.back() } private fetchRecommendation (): Observable {