feat(rsplat): add % progress bar for 3D generation, fix auth token lookup
Replace indeterminate sliding animation with a realistic percentage fill bar using logarithmic curve (asymptotes at 95%, based on ~60s typical Trellis 2 timing). Jumps to 100% on completion. Fix "sign in to save" showing for authenticated users by checking both localStorage and cookie for auth token, and improving the 401 message to "Session expired" when a token exists locally. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
95db01b451
commit
cf93b33c8b
|
|
@ -114,7 +114,7 @@ export class FolkSplatViewer extends HTMLElement {
|
|||
}
|
||||
|
||||
private async loadMyHistory() {
|
||||
const token = localStorage.getItem("encryptid_token");
|
||||
const token = this.getAuthToken();
|
||||
if (!token || this._spaceSlug === "demo") return;
|
||||
|
||||
try {
|
||||
|
|
@ -355,7 +355,7 @@ export class FolkSplatViewer extends HTMLElement {
|
|||
formData.append("tags", tagsInput.value.trim());
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem("encryptid_token") || "";
|
||||
const token = this.getAuthToken();
|
||||
const res = await fetch(`/${this._spaceSlug}/rsplat/api/splats`, {
|
||||
method: "POST",
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
|
|
@ -496,6 +496,17 @@ export class FolkSplatViewer extends HTMLElement {
|
|||
{ t: 75, msg: "Almost there..." },
|
||||
];
|
||||
|
||||
// Realistic progress curve — typical Trellis 2 takes 45-75s
|
||||
const EXPECTED_SECONDS = 60;
|
||||
const progressBar = progress.querySelector(".splat-generate__progress-bar") as HTMLElement;
|
||||
|
||||
const estimatePercent = (elapsed: number): number => {
|
||||
if (elapsed <= 0) return 0;
|
||||
// Logarithmic curve: fast start, slows toward 95% asymptote
|
||||
const ratio = elapsed / EXPECTED_SECONDS;
|
||||
return Math.min(95, 100 * (1 - Math.exp(-2.5 * ratio)));
|
||||
};
|
||||
|
||||
const onVisChange = () => {
|
||||
if (document.hidden) {
|
||||
hiddenAt = Date.now();
|
||||
|
|
@ -510,10 +521,12 @@ export class FolkSplatViewer extends HTMLElement {
|
|||
if (document.hidden) return; // Don't update while hidden
|
||||
const elapsed = Math.floor((Date.now() - startTime - hiddenTime) / 1000);
|
||||
const phase = [...phases].reverse().find(p => elapsed >= p.t);
|
||||
const pct = Math.round(estimatePercent(elapsed));
|
||||
if (progressBar) progressBar.style.setProperty("--splat-progress", `${pct}%`);
|
||||
if (progressText && phase) {
|
||||
progressText.textContent = `${phase.msg} (${elapsed}s)`;
|
||||
progressText.textContent = `${phase.msg} ${pct}% (${elapsed}s)`;
|
||||
}
|
||||
}, 1000);
|
||||
}, 500);
|
||||
|
||||
try {
|
||||
const imageUrl = await this.stageImage(selectedFile!);
|
||||
|
|
@ -552,6 +565,10 @@ export class FolkSplatViewer extends HTMLElement {
|
|||
}
|
||||
|
||||
const data = await res.json() as { url: string; format: string };
|
||||
// Jump to 100% before hiding
|
||||
if (progressBar) progressBar.style.setProperty("--splat-progress", "100%");
|
||||
if (progressText) progressText.textContent = "Complete!";
|
||||
await new Promise(r => setTimeout(r, 400));
|
||||
progress.style.display = "none";
|
||||
const elapsed = Math.floor((Date.now() - startTime - hiddenTime) / 1000);
|
||||
status.textContent = `Generated in ${elapsed}s`;
|
||||
|
|
@ -588,7 +605,7 @@ export class FolkSplatViewer extends HTMLElement {
|
|||
// ── Auto-save after generation ──
|
||||
|
||||
private async autoSave() {
|
||||
const token = localStorage.getItem("encryptid_token");
|
||||
const token = this.getAuthToken();
|
||||
if (!token || !this._generatedUrl || this._spaceSlug === "demo") return;
|
||||
|
||||
try {
|
||||
|
|
@ -739,6 +756,12 @@ export class FolkSplatViewer extends HTMLElement {
|
|||
}
|
||||
}
|
||||
|
||||
private getAuthToken(): string {
|
||||
return localStorage.getItem("encryptid_token")
|
||||
|| document.cookie.match(/encryptid_token=([^;]+)/)?.[1]
|
||||
|| "";
|
||||
}
|
||||
|
||||
private async saveToGallery() {
|
||||
const saveBtn = this.querySelector("#splat-save-btn") as HTMLButtonElement;
|
||||
if (!saveBtn || !this._generatedUrl) return;
|
||||
|
|
@ -747,7 +770,7 @@ export class FolkSplatViewer extends HTMLElement {
|
|||
saveBtn.textContent = "Saving...";
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem("encryptid_token") || "";
|
||||
const token = this.getAuthToken();
|
||||
if (!token) {
|
||||
saveBtn.textContent = "Sign in to save";
|
||||
saveBtn.disabled = false;
|
||||
|
|
@ -768,7 +791,7 @@ export class FolkSplatViewer extends HTMLElement {
|
|||
});
|
||||
|
||||
if (res.status === 401) {
|
||||
saveBtn.textContent = "Sign in to save";
|
||||
saveBtn.textContent = "Session expired — sign in again";
|
||||
saveBtn.disabled = false;
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -340,8 +340,9 @@
|
|||
}
|
||||
|
||||
.splat-generate__progress-bar {
|
||||
height: 4px;
|
||||
border-radius: 2px;
|
||||
--splat-progress: 0%;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background: var(--splat-border);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
|
@ -350,16 +351,13 @@
|
|||
.splat-generate__progress-bar::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: var(--splat-accent);
|
||||
width: 40%;
|
||||
border-radius: 2px;
|
||||
animation: splat-progress-slide 1.2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes splat-progress-slide {
|
||||
0% { transform: translateX(-100%); }
|
||||
100% { transform: translateX(350%); }
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: var(--splat-progress);
|
||||
background: linear-gradient(90deg, var(--splat-accent), #a78bfa);
|
||||
border-radius: 3px;
|
||||
transition: width 0.5s ease-out;
|
||||
}
|
||||
|
||||
.splat-generate__progress-text {
|
||||
|
|
|
|||
Loading…
Reference in New Issue