ui: adds install/uninstall warnings

This commit is contained in:
Aaron Greenspan
2021-01-13 17:11:12 -07:00
committed by Aiden McClelland
parent 071bd159ab
commit 238ede33b9
12 changed files with 57 additions and 75 deletions

View File

@@ -38,9 +38,12 @@ export class DependenciesComponent implements OnInit, Loadable, Colorable {
label: string label: string
$color$ = new BehaviorSubject('medium') $color$ = new BehaviorSubject('medium')
constructor (private readonly popoverController: PopoverController) { } constructor (private readonly popoverController: PopoverController) {
console.log('dependencies')
}
load () { load () {
console.log('loading dependencies')
this.$color$.next(this.$color$.getValue()) this.$color$.next(this.$color$.getValue())
} }

View File

@@ -1,17 +1,12 @@
<div *ngIf="!($loading$ | async) && !params.skipCompletionDialogue" class="slide-content"> <div class="slide-content">
<div style="margin-top: 25px;"> <div style="margin-top: 25px;">
<div style="margin: 15px; display: flex; justify-content: center; align-items: center;"> <div style="margin: 15px; display: flex; justify-content: center; align-items: center;">
<ion-label [color]="$color$ | async" style="font-size: xx-large; font-weight: bold;"> <ion-label [color]="$color$ | async" style="font-size: xx-large; font-weight: bold;">
{{successText}} Warning
</ion-label> </ion-label>
</div> </div>
<div class="long-message"> <div class="long-message">
{{summary}} {{params.developerNotes}}
</div> </div>
</div> </div>
</div> </div>
<div *ngIf="$loading$ | async" class="center-spinner">
<ion-spinner color="warning" name="lines"></ion-spinner>
<ion-label class="long-message">{{label}}</ion-label>
</div>

View File

@@ -1,8 +1,5 @@
import { Component, Input, OnInit } from '@angular/core' import { Component, Input, OnInit } from '@angular/core'
import { BehaviorSubject, from, Subject } from 'rxjs' import { BehaviorSubject, 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 { Colorable, Loadable } from '../loadable'
import { WizardAction } from '../wizard-types' import { WizardAction } from '../wizard-types'
@@ -18,50 +15,13 @@ export class DeveloperNotesComponent implements OnInit, Loadable, Colorable {
} }
$loading$ = new BehaviorSubject(false) $loading$ = new BehaviorSubject(false)
$color$ = new BehaviorSubject('medium') $color$ = new BehaviorSubject('warning')
$cancel$ = new Subject<void>() $cancel$ = new Subject<void>()
load () {} load () { }
constructor () { } constructor () { }
ngOnInit () { ngOnInit () {
switch (this.params.action) { console.log('Developer Notes', this.params)
case 'install':
this.summary = `Installation of ${this.params.title} is now in progress. You will receive a notification when the installation has completed.`
this.label = `${capitalizeFirstLetter(this.params.verb)} ${this.params.title}...`
this.$color$.next('primary')
this.successText = 'In Progress'
break
case 'downgrade':
this.summary = `Downgrade for ${this.params.title} is now in progress. You will receive a notification when the downgrade has completed.`
this.label = `${capitalizeFirstLetter(this.params.verb)} ${this.params.title}...`
this.$color$.next('primary')
this.successText = 'In Progress'
break
case 'update':
this.summary = `Update for ${this.params.title} is now in progress. You will receive a notification when the update has completed.`
this.label = `${capitalizeFirstLetter(this.params.verb)} ${this.params.title}...`
this.$color$.next('primary')
this.successText = 'In Progress'
break
case 'uninstall':
this.summary = `${capitalizeFirstLetter(this.params.title)} has been successfully uninstalled.`
this.label = `${capitalizeFirstLetter(this.params.verb)} ${this.params.title}...`
this.$color$.next('success')
this.successText = 'Success'
break
case 'stop':
this.summary = `${capitalizeFirstLetter(this.params.title)} has been successfully stopped.`
this.label = `${capitalizeFirstLetter(this.params.verb)} ${this.params.title}...`
this.$color$.next('success')
this.successText = 'Success'
break
case 'configure':
this.summary = `New config for ${this.params.title} has been successfully saved.`
this.label = `${capitalizeFirstLetter(this.params.verb)} ${this.params.title}...`
this.$color$.next('success')
this.successText = 'Success'
break
}
} }
} }

View File

