|
|
@@ -18,7 +18,12 @@ function div(className: string | undefined, text?: string): HTMLDivElement {
|
|
|
export class BootPage {
|
|
|
private readonly root: HTMLDivElement
|
|
|
private readonly card: HTMLDivElement
|
|
|
+ private readonly wordmark: HTMLDivElement
|
|
|
+ private readonly spinner: HTMLDivElement
|
|
|
+ private readonly hint: HTMLDivElement
|
|
|
private readonly states = new Map<string, LoaderEntryState>()
|
|
|
+ private readonly active = new Set<string>()
|
|
|
+ private total = 0
|
|
|
private failure: string | undefined
|
|
|
|
|
|
/**
|
|
|
@@ -29,10 +34,23 @@ export class BootPage {
|
|
|
this.root = div(css.boot)
|
|
|
this.root.dataset.dshBoot = ''
|
|
|
this.card = div(css.card)
|
|
|
- this.card.append(div(css.wordmark, 'HARNESS'))
|
|
|
+ this.wordmark = div(css.wordmark, 'HARNESS')
|
|
|
+ this.spinner = div(css.spinner)
|
|
|
+ this.spinner.dataset.dshBootSpinner = ''
|
|
|
+ this.hint = div(css.hint, 'Loading plugins…')
|
|
|
+ this.card.append(this.wordmark, this.spinner, this.hint)
|
|
|
this.root.append(this.card)
|
|
|
container.append(this.root)
|
|
|
- this.render()
|
|
|
+ this.updateProgress()
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Set the number of loader entries represented by the progress arc.
|
|
|
+ * @param total - Complete boot roster size.
|
|
|
+ */
|
|
|
+ setTotal(total: number): void {
|
|
|
+ this.total = total
|
|
|
+ this.updateProgress()
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -42,6 +60,8 @@ export class BootPage {
|
|
|
*/
|
|
|
setState(id: string, state: LoaderEntryState): void {
|
|
|
this.states.set(id, state)
|
|
|
+ if (state === 'active') this.active.add(id)
|
|
|
+ this.updateProgress()
|
|
|
this.render()
|
|
|
}
|
|
|
|
|
|
@@ -61,16 +81,23 @@ export class BootPage {
|
|
|
|
|
|
/** Redraw the state-dependent content below the wordmark. */
|
|
|
private render(): void {
|
|
|
- while (this.card.childNodes.length > 1) this.card.lastChild?.remove()
|
|
|
const failed = [...this.states].filter(([, state]) => state === 'failed').map(([id]) => id)
|
|
|
if (this.failure === undefined && failed.length === 0) {
|
|
|
- this.card.append(div(css.spinner), div(css.hint, 'Loading plugins…'))
|
|
|
+ if (this.spinner.parentElement !== this.card) {
|
|
|
+ this.card.replaceChildren(this.wordmark, this.spinner, this.hint)
|
|
|
+ }
|
|
|
return
|
|
|
}
|
|
|
const report = div(css.failed)
|
|
|
report.append(div(css.failedTitle, 'Failed to load plugins'))
|
|
|
for (const id of failed) report.append(div(css.failedItem, id))
|
|
|
if (this.failure !== undefined) report.append(div(css.failedItem, this.failure))
|
|
|
- this.card.append(report)
|
|
|
+ this.card.replaceChildren(this.wordmark, report)
|
|
|
+ }
|
|
|
+
|
|
|
+ /** Grow the rotating arc monotonically as loader entries activate. */
|
|
|
+ private updateProgress(): void {
|
|
|
+ const ratio = this.total === 0 ? 0 : Math.min(this.active.size / this.total, 1)
|
|
|
+ this.spinner.style.setProperty('--dsh-boot-arc', `${String(Math.round(72 + ratio * 216))}deg`)
|
|
|
}
|
|
|
}
|