pages235/src/core/progressReporter.ts

230 lines
5.7 KiB
TypeScript

import { setLoadingScreenStatus } from '../appStatus'
import { appStatusState } from '../react/AppStatusProvider'
import { hideNotification, showNotification } from '../react/NotificationProvider'
export interface ProgressReporter {
currentMessage: string | undefined
beginStage (stage: string, title: string): void
endStage (stage: string): void
setSubStage (stage: string, subStageTitle: string): void
reportProgress (stage: string, progress: number): void
executeWithMessage<T>(message: string, fn: () => Promise<T>): Promise<T>
executeWithMessage<T>(message: string, stage: string, fn: () => Promise<T>): Promise<T>
setMessage (message: string): void
end(): void
error(message: string): void
}
interface ReporterDisplayImplementation {
setMessage (message: string): void
end (): void
error(message: string): void
}
interface StageInfo {
title: string
subStage?: string
progress?: number
}
const NO_STAGES_ACTION_END = false
const createProgressReporter = (implementation: ReporterDisplayImplementation): ProgressReporter => {
const stages = new Map<string, StageInfo>()
let currentMessage: string | undefined
let ended = false
const end = () => {
if (ended) return
ended = true
stages.clear()
implementation.end()
}
const updateStatus = () => {
if (ended) return
const activeStages = [...stages.entries()]
if (activeStages.length === 0) {
if (NO_STAGES_ACTION_END) {
end()
} else {
implementation.setMessage('Waiting for tasks')
}
return
}
const [currentStage, info] = activeStages.at(-1)!
let message = info.title
if (info.subStage) {
message += ` - ${info.subStage}`
}
if (info.progress !== undefined) {
const num = Math.round(info.progress * 100)
if (isFinite(num)) {
message += `: ${num}%`
}
}
currentMessage = message
implementation.setMessage(message)
}
const reporter = {
beginStage (stage: string, title: string) {
if (stages.has(stage)) {
throw new Error(`Stage ${stage} already is running`)
}
stages.set(stage, { title })
updateStatus()
},
endStage (stage: string) {
stages.delete(stage)
updateStatus()
},
setSubStage (stage: string, subStageTitle: string) {
const info = stages.get(stage)
if (info) {
info.subStage = subStageTitle
updateStatus()
}
},
reportProgress (stage: string, progress: number) {
const info = stages.get(stage)
if (info) {
info.progress = progress
updateStatus()
}
},
async executeWithMessage<T>(...args: any[]): Promise<T> {
const message = args[0]
const stage = typeof args[1] === 'string' ? args[1] : undefined
const fn = typeof args[1] === 'string' ? args[2] : args[1]
const tempStage = stage ?? 'temp-' + Math.random().toString(36).slice(2)
reporter.beginStage(tempStage, message)
try {
const result = await fn()
return result
} finally {
reporter.endStage(tempStage)
}
},
end (): void {
end()
},
setMessage (message: string): void {
implementation.setMessage(message)
},
get currentMessage () {
return currentMessage
},
error (message: string): void {
implementation.error(message)
}
}
return reporter
}
const fullScreenReporters = [] as ProgressReporter[]
export const createFullScreenProgressReporter = (): ProgressReporter => {
const reporter = createProgressReporter({
setMessage (message: string) {
if (appStatusState.isError) return
setLoadingScreenStatus(message)
},
end () {
if (appStatusState.isError) return
fullScreenReporters.splice(fullScreenReporters.indexOf(reporter), 1)
if (fullScreenReporters.length === 0) {
setLoadingScreenStatus(undefined)
} else {
setLoadingScreenStatus(fullScreenReporters.at(-1)!.currentMessage)
}
},
error (message: string): void {
if (appStatusState.isError) return
setLoadingScreenStatus(message, true)
}
})
fullScreenReporters.push(reporter)
return reporter
}
export const createNotificationProgressReporter = (endMessage?: string): ProgressReporter => {
return createProgressReporter({
setMessage (message: string) {
showNotification(`${message}...`, '', false, '', undefined, true)
},
end () {
if (endMessage) {
showNotification(endMessage, '', false, '', undefined, true)
} else {
hideNotification()
}
},
error (message: string): void {
showNotification(message, '', true, '', undefined, true)
}
})
}
export const createConsoleLogProgressReporter = (group?: string): ProgressReporter => {
return createProgressReporter({
setMessage (message: string) {
console.log(group ? `[${group}] ${message}` : message)
},
end () {
console.log(group ? `[${group}] done` : 'done')
},
error (message: string): void {
console.error(message)
}
})
}
export const createWrappedProgressReporter = (reporter: ProgressReporter, message?: string) => {
const stage = `wrapped-${message}`
if (message) {
reporter.beginStage(stage, message)
}
return createProgressReporter({
setMessage (message: string) {
reporter.setMessage(message)
},
end () {
if (message) {
reporter.endStage(stage)
}
},
error (message: string): void {
reporter.error(message)
}
})
}
export const createNullProgressReporter = (): ProgressReporter => {
return createProgressReporter({
setMessage (message: string) {
},
end () {
},
error (message: string) {
}
})
}