Skip to content

Commit 898046d

Browse files
authored
Merge pull request #97 from Automattic/feat/figma-studio-loopback-import
Post Figma imports to local Studio
2 parents a2d565b + 6d3101e commit 898046d

4 files changed

Lines changed: 52 additions & 88 deletions

File tree

docs/figma-studio-runner.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
# Figma to WordPress Studio
22

3-
`plugins/figma-to-wordpress-studio` moves a Figma file into WordPress by preparing a Studio CLI source for `studio create --from <source>`. Studio creates the site and imports the result with Static Site Importer and Blocks Engine.
3+
`plugins/figma-to-wordpress-studio` moves a Figma file into WordPress by posting a Studio import source to the local WordPress Studio app. Studio creates the site and imports the result with Static Site Importer and Blocks Engine.
44

55
## Architecture
66

77
```text
88
Figma plugin UI
99
-> Figma document scene data
1010
-> Studio import artifact JSON
11-
-> studio create --from <artifact-json>
11+
-> local WordPress Studio handoff endpoint
1212
-> WordPress Studio site
1313
-> Static Site Importer / Blocks Engine inside WordPress
1414
```
1515

16-
The TypeScript boundary stops at artifact construction and a user-facing CLI command. WordPress, PHP, Static Site Importer, Blocks Engine, and Studio site orchestration remain Studio runtime concerns.
16+
The TypeScript boundary stops at artifact construction and a local handoff request. WordPress, PHP, Static Site Importer, Blocks Engine, and Studio site orchestration remain Studio runtime concerns.
1717

1818
## Figma Iframe Limits
1919

@@ -27,7 +27,7 @@ Practical constraints for this browser-based flow:
2727
- Clipboard access can require user activation.
2828
- Local file URLs can behave differently between desktop Figma, browser Figma, and development builds.
2929

30-
Because of those constraints, the primary integration shape is a CLI handoff: the plugin saves an artifact JSON source and presents/copies `studio create --from <source>`. If a native bridge or deeper Studio integration exists later, it can consume the same artifact source.
30+
Because of those constraints, the integration shape is a local Studio handoff: the plugin posts an artifact JSON source to Studio's loopback endpoint. If Studio is not running, the plugin surfaces the connection error.
3131

3232
## Studio And PHP Role
3333

@@ -41,7 +41,7 @@ Implemented now:
4141

4242
- `GeneratedWebsiteArtifact` for static generated files from Figma.
4343
- `blocks-engine/php-transformer/site-artifact/v1` for the Studio CLI artifact handoff.
44-
- A Figma UI client that downloads the artifact JSON and copies `studio create --from ./<artifact>.studio-import.json`.
44+
- A Figma UI client that posts the artifact JSON to local Studio.
4545

4646
Not implemented yet:
4747

@@ -62,8 +62,9 @@ Figma development test:
6262
1. Run `npm run build --prefix plugins/figma-to-wordpress-studio`.
6363
2. Load `plugins/figma-to-wordpress-studio/manifest.json` as a Figma development plugin.
6464
3. Run the plugin.
65-
4. Use `Save Studio import payload`.
66-
5. Run the copied `studio create --from ./<artifact>.studio-import.json` command from a terminal, adjusting the path to the saved artifact if needed.
65+
4. Start a compatible WordPress Studio build.
66+
5. Use `Open in WordPress Studio`.
67+
6. Studio creates the site and opens the local site URL in your browser. If Studio is not reachable, the plugin reports the connection error.
6768

6869
## Next Integration Step
6970

plugins/figma-to-wordpress-studio/README.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
# Figma to WordPress Studio
22

3-
This plugin moves a Figma file into WordPress by preparing a Studio CLI import payload for `studio create --from <source>`.
3+
This plugin moves a Figma file into WordPress by sending a Studio import payload to the local WordPress Studio app.
44

5-
It does not port Static Site Importer, Blocks Engine, WordPress, PHP, or Studio internals to TypeScript. The TypeScript code extracts Figma scene data, prepares an artifact JSON source, and gives the user the `studio create --from ...` command. Studio owns site creation, import through Static Site Importer, and cleanup after a successful import.
5+
It does not port Static Site Importer, Blocks Engine, WordPress, PHP, or Studio internals to TypeScript. The TypeScript code extracts Figma scene data, prepares an artifact JSON source, and posts it to Studio's local handoff endpoint. Studio owns site creation, import through Static Site Importer, and cleanup after a successful import. If Studio is not running, the plugin surfaces the connection error.
66

77
## Flow
88

