mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-03 10:37:48 +09:00
Closes #6605 New Forms show editable feedback while Resource actions run. The `formProgress` variable starts with `status: "idle"`, `loaded: 0`, `total: 0`, and `files: []`. It becomes pending during submission, then returns to idle. The existing `formState` still reports only initial, success, or error. The Progress container says “Submitting…” and adds measured whole-request bytes when the total is known. Files include ID, field name, name, size, and type. The browser cannot measure reliable per-file transfer bytes in the single multipart request. The binding editor suggests one example file to reveal those fields; the example never enters the actual Form value. Saved Forms retain their existing bindings and submission message. ## Checklist - [x] Keep the existing Form outcome variable and add upload activity, byte counts, and file details. - [x] Show zero byte defaults on insertion, new submissions, cancellation, and Preview reset without fake runtime files. - [x] Suggest file fields in Form and Resource bindings while evaluation, reset, rendering, and stored values use real data. - [x] Preserve Builder Preview headers, authentication, response checks, and cancellation. - [x] Keep existing success/error feedback and saved-Form fallback. - [x] Reset external progress when an active published Form unmounts. - [x] Update Form, Variables, and MCP/CLI guides; clarify the older screenshot and final-only simulation. Generated MCP docs check passes. - [x] Audit Form and Variables docs against final progress behavior; clarify the older Variables screenshot. - [x] Cover lifecycle, transport, repeated submission, cancellation, unmount, Preview, template, MCP binding/style, and editor completion with focused tests. - [x] Run relevant typechecks, lint, formatting, fixture generation, and independent review. - [x] Required CI checks pass on the latest commit. ## Verification Local focused Form browser tests (25), Form template tests (12), Builder Preview and Form context tests (14), and mounted editor example/reset tests (6) pass. Builder, SDK React, SDK registry, and project-build typechecks pass, as do lint, formatting, and fixture generation. The fixture sync regenerated two unrelated linked-project site paths; those outputs were restored before commit. Independent review found no remaining issues. All latest-commit checks passed; E2E shard 9 passed on a targeted rerun after its two Form tests also passed locally with 11 related tests. This follow-up adds 162 net non-test lines, more than the initial estimate. The extra code identifies the actual callback-bound variable by ID, even when renamed, and gives autocomplete a separate example scope so it cannot affect actual values. One provider covers Form fields and Resource editors. No schema or package was added. The existing Variables screenshot predates `formProgress`; its explanatory text is current. The React Router fixture's built browser client grew by 4 KiB on Linux CI (376 → 380 KiB), and its checked limit matches that measured output. Multipart transfer remains one whole request. Browser submission supplies live progress. MCP can discover `formProgress`, bind the Progress container, and style it; server simulation reports final outcomes and does not measure browser uploads.
77 lines
2.3 KiB
TypeScript
77 lines
2.3 KiB
TypeScript
import { toast } from "@webstudio-is/design-system";
|
|
import {
|
|
apiClientHeader,
|
|
apiClientVersionHeader,
|
|
getApiCompatibilityPayload,
|
|
} from "@webstudio-is/trpc-interface/api-compatibility";
|
|
import { csrfToken } from "./csrf.client";
|
|
import { $authToken } from "./nano-states/misc";
|
|
import { publicStaticEnv } from "~/env/env.static";
|
|
|
|
/**
|
|
* To avoid fetch interception from the canvas, i.e., `globalThis.fetch = () => console.log('INTERCEPTED');`,
|
|
*/
|
|
const _fetch = globalThis.fetch;
|
|
|
|
/** Apply the Builder request headers to both fetch and Form uploads. */
|
|
export const getBuilderRequestHeaders = (initial?: HeadersInit) => {
|
|
if (csrfToken === undefined) {
|
|
toast.error("CSRF token is not set.");
|
|
throw new Error("CSRF token is not set.");
|
|
}
|
|
|
|
const headers = new Headers(initial);
|
|
|
|
headers.set("X-CSRF-Token", csrfToken);
|
|
headers.set(apiClientHeader, "browser");
|
|
headers.set(apiClientVersionHeader, publicStaticEnv.VERSION);
|
|
|
|
const authToken = $authToken.get();
|
|
|
|
// Do not override the existing x-auth-token header.
|
|
// As some mutations are queue based and they need to be authenticated with the same token as in the queue.
|
|
if (authToken !== undefined && headers.get("x-auth-token") === null) {
|
|
headers.set("x-auth-token", authToken);
|
|
}
|
|
|
|
return headers;
|
|
};
|
|
|
|
export const inspectBuilderResponse = async (response: Response) => {
|
|
if (response.ok === false) {
|
|
const contentType = response.headers.get("Content-Type") ?? "";
|
|
if (contentType.includes("application/json")) {
|
|
const body: unknown = await response
|
|
.clone()
|
|
.json()
|
|
.catch(() => undefined);
|
|
const payload = getApiCompatibilityPayload(body);
|
|
if (payload?.action.type === "reloadBrowser") {
|
|
toast.error(payload.message, {
|
|
id: "api-compatibility",
|
|
duration: Number.POSITIVE_INFINITY,
|
|
});
|
|
throw new Error(payload.message, { cause: payload });
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
export const fetch: typeof globalThis.fetch = async (
|
|
requestInfo,
|
|
requestInit
|
|
) => {
|
|
const headers = getBuilderRequestHeaders(requestInit?.headers);
|
|
|
|
const modifiedInit: RequestInit = {
|
|
...requestInit,
|
|
cache: requestInit?.cache ?? "no-store",
|
|
headers,
|
|
};
|
|
|
|
const response = await _fetch(requestInfo, modifiedInit);
|
|
await inspectBuilderResponse(response);
|
|
|
|
return response;
|
|
};
|