feng-x's picture
Upload folder using huggingface_hub
4fa3ab9 verified
// Tiny linear step controller for the mobile flow.
//
// Each step registers a `mount(container)` function that paints itself
// into the supplied DOM container, plus an optional `unmount()` for
// cleanup (e.g. tearing down a camera stream when leaving the capture
// step). The controller maintains an index into a fixed step order and
// exposes `next()` / `back()` / `goTo(name)` for transitions.
//
// Deliberately minimal: no transitions, no history-API integration, no
// deep-linking. Five linear steps with back/forward β€” anything fancier
// can be added when the UX calls for it.
const stepRegistry = new Map();
let order = [];
let currentIndex = -1;
let currentStep = null;
let rootContainer = null;
export function registerStep(name, step) {
if (typeof step.mount !== "function") {
throw new Error(`Step "${name}" must define mount(container)`);
}
stepRegistry.set(name, step);
}
export function setOrder(names) {
for (const name of names) {
if (!stepRegistry.has(name)) {
throw new Error(`Step "${name}" is not registered`);
}
}
order = [...names];
}
export function start(container) {
rootContainer = container;
if (!order.length) throw new Error("setOrder() must be called before start()");
goTo(order[0]);
}
// `data` is an optional payload handed to the destination step's
// mount() β€” used by the capture step to forward the measurement result
// to the result step. Steps that don't need data can ignore the third
// argument; steps that need persistent state should still maintain it
// themselves (the controller doesn't survive a hard reload).
export function goTo(name, data) {
const idx = order.indexOf(name);
if (idx === -1) throw new Error(`Step "${name}" is not in the active order`);
if (currentStep && typeof currentStep.unmount === "function") {
try {
currentStep.unmount();
} catch (err) {
console.error(`Step "${order[currentIndex]}" unmount failed:`, err);
}
}
rootContainer.innerHTML = "";
currentIndex = idx;
currentStep = stepRegistry.get(name);
// Pass a small `nav` API down so steps don't need to import this
// module β€” keeps each step file standalone and easy to test.
const nav = {
next: (payload) => {
if (currentIndex < order.length - 1) goTo(order[currentIndex + 1], payload);
},
back: (payload) => {
if (currentIndex > 0) goTo(order[currentIndex - 1], payload);
},
goTo,
isFirst: () => currentIndex === 0,
isLast: () => currentIndex === order.length - 1,
progress: () => ({ index: currentIndex, total: order.length }),
};
currentStep.mount(rootContainer, nav, data);
}