@taboo-avalanche/andesite-compiler 1.0.0 → 1.0.2
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/dist/compile.js +82 -10
- package/package.json +2 -2
- package/src/compile.ts +2 -1
- package/src/rasterize/puppeteer.ts +98 -14
package/dist/compile.js
CHANGED
|
@@ -53,6 +53,8 @@ async function rasterizeAllInBrowser(browser, pageUrl, outputDir, options) {
|
|
|
53
53
|
const kind = await page.$eval("[data-andesite-canvas]", (node) => {
|
|
54
54
|
return node.getAttribute("data-andesite-page-kind") === "hud" ? "hud" : "menu";
|
|
55
55
|
});
|
|
56
|
+
const mask = await page.$eval("[data-andesite-canvas]", (node) => node.getAttribute("data-andesite-mask"));
|
|
57
|
+
await page.$$eval("[data-andesite-screen-touch-mask]", (nodes) => nodes.forEach((node) => node.style.display = "none"));
|
|
56
58
|
const pageBox = await page.$eval("[data-andesite-content-root]", (node) => {
|
|
57
59
|
const rect = node.getBoundingClientRect();
|
|
58
60
|
return { width: Math.round(rect.width), height: Math.round(rect.height) };
|
|
@@ -92,6 +94,43 @@ async function rasterizeAllInBrowser(browser, pageUrl, outputDir, options) {
|
|
|
92
94
|
}
|
|
93
95
|
const mounts = kind === "hud" ? await collectHudMounts(page) : [];
|
|
94
96
|
const viewStacks = [];
|
|
97
|
+
for (const dialog of await page.$$('[data-andesite-type="dialog"]')) {
|
|
98
|
+
const dialogId = await dialog.evaluate((node) => node.getAttribute("data-andesite-id"));
|
|
99
|
+
const defaultVisible = await readDefaultVisible(dialog);
|
|
100
|
+
const openViewEl = await dialog.$('[data-andesite-view-name="open"]');
|
|
101
|
+
const box = openViewEl ? await measureAndesiteBox(openViewEl) : await measureAndesiteBox(dialog);
|
|
102
|
+
if (box.width <= 0 || box.height <= 0) throw new Error(`Collapsed ADialog: ${dialogId}`);
|
|
103
|
+
const controls = await dialog.evaluate(
|
|
104
|
+
(node) => Array.from(node.querySelectorAll('[data-andesite-view-name="open"] [data-andesite-id]')).map((child) => child.getAttribute("data-andesite-id"))
|
|
105
|
+
);
|
|
106
|
+
const openViewId = `${dialogId}:open`;
|
|
107
|
+
const closedViewId = `${dialogId}:closed`;
|
|
108
|
+
const isOpen = defaultVisible !== false;
|
|
109
|
+
viewStacks.push({
|
|
110
|
+
id: dialogId,
|
|
111
|
+
defaultView: isOpen ? "open" : "closed",
|
|
112
|
+
views: [
|
|
113
|
+
{
|
|
114
|
+
id: closedViewId,
|
|
115
|
+
name: "closed",
|
|
116
|
+
x: box.x,
|
|
117
|
+
y: box.y,
|
|
118
|
+
width: box.width,
|
|
119
|
+
height: box.height,
|
|
120
|
+
controls: []
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: openViewId,
|
|
124
|
+
name: "open",
|
|
125
|
+
x: box.x,
|
|
126
|
+
y: box.y,
|
|
127
|
+
width: box.width,
|
|
128
|
+
height: box.height,
|
|
129
|
+
controls: [openViewId + "/__background", ...controls]
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
});
|
|
133
|
+
}
|
|
95
134
|
for (const stack of await page.$$("[data-andesite-viewstack]")) {
|
|
96
135
|
const info = await stack.evaluate((node) => ({ id: node.getAttribute("data-andesite-id"), defaultView: node.getAttribute("data-andesite-default-view") }));
|
|
97
136
|
const views = [];
|
|
@@ -113,7 +152,7 @@ async function rasterizeAllInBrowser(browser, pageUrl, outputDir, options) {
|
|
|
113
152
|
const inputs = await rasterizeTextInputs(page, outputDir);
|
|
114
153
|
const backgroundPanels = kind === "hud" ? await rasterizeHudStaticBackgrounds(page, outputDir) : await rasterizeStaticBackground(page, outputDir);
|
|
115
154
|
const panels = await rasterizePanels(page, outputDir);
|
|
116
|
-
const buttons = await rasterizeButtons(page, outputDir);
|
|
155
|
+
const buttons = await rasterizeButtons(page, outputDir, viewStacks);
|
|
117
156
|
const progresses = await rasterizeProgresses(page, outputDir);
|
|
118
157
|
const sprites = await rasterizeSprites(page, outputDir);
|
|
119
158
|
const scrollViews = await rasterizeScrollViews(page, outputDir);
|
|
@@ -123,13 +162,14 @@ async function rasterizeAllInBrowser(browser, pageUrl, outputDir, options) {
|
|
|
123
162
|
for (const stack of viewStacks) for (const view of stack.views) {
|
|
124
163
|
view.controls = view.controls.filter((id) => exportedIds.has(id));
|
|
125
164
|
for (const control of exported.filter((control2) => view.controls.includes(control2.id))) {
|
|
126
|
-
|
|
127
|
-
if (
|
|
165
|
+
const isDialogMask = control.id.endsWith("_mask");
|
|
166
|
+
if (!isDialogMask && (control.width <= 0 || control.height <= 0)) throw new Error(`Collapsed AView control: ${control.id}`);
|
|
167
|
+
if (!isDialogMask && kind !== "hud" && (control.x < -1 || control.y < -1 || control.x + control.width > pageBox.width + 1 || control.y + control.height > pageBox.height + 1)) {
|
|
128
168
|
throw new Error(`AView control outside page: ${control.id}`);
|
|
129
169
|
}
|
|
130
170
|
}
|
|
131
171
|
}
|
|
132
|
-
return { kind, width: pageBox.width, height: pageBox.height, mounts, viewStacks, panels: [...backgroundPanels, ...panels], buttons, labels, inputs, slots, progresses, sprites, scrollViews };
|
|
172
|
+
return { kind, mask, width: pageBox.width, height: pageBox.height, mounts, viewStacks, panels: [...backgroundPanels, ...panels], buttons, labels, inputs, slots, progresses, sprites, scrollViews };
|
|
133
173
|
} finally {
|
|
134
174
|
await page.close();
|
|
135
175
|
}
|
|
@@ -748,7 +788,7 @@ async function rasterizePanels(page, outputDir) {
|
|
|
748
788
|
}
|
|
749
789
|
return results;
|
|
750
790
|
}
|
|
751
|
-
async function rasterizeButtons(page, outputDir) {
|
|
791
|
+
async function rasterizeButtons(page, outputDir, viewStacks) {
|
|
752
792
|
const elements = await page.$$('[data-andesite-type="button"]');
|
|
753
793
|
const results = [];
|
|
754
794
|
for (const el of elements) {
|
|
@@ -758,7 +798,20 @@ async function rasterizeButtons(page, outputDir) {
|
|
|
758
798
|
const id = await el.evaluate((node) => node.getAttribute("data-andesite-id") ?? "");
|
|
759
799
|
const mountId = await collectHudMountId(el);
|
|
760
800
|
const defaultVisible = await readDefaultVisible(el);
|
|
761
|
-
const
|
|
801
|
+
const isDialogMask = id.endsWith("_mask");
|
|
802
|
+
let box;
|
|
803
|
+
if (isDialogMask) {
|
|
804
|
+
const dialogId = id.replace(/_mask$/, "");
|
|
805
|
+
const dialogStack = viewStacks.find((s) => s.id === dialogId);
|
|
806
|
+
const openView = dialogStack?.views.find((v) => v.name === "open");
|
|
807
|
+
if (openView) {
|
|
808
|
+
box = { x: openView.x, y: openView.y, width: openView.width, height: openView.height };
|
|
809
|
+
} else {
|
|
810
|
+
box = await measureAndesiteBox(el);
|
|
811
|
+
}
|
|
812
|
+
} else {
|
|
813
|
+
box = await measureAndesiteBox(el);
|
|
814
|
+
}
|
|
762
815
|
const pressDuration = Number(await el.evaluate((node) => node.getAttribute("data-andesite-press-duration") ?? "2"));
|
|
763
816
|
const clickable = await collectButtonClickable(el);
|
|
764
817
|
const layer = optionalNumber(await el.evaluate((node) => node.getAttribute("data-andesite-layer")));
|
|
@@ -786,10 +839,16 @@ async function rasterizeButtons(page, outputDir) {
|
|
|
786
839
|
frames.push(screenshot);
|
|
787
840
|
}
|
|
788
841
|
if (frames.length === 2) {
|
|
789
|
-
const
|
|
842
|
+
const isDialogMask2 = id.endsWith("_mask");
|
|
843
|
+
let outputPng;
|
|
844
|
+
if (isDialogMask2) {
|
|
845
|
+
outputPng = frames[0];
|
|
846
|
+
} else {
|
|
847
|
+
outputPng = await combinePngsVertically(page, frames);
|
|
848
|
+
}
|
|
790
849
|
const outputPath = path.join(outputDir, texturePath);
|
|
791
850
|
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
792
|
-
fs.writeFileSync(outputPath,
|
|
851
|
+
fs.writeFileSync(outputPath, outputPng);
|
|
793
852
|
results.push({
|
|
794
853
|
id,
|
|
795
854
|
mountId,
|
|
@@ -925,13 +984,25 @@ async function captureButtonPng(page, el) {
|
|
|
925
984
|
wrapper.style.zIndex = "2147483647";
|
|
926
985
|
const rect = node.getBoundingClientRect();
|
|
927
986
|
const clone = node.cloneNode(true);
|
|
987
|
+
const isDialogMask = node.getAttribute("data-andesite-id")?.endsWith("_mask");
|
|
988
|
+
let captureWidth = Math.max(1, Math.round(rect.width));
|
|
989
|
+
let captureHeight = Math.max(1, Math.round(rect.height));
|
|
990
|
+
if (isDialogMask) {
|
|
991
|
+
const dialogId = node.getAttribute("data-andesite-id")?.replace(/_mask$/, "");
|
|
992
|
+
const viewStackEl = document.querySelector(`[data-andesite-viewstack="${dialogId}"]`);
|
|
993
|
+
if (viewStackEl) {
|
|
994
|
+
const stackRect = viewStackEl.getBoundingClientRect();
|
|
995
|
+
captureWidth = Math.max(1, Math.round(stackRect.width));
|
|
996
|
+
captureHeight = Math.max(1, Math.round(stackRect.height));
|
|
997
|
+
}
|
|
998
|
+
}
|
|
928
999
|
clone.style.position = "relative";
|
|
929
1000
|
clone.style.left = "0";
|
|
930
1001
|
clone.style.top = "0";
|
|
931
1002
|
clone.style.right = "auto";
|
|
932
1003
|
clone.style.bottom = "auto";
|
|
933
|
-
clone.style.width = `${
|
|
934
|
-
clone.style.height = `${
|
|
1004
|
+
clone.style.width = `${captureWidth}px`;
|
|
1005
|
+
clone.style.height = `${captureHeight}px`;
|
|
935
1006
|
clone.style.margin = "0";
|
|
936
1007
|
const cloneBoxShadow = getComputedStyle(node).boxShadow;
|
|
937
1008
|
clone.style.boxShadow = cloneBoxShadow;
|
|
@@ -2023,6 +2094,7 @@ async function compilePage(pageUrl, pageId, outputZipPath, options) {
|
|
|
2023
2094
|
width: rasterResult.width,
|
|
2024
2095
|
height: rasterResult.height,
|
|
2025
2096
|
anchor: "center",
|
|
2097
|
+
mask: rasterResult.mask,
|
|
2026
2098
|
...pageControls,
|
|
2027
2099
|
slots: rasterResult.slots,
|
|
2028
2100
|
progresses: rasterResult.progresses,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taboo-avalanche/andesite-compiler",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Rasterize Andesite React pages to Bedrock chest UI zip",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"archiver": "^7.0.1",
|
|
20
20
|
"puppeteer": "^23.0.0",
|
|
21
|
-
"@taboo-avalanche/andesite": "1.0.
|
|
21
|
+
"@taboo-avalanche/andesite": "1.0.3"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/node": "^26.6.2",
|
package/src/compile.ts
CHANGED
|
@@ -89,7 +89,8 @@ export async function compilePage(
|
|
|
89
89
|
// 页面整体尺寸与锚点:anchor=center 时运行时按屏幕把整体包围盒居中,解决左上锚定导致的偏移
|
|
90
90
|
width: rasterResult.width,
|
|
91
91
|
height: rasterResult.height,
|
|
92
|
-
anchor: 'center',
|
|
92
|
+
anchor: 'center',
|
|
93
|
+
mask: rasterResult.mask,
|
|
93
94
|
...pageControls,
|
|
94
95
|
slots: rasterResult.slots,
|
|
95
96
|
progresses: rasterResult.progresses,
|
|
@@ -68,8 +68,9 @@ interface RepeaterCellBox extends DomBox {
|
|
|
68
68
|
index: number
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
export interface AndesiteRasterResult {
|
|
72
|
-
kind: 'menu' | 'hud'
|
|
71
|
+
export interface AndesiteRasterResult {
|
|
72
|
+
kind: 'menu' | 'hud'
|
|
73
|
+
mask: string | null
|
|
73
74
|
// 页面整体包围盒(内容根 px),供运行时居中摆放
|
|
74
75
|
width: number
|
|
75
76
|
height: number
|
|
@@ -143,7 +144,10 @@ export async function rasterizeAllInBrowser(
|
|
|
143
144
|
const kind: AndesiteRasterResult['kind'] = await page.$eval('[data-andesite-canvas]', node => {
|
|
144
145
|
return (node as HTMLElement).getAttribute('data-andesite-page-kind') === 'hud' ? 'hud' : 'menu'
|
|
145
146
|
})
|
|
146
|
-
|
|
147
|
+
const mask = await page.$eval('[data-andesite-canvas]', node => node.getAttribute('data-andesite-mask'))
|
|
148
|
+
// 全屏遮罩由导入器单独创建,采集内容贴图时移除其像素,避免半透明背景重复叠色。
|
|
149
|
+
await page.$$eval('[data-andesite-screen-touch-mask]', nodes => nodes.forEach(node => (node as HTMLElement).style.display = 'none'))
|
|
150
|
+
// 页面整体包围盒:菜单以内容根居中;HUD 控件另按最近 mount 作为局部坐标基准。
|
|
147
151
|
const pageBox = await page.$eval('[data-andesite-content-root]', node => {
|
|
148
152
|
const rect = (node as HTMLElement).getBoundingClientRect()
|
|
149
153
|
return { width: Math.round(rect.width), height: Math.round(rect.height) }
|
|
@@ -198,6 +202,49 @@ export async function rasterizeAllInBrowser(
|
|
|
198
202
|
}
|
|
199
203
|
const mounts = kind === 'hud' ? await collectHudMounts(page) : []
|
|
200
204
|
const viewStacks: ViewStackDecl[] = []
|
|
205
|
+
// ADialog 弹窗:导出为 viewStack(id=弹窗id),含 closed/open 两个视图。
|
|
206
|
+
// closed 是空视图(弹窗关闭状态),open 包含遮罩与内容面板,随视图根整体开合。
|
|
207
|
+
for (const dialog of await page.$$('[data-andesite-type="dialog"]')) {
|
|
208
|
+
const dialogId = await dialog.evaluate(node => node.getAttribute('data-andesite-id')!)
|
|
209
|
+
const defaultVisible = await readDefaultVisible(dialog)
|
|
210
|
+
// 测量 open 视图(实际弹窗内容)的尺寸,而非 dialog 根节点(可能是 closed 空视图)
|
|
211
|
+
const openViewEl = await dialog.$('[data-andesite-view-name="open"]')
|
|
212
|
+
const box = openViewEl ? await measureAndesiteBox(openViewEl) : await measureAndesiteBox(dialog)
|
|
213
|
+
if (box.width <= 0 || box.height <= 0) throw new Error(`Collapsed ADialog: ${dialogId}`)
|
|
214
|
+
const controls = await dialog.evaluate(node =>
|
|
215
|
+
Array.from(node.querySelectorAll('[data-andesite-view-name="open"] [data-andesite-id]'))
|
|
216
|
+
.map(child => child.getAttribute('data-andesite-id')!),
|
|
217
|
+
)
|
|
218
|
+
// 弹窗遮罩(AButton clickable=false)与内容面板内控件都归入 open 视图。
|
|
219
|
+
// defaultVisible=false 时弹窗初始隐藏,对应 defaultView=closed;否则 defaultView=open。
|
|
220
|
+
const openViewId = `${dialogId}:open`
|
|
221
|
+
const closedViewId = `${dialogId}:closed`
|
|
222
|
+
const isOpen = defaultVisible !== false
|
|
223
|
+
viewStacks.push({
|
|
224
|
+
id: dialogId,
|
|
225
|
+
defaultView: isOpen ? 'open' : 'closed',
|
|
226
|
+
views: [
|
|
227
|
+
{
|
|
228
|
+
id: closedViewId,
|
|
229
|
+
name: 'closed',
|
|
230
|
+
x: box.x,
|
|
231
|
+
y: box.y,
|
|
232
|
+
width: box.width,
|
|
233
|
+
height: box.height,
|
|
234
|
+
controls: [],
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
id: openViewId,
|
|
238
|
+
name: 'open',
|
|
239
|
+
x: box.x,
|
|
240
|
+
y: box.y,
|
|
241
|
+
width: box.width,
|
|
242
|
+
height: box.height,
|
|
243
|
+
controls: [openViewId + '/__background', ...controls],
|
|
244
|
+
},
|
|
245
|
+
],
|
|
246
|
+
})
|
|
247
|
+
}
|
|
201
248
|
for (const stack of await page.$$('[data-andesite-viewstack]')) {
|
|
202
249
|
const info = await stack.evaluate(node => ({ id: node.getAttribute('data-andesite-id')!, defaultView: node.getAttribute('data-andesite-default-view')! }))
|
|
203
250
|
const views: ViewStackDecl['views'] = []
|
|
@@ -223,7 +270,7 @@ export async function rasterizeAllInBrowser(
|
|
|
223
270
|
? await rasterizeHudStaticBackgrounds(page, outputDir)
|
|
224
271
|
: await rasterizeStaticBackground(page, outputDir)
|
|
225
272
|
const panels = await rasterizePanels(page, outputDir)
|
|
226
|
-
const buttons = await rasterizeButtons(page, outputDir)
|
|
273
|
+
const buttons = await rasterizeButtons(page, outputDir, viewStacks)
|
|
227
274
|
const progresses = await rasterizeProgresses(page, outputDir)
|
|
228
275
|
const sprites = await rasterizeSprites(page, outputDir)
|
|
229
276
|
const scrollViews = await rasterizeScrollViews(page, outputDir)
|
|
@@ -235,13 +282,15 @@ export async function rasterizeAllInBrowser(
|
|
|
235
282
|
for (const stack of viewStacks) for (const view of stack.views) {
|
|
236
283
|
view.controls = view.controls.filter(id => exportedIds.has(id))
|
|
237
284
|
for (const control of exported.filter(control => view.controls.includes(control.id))) {
|
|
238
|
-
|
|
239
|
-
|
|
285
|
+
// ADialog 的遮罩按钮(id 以 _mask 结尾)在关闭态尺寸为 0 是正常的
|
|
286
|
+
const isDialogMask = control.id.endsWith('_mask')
|
|
287
|
+
if (!isDialogMask && (control.width <= 0 || control.height <= 0)) throw new Error(`Collapsed AView control: ${control.id}`)
|
|
288
|
+
if (!isDialogMask && kind !== 'hud' && (control.x < -1 || control.y < -1 || control.x + control.width > pageBox.width + 1 || control.y + control.height > pageBox.height + 1)) {
|
|
240
289
|
throw new Error(`AView control outside page: ${control.id}`)
|
|
241
290
|
}
|
|
242
291
|
}
|
|
243
292
|
}
|
|
244
|
-
return { kind, width: pageBox.width, height: pageBox.height, mounts, viewStacks, panels: [...backgroundPanels, ...panels], buttons, labels, inputs, slots, progresses, sprites, scrollViews }
|
|
293
|
+
return { kind, mask, width: pageBox.width, height: pageBox.height, mounts, viewStacks, panels: [...backgroundPanels, ...panels], buttons, labels, inputs, slots, progresses, sprites, scrollViews }
|
|
245
294
|
} finally {
|
|
246
295
|
await page.close()
|
|
247
296
|
}
|
|
@@ -913,7 +962,7 @@ async function rasterizePanels(page: Page, outputDir: string): Promise<PanelRast
|
|
|
913
962
|
return results
|
|
914
963
|
}
|
|
915
964
|
|
|
916
|
-
async function rasterizeButtons(page: Page, outputDir: string): Promise<ButtonRaster[]> {
|
|
965
|
+
async function rasterizeButtons(page: Page, outputDir: string, viewStacks: ViewStackDecl[]): Promise<ButtonRaster[]> {
|
|
917
966
|
const elements = await page.$$('[data-andesite-type="button"]')
|
|
918
967
|
const results: ButtonRaster[] = []
|
|
919
968
|
for (const el of elements) {
|
|
@@ -922,8 +971,22 @@ async function rasterizeButtons(page: Page, outputDir: string): Promise<ButtonRa
|
|
|
922
971
|
}
|
|
923
972
|
const id = await el.evaluate((node: Element) => node.getAttribute('data-andesite-id') ?? '')
|
|
924
973
|
const mountId = await collectHudMountId(el)
|
|
925
|
-
|
|
926
|
-
|
|
974
|
+
const defaultVisible = await readDefaultVisible(el)
|
|
975
|
+
// ADialog 的遮罩按钮(id 以 _mask 结尾):从 dialog 的 viewStack 声明中取尺寸,确保铺满全屏拦截点击
|
|
976
|
+
const isDialogMask = id.endsWith('_mask')
|
|
977
|
+
let box: DomBox
|
|
978
|
+
if (isDialogMask) {
|
|
979
|
+
const dialogId = id.replace(/_mask$/, '')
|
|
980
|
+
const dialogStack = viewStacks.find(s => s.id === dialogId)
|
|
981
|
+
const openView = dialogStack?.views.find(v => v.name === 'open')
|
|
982
|
+
if (openView) {
|
|
983
|
+
box = { x: openView.x, y: openView.y, width: openView.width, height: openView.height }
|
|
984
|
+
} else {
|
|
985
|
+
box = await measureAndesiteBox(el)
|
|
986
|
+
}
|
|
987
|
+
} else {
|
|
988
|
+
box = await measureAndesiteBox(el)
|
|
989
|
+
}
|
|
927
990
|
const pressDuration = Number(await el.evaluate((node: Element) => node.getAttribute('data-andesite-press-duration') ?? '2'))
|
|
928
991
|
const clickable = await collectButtonClickable(el)
|
|
929
992
|
const layer = optionalNumber(await el.evaluate((node: Element) => node.getAttribute('data-andesite-layer')))
|
|
@@ -955,10 +1018,17 @@ async function rasterizeButtons(page: Page, outputDir: string): Promise<ButtonRa
|
|
|
955
1018
|
frames.push(screenshot)
|
|
956
1019
|
}
|
|
957
1020
|
if (frames.length === 2) {
|
|
958
|
-
|
|
1021
|
+
// ADialog 遮罩按钮(id 以 _mask 结尾):默认/按下态相同,直接用单帧,不生成纵向 atlas
|
|
1022
|
+
const isDialogMask = id.endsWith('_mask')
|
|
1023
|
+
let outputPng: Uint8Array
|
|
1024
|
+
if (isDialogMask) {
|
|
1025
|
+
outputPng = frames[0]
|
|
1026
|
+
} else {
|
|
1027
|
+
outputPng = await combinePngsVertically(page, frames)
|
|
1028
|
+
}
|
|
959
1029
|
const outputPath = path.join(outputDir, texturePath)
|
|
960
1030
|
fs.mkdirSync(path.dirname(outputPath), { recursive: true })
|
|
961
|
-
fs.writeFileSync(outputPath,
|
|
1031
|
+
fs.writeFileSync(outputPath, outputPng)
|
|
962
1032
|
results.push({
|
|
963
1033
|
id,
|
|
964
1034
|
mountId,
|
|
@@ -1104,14 +1174,28 @@ async function captureButtonPng(page: Page, el: Awaited<ReturnType<Page['$']>>):
|
|
|
1104
1174
|
|
|
1105
1175
|
const rect = (node as HTMLElement).getBoundingClientRect()
|
|
1106
1176
|
const clone = node.cloneNode(true) as HTMLElement
|
|
1177
|
+
// ADialog 遮罩按钮(id 以 _mask 结尾):按 dialog 声明尺寸截图,不受当前视图状态影响
|
|
1178
|
+
const isDialogMask = (node as HTMLElement).getAttribute('data-andesite-id')?.endsWith('_mask')
|
|
1179
|
+
let captureWidth = Math.max(1, Math.round(rect.width))
|
|
1180
|
+
let captureHeight = Math.max(1, Math.round(rect.height))
|
|
1181
|
+
if (isDialogMask) {
|
|
1182
|
+
// 从 dialog 的 viewStack 取尺寸(ADialog 渲染为 AViewStack,data-andesite-viewstack=id)
|
|
1183
|
+
const dialogId = (node as HTMLElement).getAttribute('data-andesite-id')?.replace(/_mask$/, '')
|
|
1184
|
+
const viewStackEl = document.querySelector(`[data-andesite-viewstack="${dialogId}"]`)
|
|
1185
|
+
if (viewStackEl) {
|
|
1186
|
+
const stackRect = viewStackEl.getBoundingClientRect()
|
|
1187
|
+
captureWidth = Math.max(1, Math.round(stackRect.width))
|
|
1188
|
+
captureHeight = Math.max(1, Math.round(stackRect.height))
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1107
1191
|
// 按钮常用 w-full / absolute 子层,独立截图时必须脱离原布局后固化测量尺寸。
|
|
1108
1192
|
clone.style.position = 'relative'
|
|
1109
1193
|
clone.style.left = '0'
|
|
1110
1194
|
clone.style.top = '0'
|
|
1111
1195
|
clone.style.right = 'auto'
|
|
1112
1196
|
clone.style.bottom = 'auto'
|
|
1113
|
-
clone.style.width = `${
|
|
1114
|
-
clone.style.height = `${
|
|
1197
|
+
clone.style.width = `${captureWidth}px`
|
|
1198
|
+
clone.style.height = `${captureHeight}px`
|
|
1115
1199
|
clone.style.margin = '0'
|
|
1116
1200
|
// 外层容器 box-shadow(底座/扩散投影)溢出边框盒,克隆截图需按外扩 clip 保留,
|
|
1117
1201
|
// 与面板路径一致;否则按钮底部投影在编译产物里被裁掉(dev 实时渲染不受影响)。
|