mirror of
https://github.com/zhu1090093659/dsh-web.git
synced 2026-09-28 14:24:03 +08:00
115 lines
4.2 KiB
JavaScript
115 lines
4.2 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* skins-montage — regenerate the root README "skins at a glance" collage.
|
|
*
|
|
* Reads the market catalog (market/dist/manifest.js, generated by market-build),
|
|
* lays every skin's light preview out in a 6-column grid (last row centered),
|
|
* and screenshots it into docs/images/skins-montage.png. Re-run after adding,
|
|
* removing, or renaming a skin, and update the README skin count in the same
|
|
* change.
|
|
*
|
|
* Requires playwright + its chromium browser (see scripts/capture-previews).
|
|
*
|
|
* Usage:
|
|
* node scripts/skins-montage.mjs
|
|
*/
|
|
|
|
import { readFileSync, writeFileSync, mkdtempSync, existsSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { join, resolve, dirname } from 'node:path'
|
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
import { chromium } from 'playwright'
|
|
|
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
const manifestPath = join(root, 'market/dist/manifest.js')
|
|
const out = join(root, 'docs/images/skins-montage.png')
|
|
|
|
// --- catalog ---------------------------------------------------------------
|
|
let text
|
|
try {
|
|
text = readFileSync(manifestPath, 'utf8')
|
|
} catch {
|
|
console.error('market/dist/manifest.js not found; run `pnpm market:build` first')
|
|
process.exit(1)
|
|
}
|
|
const manifest = JSON.parse(
|
|
text
|
|
.replace(/^\/\*[\s\S]*?\*\/\s*/, '') // generated header comment
|
|
.replace(/^window\.SKIN_MANIFEST\s*=\s*/, '')
|
|
.replace(/;\s*$/, ''),
|
|
)
|
|
const skins = manifest.skins ?? []
|
|
if (!skins.length) {
|
|
console.error('market manifest has no skins; run `pnpm market:build` first')
|
|
process.exit(1)
|
|
}
|
|
|
|
// --- page ------------------------------------------------------------------
|
|
const CELL = 200 // CSS px; deviceScaleFactor 2 makes the output 2x
|
|
const GAP = 6
|
|
const COLS = 6
|
|
|
|
for (const skin of skins) {
|
|
if (!existsSync(join(root, 'market/dist', skin.preview?.light ?? ''))) {
|
|
console.error(`skin ${skin.id}: preview/light image missing; run node scripts/capture-previews ${skin.id}`)
|
|
process.exit(1)
|
|
}
|
|
}
|
|
|
|
const cells = skins
|
|
.map((skin) => {
|
|
const src = pathToFileURL(join(root, 'market/dist', skin.preview.light)).href
|
|
const label = `${skin.name} · ${skin.nameEn}`
|
|
return `<figure class="cell"><img src="${src}" alt="${skin.id}"><figcaption>${label}</figcaption></figure>`
|
|
})
|
|
.join('\n')
|
|
|
|
const html = `<!doctype html>
|
|
<html><head><meta charset="utf-8"><style>
|
|
body { margin: 0; background: #fff; }
|
|
.grid {
|
|
display: flex; flex-wrap: wrap; justify-content: center; gap: ${GAP}px;
|
|
width: ${COLS * CELL + (COLS - 1) * GAP}px; padding: 0;
|
|
}
|
|
.cell { margin: 0; width: ${CELL}px; }
|
|
.cell img {
|
|
display: block; width: ${CELL}px; height: ${CELL * 9 / 16}px;
|
|
object-fit: cover; object-position: top; background: #e3e8ee;
|
|
}
|
|
figcaption {
|
|
font: 11px/1.45 -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Segoe UI', sans-serif;
|
|
color: #4a5560; text-align: center; margin-top: 2px;
|
|
height: 32px; overflow: hidden; white-space: normal;
|
|
}
|
|
</style></head><body><div class="grid">${cells}</div></body></html>`
|
|
|
|
const tmp = mkdtempSync(join(tmpdir(), 'skins-montage-'))
|
|
const pagePath = join(tmp, 'montage.html')
|
|
writeFileSync(pagePath, html)
|
|
|
|
// --- capture ---------------------------------------------------------------
|
|
const browser = await chromium.launch()
|
|
const page = await browser.newPage({
|
|
viewport: { width: COLS * CELL + (COLS - 1) * GAP + 40, height: 600 },
|
|
deviceScaleFactor: 2,
|
|
})
|
|
page.on('console', (m) => {
|
|
if (m.type() === 'error') console.error('[page]', m.text())
|
|
})
|
|
await page.goto(pathToFileURL(pagePath).href)
|
|
await page.waitForLoadState('networkidle')
|
|
|
|
const report = await page.evaluate(() => ({
|
|
imgs: [...document.images].map((img) => ({ src: img.src, ok: img.complete && img.naturalWidth > 0 })),
|
|
scrollW: document.documentElement.scrollWidth,
|
|
}))
|
|
for (const img of report.imgs) {
|
|
if (!img.ok) throw new Error('image failed to load: ' + img.src)
|
|
}
|
|
if (report.scrollW > 1300) throw new Error(`grid overflow: ${report.scrollW}px`)
|
|
|
|
const box = await page.locator('.grid').boundingBox()
|
|
await page.screenshot({ path: out, clip: { x: box.x, y: box.y, width: box.width, height: box.height } })
|
|
await browser.close()
|
|
console.log(`wrote ${out} (${skins.length} skins)`)
|