forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			146 lines
		
	
	
		
			No EOL
		
	
	
		
			5.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			146 lines
		
	
	
		
			No EOL
		
	
	
		
			5.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import {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: UIEventSource<boolean>
 | |
|     readonly Value: UIEventSource<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: string | BaseUIElement, step: FlowStep<TOut>): FlowPanelFactory<TOut> {
 | |
|         return new FlowPanelFactory(step, [], [name])
 | |
|     }
 | |
| 
 | |
|     public then<TOut>(name: string | BaseUIElement, construct: ((t: T) => FlowStep<TOut>)): FlowPanelFactory<TOut> {
 | |
|         return new FlowPanelFactory<TOut>(
 | |
|             this._initial,
 | |
|             this._steps.concat([construct]),
 | |
|             this._stepNames.concat([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)
 | |
|                             }
 | |
|                         }),
 | |
|                         "Select a valid value to continue",
 | |
|                         initial.IsValid
 | |
|                     ),
 | |
|                     new Toggle(
 | |
|                         new FixedUiElement("Something went wrong...").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
 | |
|     }
 | |
| 
 | |
| 
 | |
| } |