mapcomplete/UI/ImportFlow/FlowStep.ts

152 lines
5.3 KiB
TypeScript

import { Store, UIEventSource } from "../../Logic/UIEventSource"
import Combine from "../Base/Combine"
import BaseUIElement from "../BaseUIElement"
import { SubtleButton } from "../Base/SubtleButton"
import Svg from "../../Svg"
import Translations from "../i18n/Translations"
import { VariableUiElement } from "../Base/VariableUIElement"
import Toggle from "../Input/Toggle"
import { UIElement } from "../UIElement"
import { FixedUiElement } from "../Base/FixedUiElement"
export interface FlowStep<T> extends BaseUIElement {
readonly IsValid: Store<boolean>
readonly Value: Store<T>
}
export class FlowPanelFactory<T> {
private _initial: FlowStep<any>
private _steps: ((x: any) => FlowStep<any>)[]
private _stepNames: (string | BaseUIElement)[]
private constructor(
initial: FlowStep<any>,
steps: ((x: any) => FlowStep<any>)[],
stepNames: (string | BaseUIElement)[]
) {
this._initial = initial
this._steps = steps
this._stepNames = stepNames
}
public static start<TOut>(
name: { title: BaseUIElement },
step: FlowStep<TOut>
): FlowPanelFactory<TOut> {
return new FlowPanelFactory(step, [], [name.title])
}
public then<TOut>(
name: string | { title: BaseUIElement },
construct: (t: T) => FlowStep<TOut>
): FlowPanelFactory<TOut> {
return new FlowPanelFactory<TOut>(
this._initial,
this._steps.concat([construct]),
this._stepNames.concat([name["title"] ?? name])
)
}
public finish(
name: string | BaseUIElement,
construct: (t: T, backButton?: BaseUIElement) => BaseUIElement
): {
flow: BaseUIElement
furthestStep: UIEventSource<number>
titles: (string | BaseUIElement)[]
} {
const furthestStep = new UIEventSource(0)
// Construct all the flowpanels step by step (in reverse order)
const nextConstr: ((t: any, back?: UIElement) => BaseUIElement)[] = this._steps.map(
(_) => undefined
)
nextConstr.push(construct)
for (let i = this._steps.length - 1; i >= 0; i--) {
const createFlowStep: (value) => FlowStep<any> = this._steps[i]
const isConfirm = i == this._steps.length - 1
nextConstr[i] = (value, backButton) => {
const flowStep = createFlowStep(value)
furthestStep.setData(i + 1)
const panel = new FlowPanel(flowStep, nextConstr[i + 1], backButton, isConfirm)
panel.isActive.addCallbackAndRun((active) => {
if (active) {
furthestStep.setData(i + 1)
}
})
return panel
}
}
const flow = new FlowPanel(this._initial, nextConstr[0])
flow.isActive.addCallbackAndRun((active) => {
if (active) {
furthestStep.setData(0)
}
})
return {
flow,
furthestStep,
titles: this._stepNames,
}
}
}
export class FlowPanel<T> extends Toggle {
public isActive: UIEventSource<boolean>
constructor(
initial: FlowStep<T>,
constructNextstep: (input: T, backButton: BaseUIElement) => BaseUIElement,
backbutton?: BaseUIElement,
isConfirm = false
) {
const t = Translations.t.general
const currentStepActive = new UIEventSource(true)
let nextStep: UIEventSource<BaseUIElement> = new UIEventSource<BaseUIElement>(undefined)
const backButtonForNextStep = new SubtleButton(Svg.back_svg(), t.back).onClick(() => {
currentStepActive.setData(true)
})
let elements: (BaseUIElement | string)[] = []
const isError = new UIEventSource(false)
if (initial !== undefined) {
// Startup the flow
elements = [
initial,
new Combine([
backbutton,
new Toggle(
new SubtleButton(
isConfirm
? Svg.checkmark_svg()
: Svg.back_svg().SetStyle("transform: rotate(180deg);"),
isConfirm ? t.confirm : t.next
).onClick(() => {
try {
const v = initial.Value.data
nextStep.setData(constructNextstep(v, backButtonForNextStep))
currentStepActive.setData(false)
} catch (e) {
console.error(e)
isError.setData(true)
}
}),
new SubtleButton(Svg.invalid_svg(), t.notValid),
initial.IsValid
),
new Toggle(t.error.SetClass("alert"), undefined, isError),
]).SetClass("flex w-full justify-end space-x-2"),
]
}
super(
new Combine(elements).SetClass("h-full flex flex-col justify-between"),
new VariableUiElement(nextStep),
currentStepActive
)
this.isActive = currentStepActive
}
}