99
```text
1010
Figma plugin controller + UI
1111
-> whole Figma document scene data
1212
-> Studio import artifact JSON
13-
-> studio create --from <artifact-json>
13+
-> local WordPress Studio handoff endpoint
1414
-> WordPress Studio site
1515
-> Static Site Importer imports through Blocks Engine
1616
```
@@ -19,7 +19,7 @@ Figma plugin controller + UI
1919

2020
- Implemented: a Figma Desktop-loadable plugin shell with whole-file export.
2121
- Implemented: a reusable scene-to-HTML/CSS artifact generator with diagnostics and tests for the current local handoff.
22-
- Implemented: a Studio CLI import payload and copyable `studio create --from ...` command.
22+
- Implemented: a Studio local handoff request.
2323
- Not implemented: running Static Site Importer from TypeScript.
2424
- Not implemented: automated visual parity/block validation after import.
2525

@@ -36,8 +36,9 @@ Figma plugin controller + UI
3636

3737
1. Run `npm run build --prefix plugins/figma-to-wordpress-studio`.
3838
2. In Figma Desktop, use Plugins -> Development -> Import plugin from manifest, then select `plugins/figma-to-wordpress-studio/manifest.json`.
39-
3. Open the plugin and choose `Save Studio import payload`.
40-
4. Run the copied `studio create --from ./<artifact>.studio-import.json` command from a terminal, adjusting the path to the saved artifact if needed.
39+
3. Start a compatible WordPress Studio build.
40+
4. Open the plugin and choose `Open in WordPress Studio`.
41+
5. Studio creates the site and opens the local site URL in your browser. If Studio is not reachable, the plugin reports the connection error.
4142

4243
For quick syntax verification without a full Figma build pipeline:
4344

