@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,221 @@
|
|
|
1
|
+
import { definePlugin } from "@lovision/plugin-sdk";
|
|
2
|
+
|
|
3
|
+
type DesignLintFinding = {
|
|
4
|
+
message: string;
|
|
5
|
+
nodeId: string;
|
|
6
|
+
rule: "unnamed-node" | "empty-text-node" | "non-positive-size";
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type SceneNodeSnapshot = {
|
|
10
|
+
children: SceneNodeSnapshot[];
|
|
11
|
+
extraFields?: Record<string, unknown>;
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
size: {
|
|
15
|
+
height: number;
|
|
16
|
+
width: number;
|
|
17
|
+
};
|
|
18
|
+
type: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type SceneSnapshot = {
|
|
22
|
+
root: SceneNodeSnapshot;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type LintMessage =
|
|
26
|
+
| {
|
|
27
|
+
type: "findings";
|
|
28
|
+
payload: {
|
|
29
|
+
findings: DesignLintFinding[];
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
| {
|
|
33
|
+
type: "error";
|
|
34
|
+
payload: {
|
|
35
|
+
message: string;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
| {
|
|
39
|
+
type: "ready";
|
|
40
|
+
payload: {
|
|
41
|
+
message: string;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const DEFAULT_LABELS = new Set(["Rectangle", "Frame", "Text"]);
|
|
46
|
+
|
|
47
|
+
definePlugin({
|
|
48
|
+
apiVersion: "1.0",
|
|
49
|
+
version: "0.1.0",
|
|
50
|
+
command: async (ctx) => {
|
|
51
|
+
const session = await ctx.ui.show({
|
|
52
|
+
mode: "panel",
|
|
53
|
+
entry: "./ui.html",
|
|
54
|
+
title: "Design Lint Panel",
|
|
55
|
+
width: 420,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
let closed = false;
|
|
59
|
+
let latestFindings: DesignLintFinding[] = [];
|
|
60
|
+
const panelClosed = new Promise<void>((resolve) => {
|
|
61
|
+
session.on("close", () => {
|
|
62
|
+
closed = true;
|
|
63
|
+
void ctx.notify.send("Design Lint Panel closed.", { kind: "info" });
|
|
64
|
+
resolve();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const postToUi = async (message: LintMessage): Promise<void> => {
|
|
69
|
+
if (closed) return;
|
|
70
|
+
await session.postMessage(message);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const runLint = async (): Promise<DesignLintFinding[]> => {
|
|
74
|
+
const snapshot = (await ctx.document.snapshot()) as SceneSnapshot;
|
|
75
|
+
latestFindings = lintSnapshot(snapshot);
|
|
76
|
+
await postToUi({
|
|
77
|
+
type: "findings",
|
|
78
|
+
payload: { findings: latestFindings },
|
|
79
|
+
});
|
|
80
|
+
await ctx.notify.send(
|
|
81
|
+
`Design Lint Panel found ${latestFindings.length} issue${
|
|
82
|
+
latestFindings.length === 1 ? "" : "s"
|
|
83
|
+
}.`,
|
|
84
|
+
{ kind: latestFindings.length > 0 ? "warning" : "success" },
|
|
85
|
+
);
|
|
86
|
+
return latestFindings;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
session.on("run-lint", async () => {
|
|
90
|
+
await handleUiAction(postToUi, runLint);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
session.on("focus-first-issue", async () => {
|
|
94
|
+
await handleUiAction(postToUi, async () => {
|
|
95
|
+
const findings =
|
|
96
|
+
latestFindings.length > 0 ? latestFindings : await runLint();
|
|
97
|
+
const firstFinding = findings[0];
|
|
98
|
+
if (!firstFinding) {
|
|
99
|
+
await ctx.notify.send("Design Lint Panel has no issue to focus.", {
|
|
100
|
+
kind: "info",
|
|
101
|
+
});
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
await ctx.viewport.focusNode(firstFinding.nodeId);
|
|
105
|
+
await ctx.notify.send(
|
|
106
|
+
`Design Lint Panel focused ${firstFinding.nodeId}.`,
|
|
107
|
+
{ kind: "info" },
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
session.on("fix-default-names", async () => {
|
|
113
|
+
await handleUiAction(postToUi, async () => {
|
|
114
|
+
const findings =
|
|
115
|
+
latestFindings.length > 0 ? latestFindings : await runLint();
|
|
116
|
+
const nodeIds = findings
|
|
117
|
+
.filter((finding) => finding.rule === "unnamed-node")
|
|
118
|
+
.map((finding) => finding.nodeId);
|
|
119
|
+
if (nodeIds.length === 0) {
|
|
120
|
+
await ctx.notify.send("Design Lint Panel found no default names.", {
|
|
121
|
+
kind: "info",
|
|
122
|
+
});
|
|
123
|
+
await runLint();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
await ctx.nodes.rename(nodeIds, { prefix: "Lint/" });
|
|
127
|
+
await ctx.notify.send(
|
|
128
|
+
`Design Lint Panel fixed ${nodeIds.length} default-named node(s).`,
|
|
129
|
+
{ kind: "success" },
|
|
130
|
+
);
|
|
131
|
+
await runLint();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
await postToUi({
|
|
136
|
+
type: "ready",
|
|
137
|
+
payload: {
|
|
138
|
+
message:
|
|
139
|
+
"Ready. Run lint to scan default names, empty text, and non-positive sizes.",
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
await panelClosed;
|
|
144
|
+
return { closed: true, ok: true };
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
async function handleUiAction(
|
|
149
|
+
postToUi: (message: LintMessage) => Promise<void>,
|
|
150
|
+
action: () => Promise<unknown>,
|
|
151
|
+
): Promise<void> {
|
|
152
|
+
try {
|
|
153
|
+
await action();
|
|
154
|
+
} catch (error) {
|
|
155
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
156
|
+
await postToUi({ type: "error", payload: { message } });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function lintSnapshot(snapshot: SceneSnapshot): DesignLintFinding[] {
|
|
161
|
+
const findings: DesignLintFinding[] = [];
|
|
162
|
+
for (const node of collectNodes(snapshot.root)) {
|
|
163
|
+
const isRootNode = node.id === snapshot.root.id;
|
|
164
|
+
const trimmedName = node.name.trim();
|
|
165
|
+
if (
|
|
166
|
+
!isRootNode &&
|
|
167
|
+
(trimmedName.length === 0 || DEFAULT_LABELS.has(trimmedName))
|
|
168
|
+
) {
|
|
169
|
+
findings.push({
|
|
170
|
+
nodeId: node.id,
|
|
171
|
+
rule: "unnamed-node",
|
|
172
|
+
message: "Node name is blank or default.",
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (node.type === "text" && isBlankText(getTextContent(node))) {
|
|
177
|
+
findings.push({
|
|
178
|
+
nodeId: node.id,
|
|
179
|
+
rule: "empty-text-node",
|
|
180
|
+
message: "Text node content is empty.",
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (!isRootNode && (node.size.width <= 0 || node.size.height <= 0)) {
|
|
185
|
+
findings.push({
|
|
186
|
+
nodeId: node.id,
|
|
187
|
+
rule: "non-positive-size",
|
|
188
|
+
message: "Node size must be positive.",
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return findings;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function collectNodes(
|
|
196
|
+
node: SceneNodeSnapshot,
|
|
197
|
+
out: SceneNodeSnapshot[] = [],
|
|
198
|
+
): SceneNodeSnapshot[] {
|
|
199
|
+
out.push(node);
|
|
200
|
+
for (const child of node.children) {
|
|
201
|
+
collectNodes(child, out);
|
|
202
|
+
}
|
|
203
|
+
return out;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function getTextContent(node: SceneNodeSnapshot): string | undefined {
|
|
207
|
+
const data = node.extraFields?.data;
|
|
208
|
+
if (!isRecord(data)) return undefined;
|
|
209
|
+
const config = data.config;
|
|
210
|
+
if (!isRecord(config)) return undefined;
|
|
211
|
+
const text = config.text;
|
|
212
|
+
return typeof text === "string" ? text : undefined;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function isBlankText(value: unknown): boolean {
|
|
216
|
+
return typeof value === "string" && value.trim().length === 0;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
220
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
221
|
+
}
|
|
@@ -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,172 @@
|
|
|
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>Design Lint Panel</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
|
+
header {
|
|
25
|
+
display: grid;
|
|
26
|
+
gap: 0.35rem;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
h1,
|
|
30
|
+
p {
|
|
31
|
+
margin: 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.actions {
|
|
35
|
+
display: flex;
|
|
36
|
+
flex-wrap: wrap;
|
|
37
|
+
gap: 0.5rem;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.status {
|
|
41
|
+
min-height: 1.5rem;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.findings {
|
|
45
|
+
display: grid;
|
|
46
|
+
gap: 0.5rem;
|
|
47
|
+
margin: 0;
|
|
48
|
+
padding: 0;
|
|
49
|
+
list-style: none;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.finding {
|
|
53
|
+
display: grid;
|
|
54
|
+
gap: 0.25rem;
|
|
55
|
+
padding: 0.75rem;
|
|
56
|
+
border: 1px solid ButtonBorder;
|
|
57
|
+
border-radius: 0.5rem;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.finding code {
|
|
61
|
+
overflow-wrap: anywhere;
|
|
62
|
+
}
|
|
63
|
+
</style>
|
|
64
|
+
</head>
|
|
65
|
+
<body>
|
|
66
|
+
<main>
|
|
67
|
+
<header>
|
|
68
|
+
<h1>Design Lint Panel</h1>
|
|
69
|
+
<p>
|
|
70
|
+
Scan the current page for default names, empty text, and invalid
|
|
71
|
+
sizes. Fixes run in the main worker.
|
|
72
|
+
</p>
|
|
73
|
+
</header>
|
|
74
|
+
|
|
75
|
+
<div class="actions">
|
|
76
|
+
<button id="run-lint" type="button">Run lint</button>
|
|
77
|
+
<button id="focus-first-issue" type="button">Focus first issue</button>
|
|
78
|
+
<button id="fix-default-names" type="button">Fix default names</button>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<p id="status" class="status" role="status">Waiting for host...</p>
|
|
82
|
+
<ul id="findings" class="findings" aria-label="Findings">
|
|
83
|
+
<li>No issues yet. Run lint to scan the document.</li>
|
|
84
|
+
</ul>
|
|
85
|
+
</main>
|
|
86
|
+
|
|
87
|
+
<script>
|
|
88
|
+
const uiId = globalThis.__LOVISION_PLUGIN_UI_ID__;
|
|
89
|
+
const status = document.getElementById("status");
|
|
90
|
+
const findingsList = document.getElementById("findings");
|
|
91
|
+
|
|
92
|
+
function post(type, payload) {
|
|
93
|
+
parent.postMessage(
|
|
94
|
+
{
|
|
95
|
+
type: "plugin-ui-message",
|
|
96
|
+
direction: "ui-to-main",
|
|
97
|
+
uiId,
|
|
98
|
+
message: { type, payload },
|
|
99
|
+
},
|
|
100
|
+
"*",
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function setFindings(findings) {
|
|
105
|
+
findingsList.textContent = "";
|
|
106
|
+
if (!Array.isArray(findings) || findings.length === 0) {
|
|
107
|
+
const empty = document.createElement("li");
|
|
108
|
+
empty.textContent = "No issues";
|
|
109
|
+
findingsList.append(empty);
|
|
110
|
+
status.textContent = "Found 0 issues.";
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
for (const finding of findings) {
|
|
115
|
+
const item = document.createElement("li");
|
|
116
|
+
item.className = "finding";
|
|
117
|
+
const title = document.createElement("strong");
|
|
118
|
+
title.textContent = finding.rule || "issue";
|
|
119
|
+
const nodeId = document.createElement("code");
|
|
120
|
+
nodeId.textContent = finding.nodeId || "unknown-node";
|
|
121
|
+
const message = document.createElement("span");
|
|
122
|
+
message.textContent = finding.message || "";
|
|
123
|
+
item.append(title, nodeId, message);
|
|
124
|
+
findingsList.append(item);
|
|
125
|
+
}
|
|
126
|
+
status.textContent = `Found ${findings.length} issue${
|
|
127
|
+
findings.length === 1 ? "" : "s"
|
|
128
|
+
}.`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
document.getElementById("run-lint").addEventListener("click", () => {
|
|
132
|
+
status.textContent = "Running lint...";
|
|
133
|
+
post("run-lint", {});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
document
|
|
137
|
+
.getElementById("focus-first-issue")
|
|
138
|
+
.addEventListener("click", () => {
|
|
139
|
+
status.textContent = "Focusing first issue...";
|
|
140
|
+
post("focus-first-issue", {});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
document
|
|
144
|
+
.getElementById("fix-default-names")
|
|
145
|
+
.addEventListener("click", () => {
|
|
146
|
+
status.textContent = "Fixing default names...";
|
|
147
|
+
post("fix-default-names", {});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
window.addEventListener("message", (event) => {
|
|
151
|
+
const envelope = event.data;
|
|
152
|
+
if (
|
|
153
|
+
!envelope ||
|
|
154
|
+
envelope.type !== "plugin-ui-message" ||
|
|
155
|
+
envelope.direction !== "main-to-ui" ||
|
|
156
|
+
envelope.uiId !== uiId
|
|
157
|
+
) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const message = envelope.message;
|
|
162
|
+
if (message.type === "ready") {
|
|
163
|
+
status.textContent = message.payload.message;
|
|
164
|
+
} else if (message.type === "findings") {
|
|
165
|
+
setFindings(message.payload.findings);
|
|
166
|
+
} else if (message.type === "error") {
|
|
167
|
+
status.textContent = message.payload.message;
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
</script>
|
|
171
|
+
</body>
|
|
172
|
+
</html>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Export Selection
|
|
2
|
+
|
|
3
|
+
This acceptance template exports the current object selection, or the first node in the scene when nothing is selected. The plugin main worker calls `ctx.export.selection` without forcing `nodeIds` first, so host-side vector edit selections can normalize back to the edited object. If there is no editor selection, the template falls back to the first canvas node.
|
|
4
|
+
|
|
5
|
+
JSON and SVG are downloadable inline artifacts. PNG selection export is reserved for clean selection-bounds raster output; until the host supports that semantic, the UI reports it as unsupported instead of downloading a misleading whole-canvas screenshot. `ctx.export.screenshot` remains available as **Capture Screen Preview** and returns a screen-pixel binary resource that can be read with `ctx.binary.read`.
|
|
6
|
+
|
|
7
|
+
## Development
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install
|
|
11
|
+
npm run dev
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Copy the printed `manifestUrl=...`, open the editor shell, then use Dev Panel -> Development -> Add by URL.
|
|
15
|
+
|
|
16
|
+
## Use
|
|
17
|
+
|
|
18
|
+
Run **Open Export Selection**, then click **Export JSON Snapshot**, **Export SVG Paths**, **Export PNG Selection**, or **Capture Screen Preview**. JSON shows `kind`, bounds, node count, and vector command count. SVG shows path and fallback counts. Screen Preview shows binary resource metadata after the worker reads bytes through `ctx.binary.read`. After each successful export, click **Download ...** to save the generated file through the editor shell.
|
|
19
|
+
|
|
20
|
+
## Formal install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm run build
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
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": "Export Selection",
|
|
5
|
+
"zh-CN": "Export Selection"
|
|
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
|
+
"binary:read",
|
|
15
|
+
"document:read",
|
|
16
|
+
"export:screenshot",
|
|
17
|
+
"export:selection",
|
|
18
|
+
"notify",
|
|
19
|
+
"selection:read",
|
|
20
|
+
"ui:modal"
|
|
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
|
+
}
|