115 lines
3.3 KiB
JavaScript
115 lines
3.3 KiB
JavaScript
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<object>} 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,
|
|
};
|
|
}
|