@@ -10,8 +10,8 @@
<ion-content> <ion-content>
<ion-slides *ngIf="!($error$ | async)" id="slide-show" style="--bullet-background: white" pager="false"> <ion-slides *ngIf="!($error$ | async)" id="slide-show" style="--bullet-background: white" pager="false">
<ion-slide *ngFor="let slide of params.slideDefinitions"> <ion-slide *ngFor="let slide of params.slideDefinitions">
<developer-notes #components *ngIf="slide.selector === 'developer=notes'" [params]="slide.params"></dependencies>
<dependencies #components *ngIf="slide.selector === 'dependencies'" [params]="slide.params"></dependencies> <dependencies #components *ngIf="slide.selector === 'dependencies'" [params]="slide.params"></dependencies>
<developer-notes #components *ngIf="slide.selector === 'developer-notes'" [params]="slide.params"></developer-notes>
<dependents #components *ngIf="slide.selector === 'dependents'" [params]="slide.params" [finished]="finished"></dependents> <dependents #components *ngIf="slide.selector === 'dependents'" [params]="slide.params" [finished]="finished"></dependents>
<complete #components *ngIf="slide.selector === 'complete'" [params]="slide.params" [finished]="finished"></complete> <complete #components *ngIf="slide.selector === 'complete'" [params]="slide.params" [finished]="finished"></complete>
</ion-slide> </ion-slide>

View File

@@ -81,10 +81,15 @@ export class InstallWizardComponent extends Cleanup implements OnInit {
private async slide () { private async slide () {
if (this.slideComponents[this.slideIndex + 1] === undefined) { return this.finished({ final: true }) } if (this.slideComponents[this.slideIndex + 1] === undefined) { return this.finished({ final: true }) }
this.slideIndex += 1 this.slideIndex += 1
await this.slideContainer.lockSwipes(false)
await Promise.all([this.contentContainer.scrollToTop(), this.slideContainer.slideNext()])
await this.slideContainer.lockSwipes(true)
this.currentSlide.load() this.currentSlide.load()
await this.slideContainer.lockSwipes(false)
await Promise.all([
this.contentContainer.scrollToTop(),
this.slideContainer.slideNext(500),
])
await this.slideContainer.lockSwipes(true)
this.slideContainer.update()
// this.currentSlide.load()
} }
} }

View File

