diff --git a/lib/folk-choice-rank.ts b/lib/folk-choice-rank.ts index 3c31dcc..354921c 100644 --- a/lib/folk-choice-rank.ts +++ b/lib/folk-choice-rank.ts @@ -839,13 +839,15 @@ export class FolkChoiceRank extends FolkShape { const onMove = (me: PointerEvent) => { me.stopPropagation(); - const target = this.#rankPanel!.querySelector( - `.rank-item:not(.dragging):hover` - ) as HTMLElement; - // Clear previous drag-over states this.#rankPanel!.querySelectorAll(".drag-over").forEach((d) => d.classList.remove("drag-over")); - - if (target) target.classList.add("drag-over"); + const items = this.#rankPanel!.querySelectorAll(".rank-item:not(.dragging)"); + for (const item of items) { + const rect = (item as HTMLElement).getBoundingClientRect(); + if (me.clientY >= rect.top && me.clientY <= rect.bottom) { + item.classList.add("drag-over"); + break; + } + } }; const onUp = (ue: PointerEvent) => { diff --git a/lib/folk-choice-spider.ts b/lib/folk-choice-spider.ts index 94ae599..08353cb 100644 --- a/lib/folk-choice-spider.ts +++ b/lib/folk-choice-spider.ts @@ -525,6 +525,7 @@ export class FolkChoiceSpider extends FolkShape { #selectedOptionId = ""; #drawerOpen = false; #settingsOpen = false; + #isSliding = false; // DOM refs #wrapperEl: HTMLElement | null = null; @@ -748,7 +749,7 @@ export class FolkChoiceSpider extends FolkShape { #render() { this.#renderOptionTabs(); this.#renderChart(); - this.#renderSliders(); + if (!this.#isSliding) this.#renderSliders(); this.#renderLegend(); this.#renderSummary(); if (this.#drawerOpen) this.#renderDrawer(); @@ -889,7 +890,9 @@ export class FolkChoiceSpider extends FolkShape { this.#slidersEl.querySelectorAll(".slider-input").forEach((slider) => { const input = slider as HTMLInputElement; input.addEventListener("click", (e) => e.stopPropagation()); - input.addEventListener("pointerdown", (e) => e.stopPropagation()); + input.addEventListener("pointerdown", (e) => { e.stopPropagation(); this.#isSliding = true; }); + input.addEventListener("pointerup", () => { this.#isSliding = false; }); + input.addEventListener("lostpointercapture", () => { this.#isSliding = false; }); input.addEventListener("input", (e) => { e.stopPropagation(); const critId = input.dataset.crit!; diff --git a/lib/folk-video-gen.ts b/lib/folk-video-gen.ts index 8a62021..1166600 100644 --- a/lib/folk-video-gen.ts +++ b/lib/folk-video-gen.ts @@ -459,7 +459,7 @@ export class FolkVideoGen extends FolkShape { this.#error = null; this.#progress = 0; if (this.#generateBtn) this.#generateBtn.disabled = true; - this.#renderLoading(); + this.#renderLoading("Submitting..."); try { const endpoint = this.#mode === "i2v" ? "/api/video-gen/i2v" : "/api/video-gen/t2v"; @@ -481,23 +481,35 @@ export class FolkVideoGen extends FolkShape { const submitData = await submitRes.json(); - // Poll for job completion (up to 5 minutes) + // Poll for job completion (up to 10 minutes) const jobId = submitData.job_id; if (!jobId) throw new Error("No job ID returned"); - const deadline = Date.now() + 300_000; - let elapsed = 0; + const deadline = Date.now() + 600_000; + let statusMsg = "Submitting to queue..."; while (Date.now() < deadline) { await new Promise((r) => setTimeout(r, 3000)); - elapsed += 3; - this.#progress = Math.min(90, (elapsed / 120) * 100); - this.#renderLoading(); const pollRes = await fetch(`/api/video-gen/${jobId}`); if (!pollRes.ok) continue; const pollData = await pollRes.json(); + // Update progress from real fal.ai status + const falStatus = pollData.fal_status; + const queuePos = pollData.queue_position; + if (falStatus === "IN_QUEUE") { + this.#progress = 10; + statusMsg = queuePos != null ? `In queue (position ${queuePos})...` : "Waiting in queue..."; + } else if (falStatus === "IN_PROGRESS") { + this.#progress = Math.min(85, this.#progress < 40 ? 40 : this.#progress + 2); + statusMsg = "Generating video..."; + } else if (!falStatus && pollData.status === "processing") { + this.#progress = Math.min(20, this.#progress + 2); + statusMsg = "Starting generation..."; + } + this.#renderLoading(statusMsg); + if (pollData.status === "complete") { const video: GeneratedVideo = { id: crypto.randomUUID(), @@ -530,16 +542,16 @@ export class FolkVideoGen extends FolkShape { } } - #renderLoading() { + #renderLoading(message = "Generating video...") { if (!this.#videoArea) return; this.#videoArea.innerHTML = `