@@ -47,8 +48,8 @@ npm run build --prefix plugins/figma-to-wordpress-studio
4748
npm test --prefix plugins/figma-to-wordpress-studio
4849
```
4950

50-
## Studio CLI Boundary
51+
## Studio Handoff Boundary
5152

52-
Figma plugin UIs run in a constrained iframe-like environment and cannot spawn local shell commands. The supported boundary is a CLI handoff: the plugin saves the import artifact and presents/copies `studio create --from <source>`. Studio accepts the artifact source, creates the site, runs Static Site Importer, and removes SSI after a successful import.
53+
Figma plugin UIs run in a constrained iframe-like environment and cannot spawn local shell commands. The supported boundary is a local Studio handoff: the plugin posts the import artifact to Studio's loopback endpoint. Studio accepts the artifact source, creates the site, runs Static Site Importer, and removes SSI after a successful import.
5354

5455
See [`../../docs/figma-studio-runner.md`](../../docs/figma-studio-runner.md) for the integration notes.

plugins/figma-to-wordpress-studio/src/ui.html

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -60,25 +60,6 @@
6060
opacity: 0.55;
6161
}
6262

63-
label {
64-
display: grid;
65-
gap: 6px;
66-
font-weight: 600;
67-
}
68-
69-
textarea {
70-
box-sizing: border-box;
71-
width: 100%;
72-
border: 1px solid var(--figma-color-border, #d9d9d9);
73-
border-radius: 8px;
74-
padding: 8px 10px;
75-
color: var(--figma-color-text, #1e1e1e);
76-
background: var(--figma-color-bg, #fff);
77-
font: inherit;
78-
min-height: 54px;
79-
resize: vertical;
80-
}
81-
8263
.panel {
8364
display: grid;
8465
gap: 10px;
@@ -104,13 +85,8 @@
10485
<header class="panel">
10586
<h1>Figma to WordPress Studio</h1>
10687
<p id="status">Preparing this Figma file for WordPress Studio.</p>
107-
<label>
108-
Studio CLI command
109-
<textarea id="studio-command" readonly spellcheck="false" aria-label="Studio CLI command"></textarea>
110-
</label>
11188
<div class="actions">
112-
<button id="studio" class="primary" type="button" disabled>Save Studio import payload</button>
113-
<button id="copy-command" type="button" disabled>Copy Studio command</button>
89+
<button id="studio" class="primary" type="button" disabled>Open in WordPress Studio</button>
11490
<button id="refresh" type="button">Refresh Figma file</button>
11591
</div>
11692
</header>

plugins/figma-to-wordpress-studio/src/ui.ts

Lines changed: 34 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ let currentSelection: NormalizedSelection | null = null;
66
const statusElement = document.querySelector<HTMLParagraphElement>("#status");
77
const refreshButton = document.querySelector<HTMLButtonElement>("#refresh");
88
const studioButton = document.querySelector<HTMLButtonElement>("#studio");
9-
const copyCommandButton = document.querySelector<HTMLButtonElement>("#copy-command");
10-
const commandElement = document.querySelector<HTMLTextAreaElement>("#studio-command");
9+
const studioHandoffEndpoint = "http://127.0.0.1:48732/figma-to-wordpress/import";
1110

1211
function log(message: string, details?: unknown) {
1312
if (typeof details === "undefined") {
@@ -41,60 +40,48 @@ function updateActions() {
4140
const disabled = !currentArtifact;
4241

4342
if (studioButton) studioButton.disabled = disabled;
44-
if (copyCommandButton) copyCommandButton.disabled = disabled;
45-
if (commandElement) commandElement.value = currentArtifact ? studioCreateCommand(currentArtifact) : "";
4643
}
4744

48-
function artifactFileName(artifact: GeneratedArtifact): string {
49-
const slug = artifact.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "figma-wordpress-import";
50-
51-
return `${slug}.studio-import.json`;
52-
}
53-
54-
function studioCreateCommand(artifact: GeneratedArtifact): string {
55-
return `studio create --from ./${artifactFileName(artifact)}`;
56-
}
57-
58-
async function copyStudioCommand() {
45+
async function openInStudio() {
5946
if (!currentArtifact) {
47+
log("Studio import requested before artifact was ready.");
6048
return;
6149
}
6250

63-
const command = studioCreateCommand(currentArtifact);
51+
if (studioButton) {
52+
studioButton.disabled = true;
53+
}
54+
setStatus("Sending this Figma file to WordPress Studio...");
6455

6556
try {
66-
await navigator.clipboard.writeText(command);
67-
setStatus("Studio command copied. Save the import payload beside the path in the command, then run it in a terminal.");
68-
sendToPlugin({ type: "notify", message: "Studio command copied." });
69-
} catch (error) {
70-
console.error("[Figma to WordPress Studio] Could not copy Studio command.", error);
71-
setStatus("Copy failed. Select the command text and copy it manually.");
72-
}
73-
}
57+
const response = await fetch(studioHandoffEndpoint, {
58+
method: "POST",
59+
headers: {
60+
"Content-Type": "application/json",
61+
},
62+
body: JSON.stringify({
63+
artifact: currentArtifact.studioImportPayload,
64+
siteName: currentArtifact.title,
65+
}),
66+
});
67+
const data = await response.json().catch(() => null) as { success?: boolean; error?: string } | null;
7468

75-
function downloadStudioPayload() {
76-
if (!currentArtifact) {
77-
log("Studio import payload requested before artifact was ready.");
78-
return;
79-
}
69+
if (!response.ok || !data?.success) {
70+
throw new Error(data?.error || `Studio handoff failed with HTTP ${response.status}.`);
71+
}
8072

81-
const fileName = artifactFileName(currentArtifact);
82-
const blob = new Blob([JSON.stringify(currentArtifact.studioImportPayload, null, 2)], { type: "application/json" });
83-
const url = URL.createObjectURL(blob);
84-
const link = document.createElement("a");
85-
86-
link.href = url;
87-
link.download = fileName;
88-
link.click();
89-
URL.revokeObjectURL(url);
90-
91-
setStatus(`Saved ${fileName}. Run the Studio command with the file path to create the WordPress site.`);
92-
log("Studio import payload prepared.", {
93-
fileName,
94-
command: studioCreateCommand(currentArtifact),
95-
...artifactBundleSummary(currentArtifact),
96-
});
97-
void copyStudioCommand();
73+
setStatus("Studio created the WordPress site and is opening it in your browser.");
74+
sendToPlugin({ type: "notify", message: "Sent to WordPress Studio." });
75+
log("Studio import handoff accepted.", {
76+
endpoint: studioHandoffEndpoint,
77+
...artifactBundleSummary(currentArtifact),
78+
});
79+
} catch (error) {
80+
console.error("[Figma to WordPress Studio] Studio handoff failed.", error);
81+
setStatus(error instanceof Error ? error.message : "Could not reach WordPress Studio.");
82+
} finally {
83+
updateActions();
84+
}
9885
}
9986

10087
function countDesignScreens(selection: NormalizedSelection): number {
@@ -149,8 +136,7 @@ window.onmessage = (event: MessageEvent<{ pluginMessage?: PluginToUiMessage }>)
149136
};
150137

151138
refreshButton?.addEventListener("click", () => sendToPlugin({ type: "refresh-document" }));
152-
studioButton?.addEventListener("click", downloadStudioPayload);
153-
copyCommandButton?.addEventListener("click", () => void copyStudioCommand());
139+
studioButton?.addEventListener("click", () => void openInStudio());
154140

155141
log("UI loaded.");
156142
sendToPlugin({ type: "refresh-document" });

0 commit comments

Comments
 (0)