@lovision/plugin-dev 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -0
- package/dist/build.d.ts +16 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +108 -0
- package/dist/build.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +123 -0
- package/dist/cli.js.map +1 -0
- package/dist/create-plugin.d.ts +19 -0
- package/dist/create-plugin.d.ts.map +1 -0
- package/dist/create-plugin.js +186 -0
- package/dist/create-plugin.js.map +1 -0
- package/dist/dev.d.ts +13 -0
- package/dist/dev.d.ts.map +1 -0
- package/dist/dev.js +206 -0
- package/dist/dev.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/publish.d.ts +15 -0
- package/dist/publish.d.ts.map +1 -0
- package/dist/publish.js +55 -0
- package/dist/publish.js.map +1 -0
- package/dist/shared.d.ts +93 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +436 -0
- package/dist/shared.js.map +1 -0
- package/dist/templates/ai-layout-assistant/README.md.template +24 -0
- package/dist/templates/ai-layout-assistant/eslint.config.mjs +19 -0
- package/dist/templates/ai-layout-assistant/manifest.json.template +38 -0
- package/dist/templates/ai-layout-assistant/package.json.template +18 -0
- package/dist/templates/ai-layout-assistant/src/main.ts.template +345 -0
- package/dist/templates/ai-layout-assistant/tsconfig.json +14 -0
- package/dist/templates/ai-layout-assistant/ui.html.template +114 -0
- package/dist/templates/asset-browser/README.md.template +24 -0
- package/dist/templates/asset-browser/eslint.config.mjs +19 -0
- package/dist/templates/asset-browser/manifest.json.template +29 -0
- package/dist/templates/asset-browser/package.json.template +18 -0
- package/dist/templates/asset-browser/src/main.ts.template +177 -0
- package/dist/templates/asset-browser/tsconfig.json +14 -0
- package/dist/templates/asset-browser/ui.html.template +137 -0
- package/dist/templates/base/README.md.template +34 -0
- package/dist/templates/base/eslint.config.mjs +19 -0
- package/dist/templates/base/manifest.json.template +22 -0
- package/dist/templates/base/package.json.template +18 -0
- package/dist/templates/base/src/main.ts.template +20 -0
- package/dist/templates/base/tsconfig.json +14 -0
- package/dist/templates/batch-layout-organizer/README.md.template +24 -0
- package/dist/templates/batch-layout-organizer/eslint.config.mjs +19 -0
- package/dist/templates/batch-layout-organizer/manifest.json.template +31 -0
- package/dist/templates/batch-layout-organizer/package.json.template +18 -0
- package/dist/templates/batch-layout-organizer/src/main.ts.template +324 -0
- package/dist/templates/batch-layout-organizer/tsconfig.json +14 -0
- package/dist/templates/batch-layout-organizer/ui.html.template +116 -0
- package/dist/templates/data-filler-full/README.md.template +32 -0
- package/dist/templates/data-filler-full/eslint.config.mjs +19 -0
- package/dist/templates/data-filler-full/manifest.json.template +31 -0
- package/dist/templates/data-filler-full/package.json.template +18 -0
- package/dist/templates/data-filler-full/src/main.ts.template +412 -0
- package/dist/templates/data-filler-full/tsconfig.json +14 -0
- package/dist/templates/data-filler-full/ui.html.template +221 -0
- package/dist/templates/data-filler-lite/README.md.template +47 -0
- package/dist/templates/data-filler-lite/eslint.config.mjs +19 -0
- package/dist/templates/data-filler-lite/manifest.json.template +29 -0
- package/dist/templates/data-filler-lite/package.json.template +18 -0
- package/dist/templates/data-filler-lite/src/main.ts.template +222 -0
- package/dist/templates/data-filler-lite/tsconfig.json +14 -0
- package/dist/templates/data-filler-lite/ui.html.template +180 -0
- package/dist/templates/design-lint-panel/README.md.template +33 -0
- package/dist/templates/design-lint-panel/eslint.config.mjs +19 -0
- package/dist/templates/design-lint-panel/manifest.json.template +29 -0
- package/dist/templates/design-lint-panel/package.json.template +18 -0
- package/dist/templates/design-lint-panel/src/main.ts.template +221 -0
- package/dist/templates/design-lint-panel/tsconfig.json +14 -0
- package/dist/templates/design-lint-panel/ui.html.template +172 -0
- package/dist/templates/export-selection/README.md.template +26 -0
- package/dist/templates/export-selection/eslint.config.mjs +19 -0
- package/dist/templates/export-selection/manifest.json.template +31 -0
- package/dist/templates/export-selection/package.json.template +18 -0
- package/dist/templates/export-selection/src/main.ts.template +386 -0
- package/dist/templates/export-selection/tsconfig.json +14 -0
- package/dist/templates/export-selection/ui.html.template +163 -0
- package/dist/templates/review-submitter/README.md.template +24 -0
- package/dist/templates/review-submitter/eslint.config.mjs +19 -0
- package/dist/templates/review-submitter/manifest.json.template +35 -0
- package/dist/templates/review-submitter/package.json.template +18 -0
- package/dist/templates/review-submitter/src/main.ts.template +306 -0
- package/dist/templates/review-submitter/tsconfig.json +14 -0
- package/dist/templates/review-submitter/ui.html.template +114 -0
- package/dist/validate.d.ts +8 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +42 -0
- package/dist/validate.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { definePlugin } from "@lovision/plugin-sdk";
|
|
2
|
+
|
|
3
|
+
type Vec2 = {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
type Size = {
|
|
9
|
+
height: number;
|
|
10
|
+
width: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type SampleAsset = {
|
|
14
|
+
id: "badge" | "hero";
|
|
15
|
+
fileName: string;
|
|
16
|
+
insertName: string;
|
|
17
|
+
label: string;
|
|
18
|
+
position: Vec2;
|
|
19
|
+
size: Size;
|
|
20
|
+
svg: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type AssetBrowserMessage =
|
|
24
|
+
| {
|
|
25
|
+
type: "error";
|
|
26
|
+
payload: {
|
|
27
|
+
message: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
| {
|
|
31
|
+
type: "inserted";
|
|
32
|
+
payload: {
|
|
33
|
+
message: string;
|
|
34
|
+
newVersion: number;
|
|
35
|
+
resourceId: string;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
| {
|
|
39
|
+
type: "ready";
|
|
40
|
+
payload: {
|
|
41
|
+
assets: Array<{
|
|
42
|
+
id: SampleAsset["id"];
|
|
43
|
+
label: string;
|
|
44
|
+
}>;
|
|
45
|
+
message: string;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const SAMPLE_ASSETS: readonly SampleAsset[] = [
|
|
50
|
+
{
|
|
51
|
+
id: "hero",
|
|
52
|
+
label: "Hero asset",
|
|
53
|
+
insertName: "Asset Browser Hero",
|
|
54
|
+
fileName: "asset-browser-hero.svg",
|
|
55
|
+
position: { x: 440, y: 120 },
|
|
56
|
+
size: { width: 240, height: 160 },
|
|
57
|
+
svg: `<svg xmlns="http://www.w3.org/2000/svg" width="240" height="160" viewBox="0 0 240 160"><rect width="240" height="160" rx="20" fill="#0f766e"/><circle cx="190" cy="44" r="28" fill="#facc15"/><path d="M28 118 82 70l42 36 28-24 60 36v20H28Z" fill="#ecfeff"/><text x="28" y="38" fill="#ecfeff" font-family="Arial, sans-serif" font-size="18" font-weight="700">Hero Asset</text></svg>`,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: "badge",
|
|
61
|
+
label: "Badge asset",
|
|
62
|
+
insertName: "Asset Browser Badge",
|
|
63
|
+
fileName: "asset-browser-badge.svg",
|
|
64
|
+
position: { x: 460, y: 320 },
|
|
65
|
+
size: { width: 160, height: 160 },
|
|
66
|
+
svg: `<svg xmlns="http://www.w3.org/2000/svg" width="160" height="160" viewBox="0 0 160 160"><rect width="160" height="160" rx="80" fill="#4338ca"/><path d="m80 26 13 34 36 2-28 23 9 35-30-20-30 20 9-35-28-23 36-2Z" fill="#f8fafc"/><text x="80" y="132" text-anchor="middle" fill="#f8fafc" font-family="Arial, sans-serif" font-size="14" font-weight="700">Badge</text></svg>`,
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
definePlugin({
|
|
71
|
+
apiVersion: "1.0",
|
|
72
|
+
version: "0.1.0",
|
|
73
|
+
command: async (ctx) => {
|
|
74
|
+
const session = await ctx.ui.show({
|
|
75
|
+
mode: "panel",
|
|
76
|
+
entry: "./ui.html",
|
|
77
|
+
title: "Asset Browser",
|
|
78
|
+
width: 420,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
let closed = false;
|
|
82
|
+
const postToUi = async (message: AssetBrowserMessage): Promise<void> => {
|
|
83
|
+
if (closed) return;
|
|
84
|
+
await session.postMessage(message);
|
|
85
|
+
};
|
|
86
|
+
const panelClosed = new Promise<void>((resolve) => {
|
|
87
|
+
session.on("close", () => {
|
|
88
|
+
closed = true;
|
|
89
|
+
void ctx.notify.send("Asset Browser closed.", { kind: "info" });
|
|
90
|
+
resolve();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
session.on("insert-asset", async (payload) => {
|
|
95
|
+
await handleUiAction(postToUi, async () => {
|
|
96
|
+
const asset = readSampleAsset(payload);
|
|
97
|
+
const snapshot = await ctx.document.snapshot();
|
|
98
|
+
const ref = await ctx.binary.create({
|
|
99
|
+
bytes: encodeUtf8(asset.svg),
|
|
100
|
+
mimeType: "image/svg+xml",
|
|
101
|
+
name: asset.fileName,
|
|
102
|
+
kind: "generated",
|
|
103
|
+
});
|
|
104
|
+
const result = await ctx.assets.insertImage({
|
|
105
|
+
source: ref,
|
|
106
|
+
position: { ...asset.position },
|
|
107
|
+
size: { ...asset.size },
|
|
108
|
+
name: asset.insertName,
|
|
109
|
+
expectedVersion: snapshot.version,
|
|
110
|
+
});
|
|
111
|
+
await postToUi({
|
|
112
|
+
type: "inserted",
|
|
113
|
+
payload: {
|
|
114
|
+
message: `Inserted ${asset.insertName}.`,
|
|
115
|
+
newVersion: result.newVersion,
|
|
116
|
+
resourceId: ref.id,
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
await ctx.notify.send(
|
|
120
|
+
`Asset Browser inserted ${asset.insertName}.`,
|
|
121
|
+
{ kind: "success" },
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
await postToUi({
|
|
127
|
+
type: "ready",
|
|
128
|
+
payload: {
|
|
129
|
+
message: "Ready. Choose an asset and insert it as an image node.",
|
|
130
|
+
assets: SAMPLE_ASSETS.map((asset) => ({
|
|
131
|
+
id: asset.id,
|
|
132
|
+
label: asset.label,
|
|
133
|
+
})),
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
await panelClosed;
|
|
138
|
+
return { closed: true, ok: true };
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
async function handleUiAction(
|
|
143
|
+
postToUi: (message: AssetBrowserMessage) => Promise<void>,
|
|
144
|
+
action: () => Promise<unknown>,
|
|
145
|
+
): Promise<void> {
|
|
146
|
+
try {
|
|
147
|
+
await action();
|
|
148
|
+
} catch (error) {
|
|
149
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
150
|
+
await postToUi({ type: "error", payload: { message } });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function readSampleAsset(payload: unknown): SampleAsset {
|
|
155
|
+
if (!isRecord(payload) || typeof payload.assetId !== "string") {
|
|
156
|
+
throw new Error("Asset Browser requires an assetId.");
|
|
157
|
+
}
|
|
158
|
+
const asset = SAMPLE_ASSETS.find(
|
|
159
|
+
(candidate) => candidate.id === payload.assetId,
|
|
160
|
+
);
|
|
161
|
+
if (!asset) {
|
|
162
|
+
throw new Error(`Unknown asset: ${payload.assetId}`);
|
|
163
|
+
}
|
|
164
|
+
return asset;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function encodeUtf8(text: string): ArrayBuffer {
|
|
168
|
+
const bytes = new TextEncoder().encode(text);
|
|
169
|
+
return bytes.buffer.slice(
|
|
170
|
+
bytes.byteOffset,
|
|
171
|
+
bytes.byteOffset + bytes.byteLength,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
176
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
177
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"lib": ["ESNext", "WebWorker"],
|
|
4
|
+
"module": "Preserve",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"target": "ESNext",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"verbatimModuleSyntax": true,
|
|
11
|
+
"noEmit": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"]
|
|
14
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>Asset Browser</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
color-scheme: light dark;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
body {
|
|
13
|
+
margin: 0;
|
|
14
|
+
color: CanvasText;
|
|
15
|
+
background: Canvas;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
main {
|
|
19
|
+
display: grid;
|
|
20
|
+
gap: 1rem;
|
|
21
|
+
padding: 1rem;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.assets {
|
|
25
|
+
display: grid;
|
|
26
|
+
gap: 0.75rem;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.asset {
|
|
30
|
+
display: grid;
|
|
31
|
+
gap: 0.5rem;
|
|
32
|
+
padding: 0.75rem;
|
|
33
|
+
border: 1px solid ButtonBorder;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.asset-preview {
|
|
37
|
+
width: 100%;
|
|
38
|
+
min-height: 4rem;
|
|
39
|
+
display: grid;
|
|
40
|
+
place-items: center;
|
|
41
|
+
color: CanvasText;
|
|
42
|
+
background: Canvas;
|
|
43
|
+
border: 1px solid ButtonBorder;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.status {
|
|
47
|
+
min-height: 1.5rem;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
pre {
|
|
51
|
+
max-height: 8rem;
|
|
52
|
+
overflow: auto;
|
|
53
|
+
padding: 0.75rem;
|
|
54
|
+
color: CanvasText;
|
|
55
|
+
background: Canvas;
|
|
56
|
+
border: 1px solid ButtonBorder;
|
|
57
|
+
}
|
|
58
|
+
</style>
|
|
59
|
+
</head>
|
|
60
|
+
<body>
|
|
61
|
+
<main>
|
|
62
|
+
<header>
|
|
63
|
+
<h1>Asset Browser</h1>
|
|
64
|
+
<p>Insert generated image assets through the plugin main worker.</p>
|
|
65
|
+
</header>
|
|
66
|
+
|
|
67
|
+
<section class="assets" aria-label="Assets">
|
|
68
|
+
<article class="asset">
|
|
69
|
+
<div class="asset-preview">Hero Asset</div>
|
|
70
|
+
<button id="insert-hero-button" type="button">Insert hero asset</button>
|
|
71
|
+
</article>
|
|
72
|
+
<article class="asset">
|
|
73
|
+
<div class="asset-preview">Badge Asset</div>
|
|
74
|
+
<button id="insert-badge-button" type="button">Insert badge asset</button>
|
|
75
|
+
</article>
|
|
76
|
+
</section>
|
|
77
|
+
|
|
78
|
+
<p id="status" class="status" role="status">Waiting for host...</p>
|
|
79
|
+
<pre id="details" aria-label="Details"></pre>
|
|
80
|
+
</main>
|
|
81
|
+
|
|
82
|
+
<script>
|
|
83
|
+
const uiId = globalThis.__LOVISION_PLUGIN_UI_ID__;
|
|
84
|
+
const status = document.getElementById("status");
|
|
85
|
+
const details = document.getElementById("details");
|
|
86
|
+
|
|
87
|
+
function post(type, payload) {
|
|
88
|
+
parent.postMessage(
|
|
89
|
+
{
|
|
90
|
+
type: "plugin-ui-message",
|
|
91
|
+
direction: "ui-to-main",
|
|
92
|
+
uiId,
|
|
93
|
+
message: { type, payload },
|
|
94
|
+
},
|
|
95
|
+
"*",
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function insertAsset(assetId) {
|
|
100
|
+
status.textContent = "Inserting...";
|
|
101
|
+
details.textContent = "";
|
|
102
|
+
post("insert-asset", { assetId });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
document.getElementById("insert-hero-button").addEventListener("click", () => {
|
|
106
|
+
insertAsset("hero");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
document.getElementById("insert-badge-button").addEventListener("click", () => {
|
|
110
|
+
insertAsset("badge");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
window.addEventListener("message", (event) => {
|
|
114
|
+
const envelope = event.data;
|
|
115
|
+
if (
|
|
116
|
+
!envelope ||
|
|
117
|
+
envelope.type !== "plugin-ui-message" ||
|
|
118
|
+
envelope.direction !== "main-to-ui" ||
|
|
119
|
+
envelope.uiId !== uiId
|
|
120
|
+
) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const message = envelope.message;
|
|
125
|
+
if (message.type === "ready") {
|
|
126
|
+
status.textContent = message.payload.message;
|
|
127
|
+
details.textContent = JSON.stringify(message.payload.assets, null, 2);
|
|
128
|
+
} else if (message.type === "inserted") {
|
|
129
|
+
status.textContent = message.payload.message;
|
|
130
|
+
details.textContent = JSON.stringify(message.payload, null, 2);
|
|
131
|
+
} else if (message.type === "error") {
|
|
132
|
+
status.textContent = message.payload.message;
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
</script>
|
|
136
|
+
</body>
|
|
137
|
+
</html>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# __PLUGIN_NAME_EN__
|
|
2
|
+
|
|
3
|
+
An Instinct plugin scaffold generated by `create-instinct-plugin`.
|
|
4
|
+
|
|
5
|
+
## Scripts
|
|
6
|
+
|
|
7
|
+
- `dev` — start the local HTTPS sideload server and print `manifestUrl`
|
|
8
|
+
- `build` — emit a formal-install bundle under `dist/`
|
|
9
|
+
- `validate` — run the same manifest + bundle validation path used by the host
|
|
10
|
+
- `lint` — run the default plugin ESLint setup
|
|
11
|
+
|
|
12
|
+
## First Run
|
|
13
|
+
|
|
14
|
+
1. Install dependencies with your preferred package manager.
|
|
15
|
+
2. Run `dev`.
|
|
16
|
+
3. Open the editor's plugin development surface and sideload the printed `manifestUrl`.
|
|
17
|
+
4. Accept the local HTTPS certificate once if the browser asks.
|
|
18
|
+
|
|
19
|
+
## Network Allowlist Example
|
|
20
|
+
|
|
21
|
+
The default manifest keeps permissions minimal. When you need fetch access, declare both:
|
|
22
|
+
|
|
23
|
+
- `network:fetch`
|
|
24
|
+
- `domain:api.example.com`
|
|
25
|
+
|
|
26
|
+
and pair them with:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
"networkAccess": {
|
|
30
|
+
"allowedDomains": ["api.example.com"]
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
That keeps Step 13 network enforcement on the documented happy path and avoids a first-run `network denied` surprise.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import pluginSdkConfig from "@lovision/plugin-sdk/eslint-config";
|
|
2
|
+
import tsParser from "@typescript-eslint/parser";
|
|
3
|
+
|
|
4
|
+
const pluginFiles = ["src/**/*.ts"];
|
|
5
|
+
|
|
6
|
+
export default [
|
|
7
|
+
{
|
|
8
|
+
files: pluginFiles,
|
|
9
|
+
languageOptions: {
|
|
10
|
+
parser: tsParser,
|
|
11
|
+
ecmaVersion: "latest",
|
|
12
|
+
sourceType: "module",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
...pluginSdkConfig.map((entry) => ({
|
|
16
|
+
...entry,
|
|
17
|
+
files: pluginFiles,
|
|
18
|
+
})),
|
|
19
|
+
];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "__PLUGIN_ID__",
|
|
3
|
+
"name": {
|
|
4
|
+
"en": "__PLUGIN_NAME_EN__",
|
|
5
|
+
"zh-CN": "__PLUGIN_NAME_ZH__"
|
|
6
|
+
},
|
|
7
|
+
"version": "0.1.0",
|
|
8
|
+
"apiVersion": "1.0",
|
|
9
|
+
"editorType": ["design"],
|
|
10
|
+
"main": "./dist/main.js",
|
|
11
|
+
"documentAccess": "current-page",
|
|
12
|
+
"permissions": ["document:read", "notify"],
|
|
13
|
+
"commands": [
|
|
14
|
+
{
|
|
15
|
+
"id": "run",
|
|
16
|
+
"name": {
|
|
17
|
+
"en": "__COMMAND_NAME_EN__",
|
|
18
|
+
"zh-CN": "__COMMAND_NAME_ZH__"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PACKAGE_NAME__",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "plugin-dev dev",
|
|
7
|
+
"build": "plugin-dev build",
|
|
8
|
+
"validate": "plugin-dev validate",
|
|
9
|
+
"lint": "eslint src --max-warnings=0"
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@lovision/plugin-dev": "__PLUGIN_DEV_SPEC__",
|
|
13
|
+
"@lovision/plugin-sdk": "__PLUGIN_SDK_SPEC__"__LOCAL_SUPPORT_DEPS__,
|
|
14
|
+
"@typescript-eslint/parser": "^8.30.0",
|
|
15
|
+
"eslint": "^9.25.1",
|
|
16
|
+
"typescript": "^5.8.3"
|
|
17
|
+
}__LOCAL_OVERRIDES__
|
|
18
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { definePlugin } from "@lovision/plugin-sdk";
|
|
2
|
+
|
|
3
|
+
definePlugin({
|
|
4
|
+
apiVersion: "1.0",
|
|
5
|
+
version: "0.1.0",
|
|
6
|
+
command: async (ctx, info) => {
|
|
7
|
+
const snapshot = await ctx.document.snapshot();
|
|
8
|
+
const childCount = snapshot.root.children.length;
|
|
9
|
+
await ctx.notify.send(
|
|
10
|
+
`Ran ${info.id} with ${childCount} top-level node(s)`,
|
|
11
|
+
{ kind: "success" },
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
childCount,
|
|
16
|
+
commandId: info.id,
|
|
17
|
+
ok: true,
|
|
18
|
+
};
|
|
19
|
+
},
|
|
20
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"lib": ["ESNext", "WebWorker"],
|
|
4
|
+
"module": "Preserve",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"target": "ESNext",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"verbatimModuleSyntax": true,
|
|
11
|
+
"noEmit": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"]
|
|
14
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Batch Layout Organizer
|
|
2
|
+
|
|
3
|
+
This Step 27 acceptance template proves the structural Host Facade through a real `plugin-dev` project. The panel asks the main worker to create demo cards when the scene is sparse, then runs `group / reparent / reorder` through `ctx.nodes`.
|
|
4
|
+
|
|
5
|
+
## Development
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install
|
|
9
|
+
npm run dev
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Copy the printed `manifestUrl=...`, open the editor shell, then use Dev Panel -> Development -> Add by URL.
|
|
13
|
+
|
|
14
|
+
## Use
|
|
15
|
+
|
|
16
|
+
Run **Open Batch Layout Organizer**, click **Analyze canvas**, then click **Organize layout**. The command creates a `Batch Layout Group`, reparents a promoted card into it, reorders that card to the top, focuses the group, and emits a notification.
|
|
17
|
+
|
|
18
|
+
## Formal install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm run build
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Upload the generated `dist/*.bundle.json` through Dev Panel -> Formal install, confirm the install dialog, then run the command from MainMenu.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import pluginSdkConfig from "@lovision/plugin-sdk/eslint-config";
|
|
2
|
+
import tsParser from "@typescript-eslint/parser";
|
|
3
|
+
|
|
4
|
+
const pluginFiles = ["src/**/*.ts"];
|
|
5
|
+
|
|
6
|
+
export default [
|
|
7
|
+
{
|
|
8
|
+
files: pluginFiles,
|
|
9
|
+
languageOptions: {
|
|
10
|
+
parser: tsParser,
|
|
11
|
+
ecmaVersion: "latest",
|
|
12
|
+
sourceType: "module",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
...pluginSdkConfig.map((entry) => ({
|
|
16
|
+
...entry,
|
|
17
|
+
files: pluginFiles,
|
|
18
|
+
})),
|
|
19
|
+
];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "__PLUGIN_ID__",
|
|
3
|
+
"name": {
|
|
4
|
+
"en": "Batch Layout Organizer",
|
|
5
|
+
"zh-CN": "Batch Layout Organizer"
|
|
6
|
+
},
|
|
7
|
+
"version": "0.1.0",
|
|
8
|
+
"apiVersion": "1.0",
|
|
9
|
+
"editorType": ["design"],
|
|
10
|
+
"main": "./dist/main.js",
|
|
11
|
+
"ui": "./ui.html",
|
|
12
|
+
"documentAccess": "current-page",
|
|
13
|
+
"permissions": [
|
|
14
|
+
"document:read",
|
|
15
|
+
"document:write.create",
|
|
16
|
+
"document:write.reparent",
|
|
17
|
+
"notify",
|
|
18
|
+
"selection:read",
|
|
19
|
+
"ui:panel",
|
|
20
|
+
"viewport:write"
|
|
21
|
+
],
|
|
22
|
+
"commands": [
|
|
23
|
+
{
|
|
24
|
+
"id": "run",
|
|
25
|
+
"name": {
|
|
26
|
+
"en": "__COMMAND_NAME_EN__",
|
|
27
|
+
"zh-CN": "__COMMAND_NAME_ZH__"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PACKAGE_NAME__",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "plugin-dev dev",
|
|
7
|
+
"build": "plugin-dev build",
|
|
8
|
+
"validate": "plugin-dev validate",
|
|
9
|
+
"lint": "eslint src --max-warnings=0"
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@lovision/plugin-dev": "__PLUGIN_DEV_SPEC__",
|
|
13
|
+
"@lovision/plugin-sdk": "__PLUGIN_SDK_SPEC__"__LOCAL_SUPPORT_DEPS__,
|
|
14
|
+
"@typescript-eslint/parser": "^8.30.0",
|
|
15
|
+
"eslint": "^9.25.1",
|
|
16
|
+
"typescript": "^5.8.3"
|
|
17
|
+
}__LOCAL_OVERRIDES__
|
|
18
|
+
}
|