@@ -9,9 +9,9 @@ export class WizardBaker {
constructor (private readonly apiService: ApiService, private readonly appModel: AppModel) { } constructor (private readonly apiService: ApiService, private readonly appModel: AppModel) { }
install (values: { install (values: {
id: string, title: string, version: string, serviceRequirements: AppDependency[], developerNotes?: string id: string, title: string, version: string, serviceRequirements: AppDependency[], installWarning?: string
}): InstallWizardComponent['params'] { }): InstallWizardComponent['params'] {
const { id, title, version, serviceRequirements, developerNotes } = values const { id, title, version, serviceRequirements, installWarning } = values
validate(id, exists, 'missing id') validate(id, exists, 'missing id')
validate(title, exists, 'missing title') validate(title, exists, 'missing title')
@@ -22,9 +22,9 @@ export class WizardBaker {
const toolbar: TopbarParams = { action, title, version } const toolbar: TopbarParams = { action, title, version }
const slideDefinitions: SlideDefinition[] = [ const slideDefinitions: SlideDefinition[] = [
{ selector: 'developer-notes', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Next', params: { installWarning ? { selector: 'developer-notes', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Next', params: {
action, title, version, serviceRequirements, action, developerNotes: installWarning,
}}, }} : undefined,
{ selector: 'dependencies', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Install', params: { { selector: 'dependencies', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Install', params: {
action, title, version, serviceRequirements, action, title, version, serviceRequirements,
}}, }},
@@ -34,13 +34,13 @@ export class WizardBaker {
}), }),
}}, }},
] ]
return { toolbar, slideDefinitions } return { toolbar, slideDefinitions: slideDefinitions.filter(exists) }
} }
update (values: { update (values: {
id: string, title: string, version: string, serviceRequirements: AppDependency[] id: string, title: string, version: string, serviceRequirements: AppDependency[], installWarning?: string
}): InstallWizardComponent['params'] { }): InstallWizardComponent['params'] {
const { id, title, version, serviceRequirements } = values const { id, title, version, serviceRequirements, installWarning } = values
validate(id, exists, 'missing id') validate(id, exists, 'missing id')
validate(title, exists, 'missing title') validate(title, exists, 'missing title')
@@ -51,6 +51,9 @@ export class WizardBaker {
const toolbar: TopbarParams = { action, title, version } const toolbar: TopbarParams = { action, title, version }
const slideDefinitions: SlideDefinition[] = [ const slideDefinitions: SlideDefinition[] = [
installWarning ? { selector: 'developer-notes', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Next', params: {
action, developerNotes: installWarning,
}} : undefined,
{ selector: 'dependencies', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Update', params: { { selector: 'dependencies', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Update', params: {
action, title, version, serviceRequirements, action, title, version, serviceRequirements,
}}, }},
@@ -63,13 +66,13 @@ export class WizardBaker {
}), }),
}}, }},
] ]
return { toolbar, slideDefinitions } return { toolbar, slideDefinitions: slideDefinitions.filter(exists) }
} }
downgrade (values: { downgrade (values: {
id: string, title: string, version: string, serviceRequirements: AppDependency[] id: string, title: string, version: string, serviceRequirements: AppDependency[], installWarning?: string
}): InstallWizardComponent['params'] { }): InstallWizardComponent['params'] {
const { id, title, version, serviceRequirements } = values const { id, title, version, serviceRequirements, installWarning } = values
validate(id, exists, 'missing id') validate(id, exists, 'missing id')
validate(title, exists, 'missing title') validate(title, exists, 'missing title')
@@ -80,6 +83,9 @@ export class WizardBaker {
const toolbar: TopbarParams = { action, title, version } const toolbar: TopbarParams = { action, title, version }
const slideDefinitions: SlideDefinition[] = [ const slideDefinitions: SlideDefinition[] = [
installWarning ? { selector: 'developer-notes', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Next', params: {
action, developerNotes: installWarning,
}} : undefined,
{ selector: 'dependencies', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Downgrade', params: { { selector: 'dependencies', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Downgrade', params: {
action, title, version, serviceRequirements, action, title, version, serviceRequirements,
}}, }},
@@ -92,13 +98,13 @@ export class WizardBaker {
}), }),
}}, }},
] ]
return { toolbar, slideDefinitions } return { toolbar, slideDefinitions: slideDefinitions.filter(exists) }
} }
uninstall (values: { uninstall (values: {
id: string, title: string, version: string id: string, title: string, version: string, uninstallWarning?: string
}): InstallWizardComponent['params'] { }): InstallWizardComponent['params'] {
const { id, title, version } = values const { id, title, version, uninstallWarning } = values
validate(id, exists, 'missing id') validate(id, exists, 'missing id')
validate(title, exists, 'missing title') validate(title, exists, 'missing title')
@@ -108,6 +114,9 @@ export class WizardBaker {
const toolbar: TopbarParams = { action, title, version } const toolbar: TopbarParams = { action, title, version }
const slideDefinitions: SlideDefinition[] = [ const slideDefinitions: SlideDefinition[] = [
uninstallWarning ? { selector: 'developer-notes', cancelButton: { afterLoading: { text: 'Cancel' } }, nextButton: 'Next', params: {
action, developerNotes: uninstallWarning,
}} : undefined,
{ selector: 'dependents', cancelButton: { whileLoading: { }, afterLoading: { text: 'Cancel' } }, nextButton: 'Uninstall', params: { { selector: 'dependents', cancelButton: { whileLoading: { }, afterLoading: { text: 'Cancel' } }, nextButton: 'Uninstall', params: {
action, verb: 'uninstalling', title, fetchBreakages: () => this.apiService.uninstallApp(id, true).then( ({ breakages }) => breakages ), action, verb: 'uninstalling', title, fetchBreakages: () => this.apiService.uninstallApp(id, true).then( ({ breakages }) => breakages ),
}}, }},
@@ -115,7 +124,7 @@ export class WizardBaker {
action, verb: 'uninstalling', title, executeAction: () => this.apiService.uninstallApp(id).then(() => this.appModel.delete(id)), action, verb: 'uninstalling', title, executeAction: () => this.apiService.uninstallApp(id).then(() => this.appModel.delete(id)),
}}, }},
] ]
return { toolbar, slideDefinitions } return { toolbar, slideDefinitions: slideDefinitions.filter(exists) }
} }
stop (values: { stop (values: {

View File

@@ -29,6 +29,7 @@ export interface AppAvailableVersionSpecificInfo {
releaseNotes: string releaseNotes: string
serviceRequirements: AppDependency[] serviceRequirements: AppDependency[]
versionViewing: string versionViewing: string
installWarning?: string
} }
// installed // installed
@@ -43,6 +44,7 @@ export interface AppInstalledFull extends AppInstalledPreview {
lastBackup: string | null lastBackup: string | null
configuredRequirements: AppDependency[] | null // null if not yet configured configuredRequirements: AppDependency[] | null // null if not yet configured
hasFetchedFull: boolean hasFetchedFull: boolean
uninstallWarning?: string
} }
// dependencies // dependencies

View File

@@ -61,6 +61,8 @@ export class AppAvailableShowPage extends Cleanup {
this.appId = this.route.snapshot.paramMap.get('appId') as string this.appId = this.route.snapshot.paramMap.get('appId') as string
this.cleanup( this.cleanup(
// new version always includes dependencies, but not vice versa
this.$newVersionLoading$.subscribe(this.$dependenciesLoading$),
markAsLoadingDuring$(this.$loading$, markAsLoadingDuring$(this.$loading$,
from(this.apiService.getAvailableApp(this.appId)).pipe( from(this.apiService.getAvailableApp(this.appId)).pipe(
tap(app => this.$app$ = initPropertySubject(app)), tap(app => this.$app$ = initPropertySubject(app)),
@@ -133,7 +135,7 @@ export class AppAvailableShowPage extends Cleanup {
const previousVersion = this.$app$.versionViewing.getValue() const previousVersion = this.$app$.versionViewing.getValue()
this.$app$.versionViewing.next(version) this.$app$.versionViewing.next(version)
markAsLoadingDuring$( markAsLoadingDuring$(
this.$newVersionLoading$, this.syncVersionSpecificInfo(`=${version}`) this.$newVersionLoading$, this.syncVersionSpecificInfo(`=${version}`),
) )
.subscribe({ .subscribe({
error: e => { error: e => {
@@ -158,6 +160,7 @@ export class AppAvailableShowPage extends Cleanup {
title: app.title, title: app.title,
version: app.versionViewing, version: app.versionViewing,
serviceRequirements: app.serviceRequirements, serviceRequirements: app.serviceRequirements,
installWarning: app.installWarning,
}), }),
) )
if (cancelled) return if (cancelled) return
@@ -172,6 +175,7 @@ export class AppAvailableShowPage extends Cleanup {
title: app.title, title: app.title,
version: app.versionViewing, version: app.versionViewing,
serviceRequirements: app.serviceRequirements, serviceRequirements: app.serviceRequirements,
installWarning: app.installWarning,
} }
switch (action) { switch (action) {

View File

@@ -267,6 +267,7 @@ export class AppInstalledShowPage extends Cleanup {
id: app.id, id: app.id,
title: app.title, title: app.title,
version: app.versionInstalled, version: app.versionInstalled,
uninstallWarning: app.uninstallWarning,
}), }),
) )

View File

@@ -73,6 +73,7 @@ export class MockApiService extends ApiService {
} }
async getAvailableApp (appId: string): Promise<AppAvailableFull> { async getAvailableApp (appId: string): Promise<AppAvailableFull> {
console.log('getAvilableApp', appId)
// throw new Error('Some horrible horrible error message gosh its awful') // throw new Error('Some horrible horrible error message gosh its awful')
return mockGetAvailableApp(appId) return mockGetAvailableApp(appId)
.then(res => { .then(res => {

View File

@@ -92,6 +92,7 @@ export const cupsI: AppInstalledFull = {
instructions: 'some instructions', instructions: 'some instructions',
lastBackup: new Date().toISOString(), lastBackup: new Date().toISOString(),
ui: true, ui: true,
uninstallWarning: 'This is A GREAT APP man, I just don\'t know',
configuredRequirements: [ configuredRequirements: [
toServiceRequirement(lightningI, toServiceRequirement(lightningI,
{ {
@@ -201,6 +202,7 @@ export const thunderA: AppAvailableFull = {
descriptionLong: 'Thunder is an innovative payment network and new kind of money. Thunder utilizes a robust p2p network to garner decentralized consensus.', descriptionLong: 'Thunder is an innovative payment network and new kind of money. Thunder utilizes a robust p2p network to garner decentralized consensus.',
versions: ['0.8.0', '0.8.1', '1.0.0', '1.0.1'], versions: ['0.8.0', '0.8.1', '1.0.0', '1.0.1'],
versionViewing: '1.0.1', versionViewing: '1.0.1',
installWarning: 'Oooooh you really might want to think twice about installing this...',
serviceRequirements: [ serviceRequirements: [
toServiceRequirement(bitcoinA, { toServiceRequirement(bitcoinA, {
optional: null, optional: null,
@@ -275,7 +277,7 @@ export const bitwardenA: AppAvailableFull = {
export const mockApiAppAvailableFull: { [appId: string]: AppAvailableFull; } = { export const mockApiAppAvailableFull: { [appId: string]: AppAvailableFull; } = {
bitcoind: bitcoinA, bitcoind: bitcoinA,
lightning: lightningA, 'c-lightning': lightningA,
btcPay: btcPayA, btcPay: btcPayA,
thunder: thunderA, thunder: thunderA,
cups: cupsA, cups: cupsA,

View File

@@ -1,4 +1,4 @@
import { Observable, from, interval, race, OperatorFunction, Observer, BehaviorSubject } from 'rxjs' import { Observable, from, interval, race, OperatorFunction, Observer, BehaviorSubject, Subject } from 'rxjs'
import { take, map, switchMap, delay, tap } from 'rxjs/operators' import { take, map, switchMap, delay, tap } from 'rxjs/operators'
export function fromAsync$<S, T> (async: (s: S) => Promise<T>, s: S): Observable<T> export function fromAsync$<S, T> (async: (s: S) => Promise<T>, s: S): Observable<T>