Files
start-os/frontend/projects/ui/src/app/components/install-wizard/install-wizard.component.ts
Alex Inkin 7c26b18c73 Feature/shared refactor (#1176)
* refactor: move most of the shared entities to @start8labs/shared library
2022-02-15 08:13:05 -07:00

160 lines
4.8 KiB
TypeScript

import {
Component,
Input,
NgZone,
QueryList,
ViewChild,
ViewChildren,
} from '@angular/core'
import { IonContent, IonSlides, ModalController } from '@ionic/angular'
import { capitalizeFirstLetter, pauseFor } from '@start9labs/shared'
import { CompleteComponent } from './complete/complete.component'
import { DependentsComponent } from './dependents/dependents.component'
import { AlertComponent } from './alert/alert.component'
import { NotesComponent } from './notes/notes.component'
import { Loadable } from './loadable'
import { WizardAction } from './wizard-types'
@Component({
selector: 'install-wizard',
templateUrl: './install-wizard.component.html',
styleUrls: ['./install-wizard.component.scss'],
})
export class InstallWizardComponent {
transitioning = false
@Input() params: {
// defines each slide along with bottom bar
slideDefinitions: SlideDefinition[]
toolbar: TopbarParams
}
// content container so we can scroll to top between slide transitions
@ViewChild(IonContent) contentContainer: IonContent
// slide container gives us hook into ion-slide, allowing for slide transitions
@ViewChild(IonSlides) slideContainer: IonSlides
//a slide component gives us hook into a slide. Allows us to call load when slide comes into view
@ViewChildren('components')
slideComponentsQL: QueryList<Loadable>
get slideComponents(): Loadable[] {
return this.slideComponentsQL.toArray()
}
private slideIndex = 0
get currentSlide(): Loadable {
return this.slideComponents[this.slideIndex]
}
get currentBottomBar(): SlideDefinition['bottomBar'] {
return this.params.slideDefinitions[this.slideIndex].bottomBar
}
initializing = true
error = ''
constructor(
private readonly modalController: ModalController,
private readonly zone: NgZone,
) {}
ngAfterViewInit() {
this.currentSlide.load()
this.slideContainer.update()
this.slideContainer.lockSwipes(true)
}
ionViewDidEnter() {
this.initializing = false
}
// process bottom bar buttons
private transition = (
info:
| { next: any }
| { error: Error }
| { cancelled: true }
| { final: true },
) => {
const i = info as {
next?: any
error?: Error
cancelled?: true
final?: true
}
if (i.cancelled) this.currentSlide.cancel$.next()
if (i.final || i.cancelled) return this.modalController.dismiss(i)
if (i.error) return (this.error = capitalizeFirstLetter(i.error.message))
this.moveToNextSlide(i.next)
}
// bottom bar button callbacks. Pass this into components if they need to trigger slide transitions independent of the bottom bar clicks
transitions = {
next: (prevResult: any) =>
this.transition({ next: prevResult || this.currentSlide.result }),
cancel: () => this.transition({ cancelled: true }),
final: () => this.transition({ final: true }),
error: (e: Error) => this.transition({ error: e }),
}
private async moveToNextSlide(prevResult?: any) {
if (this.slideComponents[this.slideIndex + 1] === undefined) {
return this.transition({ final: true })
}
this.zone.run(async () => {
this.slideComponents[this.slideIndex + 1].load(prevResult)
await pauseFor(50) // give the load ^ opportunity to propogate into slide before sliding it into view
this.slideIndex += 1
await this.slideContainer.lockSwipes(false)
await this.contentContainer.scrollToTop()
await this.slideContainer.slideNext(500)
await this.slideContainer.lockSwipes(true)
})
}
async callTransition(transition: Function) {
this.transitioning = true
await transition()
this.transitioning = false
}
}
export interface SlideDefinition {
slide:
| { selector: 'dependents'; params: DependentsComponent['params'] }
| { selector: 'complete'; params: CompleteComponent['params'] }
| { selector: 'alert'; params: AlertComponent['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 }
}
// indicates the existence of next or finish buttons (should only have one)
next?: string
finish?: string
}
}
export type TopbarParams = {
action: WizardAction
title: string
version: string
}
export async function wizardModal(
modalController: ModalController,
params: InstallWizardComponent['params'],
): Promise<{ cancelled?: true; final?: true; modal: HTMLIonModalElement }> {
const modal = await modalController.create({
backdropDismiss: false,
cssClass: 'wizard-modal',
component: InstallWizardComponent,
componentProps: { params },
})
await modal.present()
return modal.onWillDismiss().then(({ data }) => ({ ...data, modal }))
}