import "./ProgramBoi.sass"; import {useContext, useEffect, useMemo, useReducer} from "react"; import RuntimeContext from "../../contexts/RuntimeContext"; import {ProgramStep, weighting} from "../../models/Programs"; import {firstKey, WorkoutState} from "../../models/Workouts"; import {diffLinearValues, formatValue} from "../../models/Shared"; import {Boi} from "../../primitives/boi/Boi"; interface StepMeta { actualDuration?: WorkoutState } interface ProgressState { steps: (ProgramStep & StepMeta)[] currentIndex: number lastTransition: WorkoutState lastValue: WorkoutState toNext: { current: number, max: number } stopped: boolean } interface ProgressChange { skip?: boolean workoutState?: WorkoutState } function programReducer(state: ProgressState, change: ProgressChange) { let {steps, currentIndex, lastTransition, lastValue, toNext, stopped} = state; // Stop working if after program if (stopped) { return state; } else if (currentIndex > steps.length - 1) { return {...state, stopped: true}; } // Skip if (change.skip) { steps[currentIndex].actualDuration = diffLinearValues(lastValue, lastTransition); return { ...state, steps, lastTransition: lastValue, currentIndex: currentIndex + 1, }; } // Workout state if (change.workoutState) { lastValue = change.workoutState; const step = steps[currentIndex]; if (step.duration) { if (step.duration.time) { toNext.current = lastValue.time - lastTransition.time; toNext.max = step.duration.time; } else if (step.duration.calories && lastTransition.calories !== undefined && lastValue.calories !== undefined) { toNext.current = lastValue.calories - lastTransition.calories; toNext.max = step.duration.calories; } else if (step.duration.distance && lastTransition.distance !== undefined && lastValue.distance !== undefined) { toNext.current = lastValue.distance - lastTransition.distance; toNext.max = step.duration.distance; } if (toNext.current >= toNext.max) { steps[currentIndex].actualDuration = diffLinearValues(lastValue, lastTransition); currentIndex += 1; lastTransition = lastValue; } } } return {steps, currentIndex, lastTransition, lastValue, toNext, stopped}; } export default function ProgramBoi() { const {workout, lastEvent} = useContext(RuntimeContext); const program = workout!.program!; const [progress, dispatch] = useReducer(programReducer, { steps: program.steps, currentIndex: 0, lastTransition: {time: 0, distance: 0, calories: 0}, lastValue: {time: 0}, toNext: {current: 0, max: 1}, stopped: false, }); useEffect(() => { if (lastEvent) { for (const workoutState of lastEvent?.workoutStates || []) { dispatch({workoutState}); } if (lastEvent?.event?.name === "Skipped") { dispatch({skip: true}); } } }, [lastEvent]); if (progress.steps.some(s => weighting(s) === 0)) { return ; } return ; } interface ProgressProps { progress: ProgressState } type FromTo = { count: false } | { count: true rawCurr: number rawTo: number strCurr: string strTo: string } function InfiniteProgress({progress}: ProgressProps) { const offset = 6 - progress.steps.length + progress.currentIndex; const ft: FromTo = useMemo(() => { const currentStep = progress.steps[progress.currentIndex]; if (currentStep) { const key = firstKey(currentStep.duration as WorkoutState); if (key !== null) { const duration = progress.steps[progress.currentIndex].duration![key]!; const numStr = formatValue(progress.toNext.current, key); const toStr = formatValue(duration, key); return { count: true, rawCurr: progress.toNext.current, rawTo: duration, strCurr: numStr, strTo: toStr, } as FromTo } } return {count: false}; }, [progress]); if (progress.currentIndex >= progress.steps.length) { return null; } return (
Steg {progress.currentIndex + 1} / {progress.steps.length}
{ft.count && (
{ft.strCurr} / {ft.strTo}
)} {!ft.count && (
(Custom)
)} ); } function HealthBarProgress({progress}: ProgressProps) { const offset = 6 - progress.steps.length; const steps = progress.steps.reduce((acc, b) => ([b, ...acc]), [] as typeof progress.steps); return (
{steps.map((step) => { const stepIndex = progress.steps.indexOf(step); const level = progress.steps.indexOf(step) + offset; const max = Math.max(1, progress.toNext.max); const key = firstKey(step.duration as WorkoutState); const duration = step.duration![key!]!; const durationStr = formatValue(duration - progress.toNext.current, key!); return (
{stepIndex === progress.currentIndex && (
{durationStr}
)}
{stepIndex > progress.currentIndex && (
)} {stepIndex === progress.currentIndex && ( <>
)} {stepIndex < progress.currentIndex && (
)}
); })}
); }