mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-03 18:48:06 +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.
59 lines
1.4 KiB
TypeScript
59 lines
1.4 KiB
TypeScript
/** Lets router and Remix tests keep their existing fetch-backed endpoint fakes. */
|
|
export class FetchBackedFormRequest {
|
|
upload: { onload?: (event: ProgressEvent) => void } = {};
|
|
onload?: () => void;
|
|
onerror?: () => void;
|
|
onabort?: () => void;
|
|
status = 0;
|
|
responseText = "";
|
|
private url = "";
|
|
private headers = new Headers();
|
|
private response?: Response;
|
|
private controller = new AbortController();
|
|
|
|
open(_method: string, url: string) {
|
|
this.url = url;
|
|
}
|
|
|
|
setRequestHeader(name: string, value: string) {
|
|
this.headers.set(name, value);
|
|
}
|
|
|
|
getResponseHeader(name: string) {
|
|
return this.response?.headers.get(name) ?? null;
|
|
}
|
|
|
|
send(body: FormData) {
|
|
fetch(this.url, {
|
|
method: "POST",
|
|
body,
|
|
headers: this.headers,
|
|
signal: this.controller.signal,
|
|
})
|
|
.then(async (response) => {
|
|
if (this.controller.signal.aborted) {
|
|
return;
|
|
}
|
|
this.upload.onload?.({
|
|
loaded: 0,
|
|
total: 0,
|
|
lengthComputable: false,
|
|
} as ProgressEvent);
|
|
this.response = response;
|
|
this.status = response.status;
|
|
this.responseText = await response.text();
|
|
this.onload?.();
|
|
})
|
|
.catch(() => {
|
|
if (!this.controller.signal.aborted) {
|
|
this.onerror?.();
|
|
}
|
|
});
|
|
}
|
|
|
|
abort() {
|
|
this.controller.abort();
|
|
this.onabort?.();
|
|
}
|
|
}
|