@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,345 @@
|
|
|
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 SceneNodeSnapshot = {
|
|
14
|
+
children: SceneNodeSnapshot[];
|
|
15
|
+
id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
parentId: string | null;
|
|
18
|
+
position: Vec2;
|
|
19
|
+
size: Size;
|
|
20
|
+
type: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type SceneSnapshot = {
|
|
24
|
+
root: SceneNodeSnapshot;
|
|
25
|
+
version: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type NodeCreateSpec = {
|
|
29
|
+
name?: string;
|
|
30
|
+
parentId?: string | null;
|
|
31
|
+
position?: Vec2;
|
|
32
|
+
size?: Size;
|
|
33
|
+
type: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type MutationResult = {
|
|
37
|
+
newVersion: number;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type IntegrationInvokeResult = {
|
|
41
|
+
integrationId: string;
|
|
42
|
+
invokedAt: number;
|
|
43
|
+
operation: string;
|
|
44
|
+
output?: unknown;
|
|
45
|
+
receipt?: unknown;
|
|
46
|
+
status: "accepted" | "ok";
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type LayoutAssistantContext = {
|
|
50
|
+
document: {
|
|
51
|
+
snapshot(): Promise<SceneSnapshot>;
|
|
52
|
+
};
|
|
53
|
+
integrations: {
|
|
54
|
+
invoke(params: {
|
|
55
|
+
artifacts?: Array<{ resource: unknown; role?: string }>;
|
|
56
|
+
input: unknown;
|
|
57
|
+
integrationId: string;
|
|
58
|
+
operation: string;
|
|
59
|
+
}): Promise<IntegrationInvokeResult>;
|
|
60
|
+
};
|
|
61
|
+
nodes: {
|
|
62
|
+
create(
|
|
63
|
+
specs: NodeCreateSpec[],
|
|
64
|
+
opts?: { expectedVersion?: number },
|
|
65
|
+
): Promise<MutationResult>;
|
|
66
|
+
};
|
|
67
|
+
notify: {
|
|
68
|
+
send(
|
|
69
|
+
message: string,
|
|
70
|
+
options?: { kind?: "error" | "info" | "success" | "warning" },
|
|
71
|
+
): Promise<void>;
|
|
72
|
+
};
|
|
73
|
+
selection: {
|
|
74
|
+
get(): Promise<string[]>;
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
type LayoutCardSuggestion = {
|
|
79
|
+
name: string;
|
|
80
|
+
position: Vec2;
|
|
81
|
+
size: Size;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
type LayoutSuggestion = {
|
|
85
|
+
cards: LayoutCardSuggestion[];
|
|
86
|
+
integration: {
|
|
87
|
+
integrationId: string;
|
|
88
|
+
invokedAt: number;
|
|
89
|
+
operation: string;
|
|
90
|
+
status: string;
|
|
91
|
+
};
|
|
92
|
+
summary: string;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
type LayoutMessage =
|
|
96
|
+
| {
|
|
97
|
+
type: "analysis";
|
|
98
|
+
payload: LayoutSuggestion;
|
|
99
|
+
}
|
|
100
|
+
| {
|
|
101
|
+
type: "applied";
|
|
102
|
+
payload: {
|
|
103
|
+
cardCount: number;
|
|
104
|
+
newVersion: number;
|
|
105
|
+
suggestion: LayoutSuggestion;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
| {
|
|
109
|
+
type: "error";
|
|
110
|
+
payload: {
|
|
111
|
+
message: string;
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
| {
|
|
115
|
+
type: "ready";
|
|
116
|
+
payload: {
|
|
117
|
+
message: string;
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
definePlugin({
|
|
122
|
+
apiVersion: "1.0",
|
|
123
|
+
version: "0.1.0",
|
|
124
|
+
command: async (ctx) => {
|
|
125
|
+
const session = await ctx.ui.show({
|
|
126
|
+
mode: "panel",
|
|
127
|
+
entry: "./ui.html",
|
|
128
|
+
title: "AI Layout Assistant",
|
|
129
|
+
width: 440,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
let closed = false;
|
|
133
|
+
let latestSuggestion: LayoutSuggestion | null = null;
|
|
134
|
+
const postToUi = async (message: LayoutMessage): Promise<void> => {
|
|
135
|
+
if (closed) return;
|
|
136
|
+
await session.postMessage(message);
|
|
137
|
+
};
|
|
138
|
+
const panelClosed = new Promise<void>((resolve) => {
|
|
139
|
+
session.on("close", () => {
|
|
140
|
+
closed = true;
|
|
141
|
+
void ctx.notify.send("AI Layout Assistant closed.", {
|
|
142
|
+
kind: "info",
|
|
143
|
+
});
|
|
144
|
+
resolve();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
session.on("analyze-layout", async () => {
|
|
149
|
+
await handleUiAction(postToUi, async () => {
|
|
150
|
+
latestSuggestion = await analyzeLayout(ctx);
|
|
151
|
+
await postToUi({
|
|
152
|
+
type: "analysis",
|
|
153
|
+
payload: latestSuggestion,
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
session.on("apply-layout", async () => {
|
|
159
|
+
await handleUiAction(postToUi, async () => {
|
|
160
|
+
const suggestion = latestSuggestion ?? (await analyzeLayout(ctx));
|
|
161
|
+
latestSuggestion = suggestion;
|
|
162
|
+
const applied = await applyLayout(ctx, suggestion);
|
|
163
|
+
await postToUi({
|
|
164
|
+
type: "applied",
|
|
165
|
+
payload: {
|
|
166
|
+
cardCount: suggestion.cards.length,
|
|
167
|
+
newVersion: applied.newVersion,
|
|
168
|
+
suggestion,
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
await ctx.notify.send(
|
|
172
|
+
`AI Layout Assistant applied ${suggestion.cards.length} suggestion card(s).`,
|
|
173
|
+
{ kind: "success" },
|
|
174
|
+
);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
await postToUi({
|
|
179
|
+
type: "ready",
|
|
180
|
+
payload: {
|
|
181
|
+
message:
|
|
182
|
+
"Ready. Analyze layout through the host integration, then apply cards.",
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
await panelClosed;
|
|
187
|
+
return { closed: true, ok: true };
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
async function handleUiAction(
|
|
192
|
+
postToUi: (message: LayoutMessage) => Promise<void>,
|
|
193
|
+
action: () => Promise<unknown>,
|
|
194
|
+
): Promise<void> {
|
|
195
|
+
try {
|
|
196
|
+
await action();
|
|
197
|
+
} catch (error) {
|
|
198
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
199
|
+
await postToUi({ type: "error", payload: { message } });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function analyzeLayout(
|
|
204
|
+
ctx: LayoutAssistantContext,
|
|
205
|
+
): Promise<LayoutSuggestion> {
|
|
206
|
+
const [snapshot, selection] = await Promise.all([
|
|
207
|
+
ctx.document.snapshot(),
|
|
208
|
+
ctx.selection.get(),
|
|
209
|
+
]);
|
|
210
|
+
const nodes = collectNodes(snapshot.root).filter(
|
|
211
|
+
(node) => node.id !== snapshot.root.id,
|
|
212
|
+
);
|
|
213
|
+
const result = await ctx.integrations.invoke({
|
|
214
|
+
integrationId: "ai.layout",
|
|
215
|
+
operation: "suggest-layout",
|
|
216
|
+
input: {
|
|
217
|
+
nodeCount: nodes.length,
|
|
218
|
+
selectedIds: selection,
|
|
219
|
+
snapshotVersion: snapshot.version,
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
return normalizeSuggestion(result);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function applyLayout(
|
|
227
|
+
ctx: LayoutAssistantContext,
|
|
228
|
+
suggestion: LayoutSuggestion,
|
|
229
|
+
): Promise<MutationResult> {
|
|
230
|
+
const snapshot = await ctx.document.snapshot();
|
|
231
|
+
return await ctx.nodes.create(
|
|
232
|
+
suggestion.cards.map((card) => ({
|
|
233
|
+
type: "shape",
|
|
234
|
+
name: card.name,
|
|
235
|
+
parentId: null,
|
|
236
|
+
position: card.position,
|
|
237
|
+
size: card.size,
|
|
238
|
+
})),
|
|
239
|
+
{ expectedVersion: snapshot.version },
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function normalizeSuggestion(
|
|
244
|
+
result: IntegrationInvokeResult,
|
|
245
|
+
): LayoutSuggestion {
|
|
246
|
+
const output = readRecord(result.output);
|
|
247
|
+
const cards = readCards(output?.cards);
|
|
248
|
+
const fallbackCards =
|
|
249
|
+
cards.length > 0
|
|
250
|
+
? cards
|
|
251
|
+
: [
|
|
252
|
+
{
|
|
253
|
+
name: "AI Layout Assistant Card A",
|
|
254
|
+
position: { x: 120, y: 340 },
|
|
255
|
+
size: { width: 120, height: 80 },
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
name: "AI Layout Assistant Card B",
|
|
259
|
+
position: { x: 260, y: 340 },
|
|
260
|
+
size: { width: 120, height: 80 },
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
name: "AI Layout Assistant Card C",
|
|
264
|
+
position: { x: 400, y: 340 },
|
|
265
|
+
size: { width: 120, height: 80 },
|
|
266
|
+
},
|
|
267
|
+
];
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
cards: fallbackCards,
|
|
271
|
+
integration: {
|
|
272
|
+
integrationId: result.integrationId,
|
|
273
|
+
invokedAt: result.invokedAt,
|
|
274
|
+
operation: result.operation,
|
|
275
|
+
status: result.status,
|
|
276
|
+
},
|
|
277
|
+
summary:
|
|
278
|
+
typeof output?.summary === "string"
|
|
279
|
+
? output.summary
|
|
280
|
+
: "Host integration returned a balanced card row.",
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function readCards(value: unknown): LayoutCardSuggestion[] {
|
|
285
|
+
if (!Array.isArray(value)) return [];
|
|
286
|
+
return value
|
|
287
|
+
.map((candidate, index) => readCard(candidate, index))
|
|
288
|
+
.filter((card): card is LayoutCardSuggestion => card !== null);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function readCard(value: unknown, index: number): LayoutCardSuggestion | null {
|
|
292
|
+
const record = readRecord(value);
|
|
293
|
+
if (!record) return null;
|
|
294
|
+
const position = readVec2(record.position);
|
|
295
|
+
const size = readSize(record.size);
|
|
296
|
+
if (!position || !size) return null;
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
name:
|
|
300
|
+
typeof record.name === "string" && record.name.length > 0
|
|
301
|
+
? record.name
|
|
302
|
+
: `AI Layout Assistant Card ${String.fromCharCode(65 + index)}`,
|
|
303
|
+
position,
|
|
304
|
+
size,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function readRecord(value: unknown): Record<string, unknown> | null {
|
|
309
|
+
if (value == null || typeof value !== "object" || Array.isArray(value)) {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
return value as Record<string, unknown>;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function readVec2(value: unknown): Vec2 | null {
|
|
316
|
+
const record = readRecord(value);
|
|
317
|
+
if (!record) return null;
|
|
318
|
+
if (typeof record.x !== "number" || typeof record.y !== "number") {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
return { x: record.x, y: record.y };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function readSize(value: unknown): Size | null {
|
|
325
|
+
const record = readRecord(value);
|
|
326
|
+
if (!record) return null;
|
|
327
|
+
if (
|
|
328
|
+
typeof record.width !== "number" ||
|
|
329
|
+
typeof record.height !== "number"
|
|
330
|
+
) {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
return { height: record.height, width: record.width };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function collectNodes(
|
|
337
|
+
node: SceneNodeSnapshot,
|
|
338
|
+
out: SceneNodeSnapshot[] = [],
|
|
339
|
+
): SceneNodeSnapshot[] {
|
|
340
|
+
out.push(node);
|
|
341
|
+
for (const child of node.children) {
|
|
342
|
+
collectNodes(child, out);
|
|
343
|
+
}
|
|
344
|
+
return out;
|
|
345
|
+
}
|
|
@@ -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,114 @@
|
|
|
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>AI Layout Assistant</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
|
+
.actions {
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-wrap: wrap;
|
|
27
|
+
gap: 0.5rem;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.status {
|
|
31
|
+
min-height: 1.5rem;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
pre {
|
|
35
|
+
max-height: 16rem;
|
|
36
|
+
overflow: auto;
|
|
37
|
+
padding: 0.75rem;
|
|
38
|
+
color: CanvasText;
|
|
39
|
+
background: Canvas;
|
|
40
|
+
border: 1px solid ButtonBorder;
|
|
41
|
+
}
|
|
42
|
+
</style>
|
|
43
|
+
</head>
|
|
44
|
+
<body>
|
|
45
|
+
<main>
|
|
46
|
+
<header>
|
|
47
|
+
<h1>AI Layout Assistant</h1>
|
|
48
|
+
<p>Request a host AI layout suggestion and apply it to the canvas.</p>
|
|
49
|
+
</header>
|
|
50
|
+
|
|
51
|
+
<div class="actions">
|
|
52
|
+
<button id="analyze-button" type="button">Analyze layout</button>
|
|
53
|
+
<button id="apply-button" type="button">Apply suggestion</button>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<p id="status" class="status" role="status">Waiting for host...</p>
|
|
57
|
+
<pre id="details" aria-label="Layout details"></pre>
|
|
58
|
+
</main>
|
|
59
|
+
|
|
60
|
+
<script>
|
|
61
|
+
const uiId = globalThis.__LOVISION_PLUGIN_UI_ID__;
|
|
62
|
+
const status = document.getElementById("status");
|
|
63
|
+
const details = document.getElementById("details");
|
|
64
|
+
|
|
65
|
+
function post(type, payload) {
|
|
66
|
+
parent.postMessage(
|
|
67
|
+
{
|
|
68
|
+
type: "plugin-ui-message",
|
|
69
|
+
direction: "ui-to-main",
|
|
70
|
+
uiId,
|
|
71
|
+
message: { type, payload },
|
|
72
|
+
},
|
|
73
|
+
"*",
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
document.getElementById("analyze-button").addEventListener("click", () => {
|
|
78
|
+
status.textContent = "Analyzing layout...";
|
|
79
|
+
details.textContent = "";
|
|
80
|
+
post("analyze-layout", {});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
document.getElementById("apply-button").addEventListener("click", () => {
|
|
84
|
+
status.textContent = "Applying suggestion...";
|
|
85
|
+
post("apply-layout", {});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
window.addEventListener("message", (event) => {
|
|
89
|
+
const envelope = event.data;
|
|
90
|
+
if (
|
|
91
|
+
!envelope ||
|
|
92
|
+
envelope.type !== "plugin-ui-message" ||
|
|
93
|
+
envelope.direction !== "main-to-ui" ||
|
|
94
|
+
envelope.uiId !== uiId
|
|
95
|
+
) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const message = envelope.message;
|
|
100
|
+
if (message.type === "ready") {
|
|
101
|
+
status.textContent = message.payload.message;
|
|
102
|
+
} else if (message.type === "analysis") {
|
|
103
|
+
status.textContent = `Received ${message.payload.cards.length} layout suggestion card(s).`;
|
|
104
|
+
details.textContent = JSON.stringify(message.payload, null, 2);
|
|
105
|
+
} else if (message.type === "applied") {
|
|
106
|
+
status.textContent = `Applied ${message.payload.cardCount} layout suggestion card(s).`;
|
|
107
|
+
details.textContent = JSON.stringify(message.payload, null, 2);
|
|
108
|
+
} else if (message.type === "error") {
|
|
109
|
+
status.textContent = message.payload.message;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
</script>
|
|
113
|
+
</body>
|
|
114
|
+
</html>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Asset Browser
|
|
2
|
+
|
|
3
|
+
This Step 31 acceptance template inserts generated image assets through the plugin main worker. The UI sends asset intent only; the worker creates a command-scoped binary resource with `ctx.binary.create` and inserts it as an image node with `ctx.assets.insertImage`.
|
|
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 Asset Browser**, then click **Insert hero asset** or **Insert badge asset**. The inserted node is an image node named after the selected asset and carries plugin binary metadata in the document snapshot.
|
|
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,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "__PLUGIN_ID__",
|
|
3
|
+
"name": {
|
|
4
|
+
"en": "Asset Browser",
|
|
5
|
+
"zh-CN": "Asset Browser"
|
|
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
|
+
"asset:write",
|
|
15
|
+
"binary:write",
|
|
16
|
+
"document:read",
|
|
17
|
+
"notify",
|
|
18
|
+
"ui:panel"
|
|
19
|
+
],
|
|
20
|
+
"commands": [
|
|
21
|
+
{
|
|
22
|
+
"id": "run",
|
|
23
|
+
"name": {
|
|
24
|
+
"en": "__COMMAND_NAME_EN__",
|
|
25
|
+
"zh-CN": "__COMMAND_NAME_ZH__"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
@@ -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
|
+
}
|