mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
160 lines
4.8 KiB
TypeScript
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 }))
|
|
}
|