import { ref } from "vue"; import { parseManifest } from "./onboardingGuides"; /** * Manage onboarding guide media, pagination, and lightbox state. * * @param {object} gates - Step completion/blocking predicates from the onboarding flow. * @returns {object} guide state and guide UI helpers. */ export function useOnboardingGuides({ isStepDone, isStepBlocked }) { const guideShots = ref({}); const guidePage = ref({}); const lightboxShot = ref(null); /** * Return screenshot groups for a step, honoring configured head/tail limits. * * @param {object} step - Onboarding step definition with optional guide metadata. * @returns {Array} Screenshot groups to render for the guide carousel. */ function guideGroups(step) { if (!step.guide) return []; const service = step.guide.service; const stepKey = step.guide.step; const serviceShots = guideShots.value?.[service] || {}; const stepShots = serviceShots?.[stepKey] || {}; const groups = Object.values(stepShots); const take = step.guide.take || step.guide.tail || 0; if (!take) return groups; const useTail = Boolean(step.guide.tail); return groups.map((group) => { const shots = useTail ? group.shots.slice(-take) : group.shots.slice(0, take); return { ...group, shots }; }); } function guideKey(step, group) { const service = step.guide?.service || "unknown"; const stepKey = step.guide?.step || "unknown"; return `${service}:${stepKey}:${group.id}`; } function guideIndex(step, group) { const key = guideKey(step, group); const index = guidePage.value[key] ?? 0; const maxIndex = Math.max(group.shots.length - 1, 0); return Math.min(Math.max(index, 0), maxIndex); } function guideSet(step, group, index) { const key = guideKey(step, group); const next = Math.min(Math.max(index, 0), group.shots.length - 1); guidePage.value = { ...guidePage.value, [key]: next }; } function guidePrev(step, group) { guideSet(step, group, guideIndex(step, group) - 1); } function guideNext(step, group) { guideSet(step, group, guideIndex(step, group) + 1); } function guideShot(step, group) { return group.shots[guideIndex(step, group)] || {}; } function shouldOpenGuide(step, section) { if (!step || !step.guide || !section) return false; const first = section.steps.find( (item) => item.guide && !isStepDone(item.id) && !isStepBlocked(item.id), ); return Boolean(first && first.id === step.id); } function openLightbox(shot) { if (!shot || !shot.url) return; lightboxShot.value = shot; } function closeLightbox() { lightboxShot.value = null; } async function loadGuideShots() { try { const resp = await fetch("/media/onboarding/manifest.json", { headers: { Accept: "application/json" } }); if (!resp.ok) return; const payload = await resp.json(); const files = Array.isArray(payload?.files) ? payload.files : []; guideShots.value = parseManifest(files); } catch { guideShots.value = {}; } } return { guideShots, guidePage, lightboxShot, guideGroups, guideKey, guideIndex, guideSet, guidePrev, guideNext, guideShot, shouldOpenGuide, openLightbox, closeLightbox, loadGuideShots, }; }