67 lines
3.3 KiB
JavaScript

(() => {
const form = document.querySelector('[data-story-point-form]');
if (!form) return;
const book = form.querySelector('[data-point-book]');
const chapter = form.querySelector('[data-point-chapter]');
const scene = form.querySelector('[data-point-scene]');
const chapters = [...chapter.options].map(x => x.cloneNode(true));
const scenes = [...scene.options].map(x => x.cloneNode(true));
const snapshot = document.querySelector('[data-point-snapshot]');
const feedback = document.querySelector('[data-point-feedback]');
let pending;
let request = 0;
function filter(select, options, key, value) {
const previous = select.value;
select.replaceChildren(...options.filter(x => x.dataset[key] === value).map(x => x.cloneNode(true)));
if ([...select.options].some(x => x.value === previous)) select.value = previous;
select.disabled = !select.options.length;
}
function filterScenes() { filter(scene, scenes, 'chapter', chapter.value); }
function filterChapters() { filter(chapter, chapters, 'book', book.value); filterScenes(); }
async function load(push = true) {
const current = ++request;
pending?.abort();
if (!scene.value) {
snapshot.replaceChildren();
feedback.textContent = 'No scenes in this selection.';
return;
}
pending = new AbortController();
const url = new URL(form.action, location.href);
url.search = new URLSearchParams(new FormData(form)).toString();
feedback.textContent = 'Loading story state…';
snapshot.replaceChildren();
snapshot.setAttribute('aria-busy', 'true');
try {
const response = await fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' }, signal: pending.signal });
if (response.redirected) { location.assign(response.url); return; }
if (!response.ok) throw new Error('load');
const html = await response.text();
if (current !== request) return;
snapshot.innerHTML = html;
if (push) history.pushState({}, '', url);
feedback.textContent = 'Story state updated.';
} catch (error) {
if (error.name !== 'AbortError' && current === request) {
snapshot.replaceChildren();
feedback.textContent = 'Story state could not be loaded. Use Show state to retry.';
}
} finally { if (current === request) snapshot.removeAttribute('aria-busy'); }
}
book.addEventListener('change', () => { filterChapters(); load(); });
chapter.addEventListener('change', () => { filterScenes(); load(); });
scene.addEventListener('change', () => load());
form.addEventListener('submit', event => { event.preventDefault(); load(); });
window.addEventListener('popstate', () => {
const id = new URL(location.href).searchParams.get('sceneId');
const chosenScene = scenes.find(x => x.value === id);
const chosenChapter = chapters.find(x => x.value === chosenScene?.dataset.chapter);
if (!chosenScene || !chosenChapter) { location.reload(); return; }
book.value = chosenChapter.dataset.book;
filterChapters(); chapter.value = chosenChapter.value;
filterScenes(); scene.value = id;
load(false);
});
filterChapters();
})();