dsh-theme-palette 1.4.2 → 1.4.4
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/CHANGELOG.md +38 -0
- package/README-es.md +4 -2
- package/README-hi.md +4 -2
- package/README-pt.md +4 -2
- package/README-zh.md +4 -2
- package/README.md +4 -2
- package/client.js +43 -22
- package/docs/DESIGN.md +2 -2
- package/package.json +38 -12
- package/palette-dev.mjs +0 -140
- package/tools/README.md +0 -66
- package/tools/compare.mjs +0 -62
- package/tools/preview-settings.mjs +0 -134
- package/tools/release.mjs +0 -130
- package/tools/sheet.mjs +0 -219
- package/tools/shoot-desktop.mjs +0 -112
- package/tools/shoot-html.mjs +0 -123
- package/tools/shoot-sheet.mjs +0 -122
package/tools/shoot-html.mjs
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 把一个本地 HTML 整页截成 PNG(通用;默认 tools/preview.html)。
|
|
3
|
-
* 无需登录、无需重启 App —— 这是「配色系统」这一层的视觉回归。
|
|
4
|
-
*
|
|
5
|
-
* 用法:node tools/shoot-sheet.mjs [--out tools/out/<run>] [--width 760] [--scale 1]
|
|
6
|
-
*/
|
|
7
|
-
import { spawn } from 'node:child_process'
|
|
8
|
-
import { existsSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'
|
|
9
|
-
import { createHash } from 'node:crypto'
|
|
10
|
-
import { setTimeout as sleep } from 'node:timers/promises'
|
|
11
|
-
import path from 'node:path'
|
|
12
|
-
import os from 'node:os'
|
|
13
|
-
import { fileURLToPath } from 'node:url'
|
|
14
|
-
|
|
15
|
-
const here = path.dirname(fileURLToPath(import.meta.url))
|
|
16
|
-
const argv = process.argv.slice(2)
|
|
17
|
-
const arg = (k, d) => { const i = argv.indexOf('--' + k); return i >= 0 ? argv[i + 1] : d }
|
|
18
|
-
|
|
19
|
-
const fileArg = arg('file', 'preview.html')
|
|
20
|
-
const htmlPath = path.isAbsolute(fileArg) ? fileArg : path.join(here, fileArg)
|
|
21
|
-
if (!existsSync(htmlPath)) { console.error('找不到页面:' + htmlPath); process.exit(2) }
|
|
22
|
-
|
|
23
|
-
const stamp = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14)
|
|
24
|
-
const outDir = path.resolve(arg('out', path.join(here, 'out', stamp)))
|
|
25
|
-
const W = Number(arg('width', '760'))
|
|
26
|
-
const SCALE = Number(arg('scale', '1'))
|
|
27
|
-
mkdirSync(outDir, { recursive: true })
|
|
28
|
-
|
|
29
|
-
const CHROME = [
|
|
30
|
-
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
31
|
-
'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
|
|
32
|
-
'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe',
|
|
33
|
-
].find((p) => existsSync(p))
|
|
34
|
-
if (!CHROME) { console.error('找不到 Chrome / Edge'); process.exit(2) }
|
|
35
|
-
|
|
36
|
-
const PORT = 9400 + Math.floor(Math.random() * 300)
|
|
37
|
-
const profile = path.join(os.tmpdir(), 'palette-sheet-' + Date.now())
|
|
38
|
-
const child = spawn(CHROME, [
|
|
39
|
-
'--headless=new', `--remote-debugging-port=${PORT}`, `--user-data-dir=${profile}`,
|
|
40
|
-
'--no-first-run', '--no-default-browser-check', '--hide-scrollbars', '--disable-gpu',
|
|
41
|
-
'--allow-file-access-from-files', '--force-color-profile=srgb', `--window-size=${W},1000`, 'about:blank',
|
|
42
|
-
], { stdio: 'ignore' })
|
|
43
|
-
|
|
44
|
-
async function waitDevtools() {
|
|
45
|
-
for (let i = 0; i < 120; i++) {
|
|
46
|
-
try { const r = await fetch(`http://127.0.0.1:${PORT}/json/version`); if (r.ok) return r.json() } catch {}
|
|
47
|
-
await sleep(150)
|
|
48
|
-
}
|
|
49
|
-
throw new Error('devtools 未就绪')
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
class CDP {
|
|
53
|
-
constructor(ws) { this.ws = ws; this.id = 0; this.pending = new Map(); this.events = new Map() }
|
|
54
|
-
static async attach(url) {
|
|
55
|
-
const ws = new WebSocket(url)
|
|
56
|
-
const c = new CDP(ws)
|
|
57
|
-
await new Promise((res, rej) => { ws.onopen = res; ws.onerror = () => rej(new Error('ws error')) })
|
|
58
|
-
ws.onmessage = (m) => {
|
|
59
|
-
const msg = JSON.parse(m.data)
|
|
60
|
-
if (msg.id && c.pending.has(msg.id)) {
|
|
61
|
-
const { res, rej } = c.pending.get(msg.id); c.pending.delete(msg.id)
|
|
62
|
-
msg.error ? rej(new Error(JSON.stringify(msg.error))) : res(msg.result)
|
|
63
|
-
} else if (msg.method) {
|
|
64
|
-
const list = c.events.get(msg.method) || []
|
|
65
|
-
list.forEach((fn) => fn(msg.params)); c.events.set(msg.method, [])
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return c
|
|
69
|
-
}
|
|
70
|
-
send(method, params = {}) {
|
|
71
|
-
const id = ++this.id
|
|
72
|
-
this.ws.send(JSON.stringify({ id, method, params }))
|
|
73
|
-
return new Promise((res, rej) => this.pending.set(id, { res, rej }))
|
|
74
|
-
}
|
|
75
|
-
once(method) {
|
|
76
|
-
return new Promise((res) => { const l = this.events.get(method) || []; l.push(res); this.events.set(method, l) })
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const main = async () => {
|
|
81
|
-
try {
|
|
82
|
-
await waitDevtools()
|
|
83
|
-
const list = await (await fetch(`http://127.0.0.1:${PORT}/json/list`)).json()
|
|
84
|
-
const target = list.find((t) => t.type === 'page')
|
|
85
|
-
const cdp = await CDP.attach(target.webSocketDebuggerUrl)
|
|
86
|
-
await cdp.send('Page.enable')
|
|
87
|
-
await cdp.send('Emulation.setDeviceMetricsOverride', { width: W, height: 1000, deviceScaleFactor: 1, mobile: false })
|
|
88
|
-
|
|
89
|
-
const manifest = []
|
|
90
|
-
for (const v of [0]) {
|
|
91
|
-
const url = 'file:///' + htmlPath.replace(/\\/g, '/') + '?v=' + v
|
|
92
|
-
const loaded = cdp.once('Page.loadEventFired')
|
|
93
|
-
await cdp.send('Page.navigate', { url })
|
|
94
|
-
await loaded
|
|
95
|
-
await sleep(300)
|
|
96
|
-
await cdp.send('Runtime.evaluate', { expression: 'document.fonts && document.fonts.ready', awaitPromise: true })
|
|
97
|
-
const meas = await cdp.send('Runtime.evaluate', {
|
|
98
|
-
expression: "(() => { const p = document.querySelector('.page'); return Math.ceil(p.getBoundingClientRect().height + 32); })()",
|
|
99
|
-
returnByValue: true,
|
|
100
|
-
})
|
|
101
|
-
const h = Math.ceil(meas.result.value)
|
|
102
|
-
await cdp.send('Emulation.setDeviceMetricsOverride', { width: W, height: h, deviceScaleFactor: 1, mobile: false })
|
|
103
|
-
await sleep(200)
|
|
104
|
-
const shot = await cdp.send('Page.captureScreenshot', {
|
|
105
|
-
format: 'png', captureBeyondViewport: true,
|
|
106
|
-
clip: { x: 0, y: 0, width: W, height: h, scale: SCALE },
|
|
107
|
-
})
|
|
108
|
-
const buf = Buffer.from(shot.data, 'base64')
|
|
109
|
-
const name = path.basename(htmlPath, '.html') + '.png'
|
|
110
|
-
writeFileSync(path.join(outDir, name), buf)
|
|
111
|
-
manifest.push({ name, view: v, px: `${W * SCALE}x${h * SCALE}`, sha256: createHash('sha256').update(buf).digest('hex').slice(0, 16) })
|
|
112
|
-
console.log(`✓ ${name} ${W * SCALE}x${h * SCALE}`)
|
|
113
|
-
}
|
|
114
|
-
writeFileSync(path.join(outDir, 'manifest.json'), JSON.stringify({ html: 'tools/sheet.html', width: W, scale: SCALE, shots: manifest }, null, 2) + '\n')
|
|
115
|
-
console.log('\n输出:' + outDir)
|
|
116
|
-
} finally {
|
|
117
|
-
try { child.kill() } catch {}
|
|
118
|
-
await sleep(200)
|
|
119
|
-
try { rmSync(profile, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 }) } catch {}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
main().catch((e) => { console.error('✗ ' + e.message); process.exit(1) })
|
package/tools/shoot-sheet.mjs
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 把 tools/sheet.html 的 4 个视图(每套配色一屏,含浅色 + 深色两个面板)截成 PNG。
|
|
3
|
-
* 无需登录、无需重启 App —— 这是「配色系统」这一层的视觉回归。
|
|
4
|
-
*
|
|
5
|
-
* 用法:node tools/shoot-sheet.mjs [--out tools/out/<run>] [--width 760] [--scale 1]
|
|
6
|
-
*/
|
|
7
|
-
import { spawn } from 'node:child_process'
|
|
8
|
-
import { existsSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'
|
|
9
|
-
import { createHash } from 'node:crypto'
|
|
10
|
-
import { setTimeout as sleep } from 'node:timers/promises'
|
|
11
|
-
import path from 'node:path'
|
|
12
|
-
import os from 'node:os'
|
|
13
|
-
import { fileURLToPath } from 'node:url'
|
|
14
|
-
|
|
15
|
-
const here = path.dirname(fileURLToPath(import.meta.url))
|
|
16
|
-
const argv = process.argv.slice(2)
|
|
17
|
-
const arg = (k, d) => { const i = argv.indexOf('--' + k); return i >= 0 ? argv[i + 1] : d }
|
|
18
|
-
|
|
19
|
-
const htmlPath = path.join(here, 'sheet.html')
|
|
20
|
-
if (!existsSync(htmlPath)) { console.error('先跑 node tools/sheet.mjs 生成 sheet.html'); process.exit(2) }
|
|
21
|
-
|
|
22
|
-
const stamp = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14)
|
|
23
|
-
const outDir = path.resolve(arg('out', path.join(here, 'out', stamp)))
|
|
24
|
-
const W = Number(arg('width', '760'))
|
|
25
|
-
const SCALE = Number(arg('scale', '1'))
|
|
26
|
-
mkdirSync(outDir, { recursive: true })
|
|
27
|
-
|
|
28
|
-
const CHROME = [
|
|
29
|
-
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
30
|
-
'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
|
|
31
|
-
'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe',
|
|
32
|
-
].find((p) => existsSync(p))
|
|
33
|
-
if (!CHROME) { console.error('找不到 Chrome / Edge'); process.exit(2) }
|
|
34
|
-
|
|
35
|
-
const PORT = 9400 + Math.floor(Math.random() * 300)
|
|
36
|
-
const profile = path.join(os.tmpdir(), 'palette-sheet-' + Date.now())
|
|
37
|
-
const child = spawn(CHROME, [
|
|
38
|
-
'--headless=new', `--remote-debugging-port=${PORT}`, `--user-data-dir=${profile}`,
|
|
39
|
-
'--no-first-run', '--no-default-browser-check', '--hide-scrollbars', '--disable-gpu',
|
|
40
|
-
'--allow-file-access-from-files', '--force-color-profile=srgb', `--window-size=${W},1000`, 'about:blank',
|
|
41
|
-
], { stdio: 'ignore' })
|
|
42
|
-
|
|
43
|
-
async function waitDevtools() {
|
|
44
|
-
for (let i = 0; i < 120; i++) {
|
|
45
|
-
try { const r = await fetch(`http://127.0.0.1:${PORT}/json/version`); if (r.ok) return r.json() } catch {}
|
|
46
|
-
await sleep(150)
|
|
47
|
-
}
|
|
48
|
-
throw new Error('devtools 未就绪')
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
class CDP {
|
|
52
|
-
constructor(ws) { this.ws = ws; this.id = 0; this.pending = new Map(); this.events = new Map() }
|
|
53
|
-
static async attach(url) {
|
|
54
|
-
const ws = new WebSocket(url)
|
|
55
|
-
const c = new CDP(ws)
|
|
56
|
-
await new Promise((res, rej) => { ws.onopen = res; ws.onerror = () => rej(new Error('ws error')) })
|
|
57
|
-
ws.onmessage = (m) => {
|
|
58
|
-
const msg = JSON.parse(m.data)
|
|
59
|
-
if (msg.id && c.pending.has(msg.id)) {
|
|
60
|
-
const { res, rej } = c.pending.get(msg.id); c.pending.delete(msg.id)
|
|
61
|
-
msg.error ? rej(new Error(JSON.stringify(msg.error))) : res(msg.result)
|
|
62
|
-
} else if (msg.method) {
|
|
63
|
-
const list = c.events.get(msg.method) || []
|
|
64
|
-
list.forEach((fn) => fn(msg.params)); c.events.set(msg.method, [])
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return c
|
|
68
|
-
}
|
|
69
|
-
send(method, params = {}) {
|
|
70
|
-
const id = ++this.id
|
|
71
|
-
this.ws.send(JSON.stringify({ id, method, params }))
|
|
72
|
-
return new Promise((res, rej) => this.pending.set(id, { res, rej }))
|
|
73
|
-
}
|
|
74
|
-
once(method) {
|
|
75
|
-
return new Promise((res) => { const l = this.events.get(method) || []; l.push(res); this.events.set(method, l) })
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const main = async () => {
|
|
80
|
-
try {
|
|
81
|
-
await waitDevtools()
|
|
82
|
-
const list = await (await fetch(`http://127.0.0.1:${PORT}/json/list`)).json()
|
|
83
|
-
const target = list.find((t) => t.type === 'page')
|
|
84
|
-
const cdp = await CDP.attach(target.webSocketDebuggerUrl)
|
|
85
|
-
await cdp.send('Page.enable')
|
|
86
|
-
await cdp.send('Emulation.setDeviceMetricsOverride', { width: W, height: 1000, deviceScaleFactor: 1, mobile: false })
|
|
87
|
-
|
|
88
|
-
const manifest = []
|
|
89
|
-
for (const v of [1, 2, 3, 4]) {
|
|
90
|
-
const url = 'file:///' + htmlPath.replace(/\\/g, '/') + '?v=' + v
|
|
91
|
-
const loaded = cdp.once('Page.loadEventFired')
|
|
92
|
-
await cdp.send('Page.navigate', { url })
|
|
93
|
-
await loaded
|
|
94
|
-
await sleep(300)
|
|
95
|
-
await cdp.send('Runtime.evaluate', { expression: 'document.fonts && document.fonts.ready', awaitPromise: true })
|
|
96
|
-
const meas = await cdp.send('Runtime.evaluate', {
|
|
97
|
-
expression: "(() => { const p = document.querySelector('.page'); return Math.ceil(p.getBoundingClientRect().height + 32); })()",
|
|
98
|
-
returnByValue: true,
|
|
99
|
-
})
|
|
100
|
-
const h = Math.ceil(meas.result.value)
|
|
101
|
-
await cdp.send('Emulation.setDeviceMetricsOverride', { width: W, height: h, deviceScaleFactor: 1, mobile: false })
|
|
102
|
-
await sleep(200)
|
|
103
|
-
const shot = await cdp.send('Page.captureScreenshot', {
|
|
104
|
-
format: 'png', captureBeyondViewport: true,
|
|
105
|
-
clip: { x: 0, y: 0, width: W, height: h, scale: SCALE },
|
|
106
|
-
})
|
|
107
|
-
const buf = Buffer.from(shot.data, 'base64')
|
|
108
|
-
const name = `sheet-v${v}.png`
|
|
109
|
-
writeFileSync(path.join(outDir, name), buf)
|
|
110
|
-
manifest.push({ name, view: v, px: `${W * SCALE}x${h * SCALE}`, sha256: createHash('sha256').update(buf).digest('hex').slice(0, 16) })
|
|
111
|
-
console.log(`✓ ${name} ${W * SCALE}x${h * SCALE}`)
|
|
112
|
-
}
|
|
113
|
-
writeFileSync(path.join(outDir, 'manifest.json'), JSON.stringify({ html: 'tools/sheet.html', width: W, scale: SCALE, shots: manifest }, null, 2) + '\n')
|
|
114
|
-
console.log('\n输出:' + outDir)
|
|
115
|
-
} finally {
|
|
116
|
-
try { child.kill() } catch {}
|
|
117
|
-
await sleep(200)
|
|
118
|
-
try { rmSync(profile, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 }) } catch {}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
main().catch((e) => { console.error('✗ ' + e.message); process.exit(1) })
|