@yfwu2020/dsh-selection-explain 0.4.1 → 0.7.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 +5 -1
- package/lib/client.js +811 -26
- package/lib/index.js +88 -0
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/scripts/gen-star-path.mjs +71 -0
- package/scripts/smoke-panel.mjs +709 -1
- package/scripts/test-client.mjs +12 -2
- package/src/client/index.js +811 -26
- package/src/index.ts +92 -0
package/src/index.ts
CHANGED
|
@@ -1381,6 +1381,94 @@ export function apply(ctx: Context, rawConfig: Config): void {
|
|
|
1381
1381
|
const historyFile = (): string =>
|
|
1382
1382
|
join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'selection-explain', 'history.json')
|
|
1383
1383
|
|
|
1384
|
+
/**
|
|
1385
|
+
* 悬浮胶囊"拖到别处"后的位置(用户要求记住,存 host 上 —— 不用 localStorage)。
|
|
1386
|
+
* 只记自由摆放的**右边缘 + 垂直中心**:收球/展开都以右边缘为锚(client 半的 applyFreePill 用 CSS right 钉住)。
|
|
1387
|
+
* 吸附回右下角(anchor)时会清掉 —— 那是默认位置,不需要记。
|
|
1388
|
+
*/
|
|
1389
|
+
const pillPosFile = (): string =>
|
|
1390
|
+
join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'selection-explain', 'pill.json')
|
|
1391
|
+
|
|
1392
|
+
let pillPos: { right: number; y: number } | null = null
|
|
1393
|
+
let pillPosLoaded = false
|
|
1394
|
+
let pillPosTimer: ReturnType<typeof setTimeout> | null = null
|
|
1395
|
+
|
|
1396
|
+
/** 位置校验:只接受有限数值,并夹到一个"任何屏幕都放得下"的量级。 */
|
|
1397
|
+
function normalizePillPos(input: unknown): { right: number; y: number } | null {
|
|
1398
|
+
const value = input as { right?: unknown; y?: unknown } | null | undefined
|
|
1399
|
+
const right = typeof value?.right === 'number' && Number.isFinite(value.right) ? value.right : NaN
|
|
1400
|
+
const y = typeof value?.y === 'number' && Number.isFinite(value.y) ? value.y : NaN
|
|
1401
|
+
if (!Number.isFinite(right) || !Number.isFinite(y)) return null
|
|
1402
|
+
const clamp = (n: number): number => Math.round(Math.min(40000, Math.max(-40000, n)))
|
|
1403
|
+
return { right: clamp(right), y: clamp(y) }
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
async function loadPillPos(): Promise<void> {
|
|
1407
|
+
if (pillPosLoaded) return
|
|
1408
|
+
pillPosLoaded = true
|
|
1409
|
+
try {
|
|
1410
|
+
const parsed = JSON.parse(await readFile(pillPosFile(), 'utf8')) as { position?: unknown }
|
|
1411
|
+
pillPos = normalizePillPos(parsed?.position)
|
|
1412
|
+
} catch {
|
|
1413
|
+
pillPos = null // 没存过 / 坏了都当作"没记过"
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
function schedulePillPosWrite(): void {
|
|
1418
|
+
if (pillPosTimer) clearTimeout(pillPosTimer)
|
|
1419
|
+
pillPosTimer = setTimeout(() => {
|
|
1420
|
+
pillPosTimer = null
|
|
1421
|
+
void (async () => {
|
|
1422
|
+
try {
|
|
1423
|
+
await mkdir(join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'selection-explain'), { recursive: true })
|
|
1424
|
+
await writeFile(pillPosFile(), JSON.stringify({ version: 1, position: pillPos }, null, 0), 'utf8')
|
|
1425
|
+
} catch (error) {
|
|
1426
|
+
ctx.logger?.warn?.(`[${name}] 写入胶囊位置失败:${String((error as Error)?.message ?? error)}`)
|
|
1427
|
+
}
|
|
1428
|
+
})()
|
|
1429
|
+
}, 300)
|
|
1430
|
+
pillPosTimer.unref?.()
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
/**
|
|
1434
|
+
* 胶囊位置:GET 读回来(挂载时恢复)、POST 存({right,y})或清({clear:true},吸附回右下角时调)。
|
|
1435
|
+
*/
|
|
1436
|
+
const handlePillPos = (req: IncomingMessage, res: ServerResponse): void => {
|
|
1437
|
+
void (async () => {
|
|
1438
|
+
await loadPillPos()
|
|
1439
|
+
if (req.method === 'GET') {
|
|
1440
|
+
sendJson(res, 200, { ok: true, position: pillPos })
|
|
1441
|
+
return
|
|
1442
|
+
}
|
|
1443
|
+
if (req.method !== 'POST') {
|
|
1444
|
+
sendJson(res, 405, { ok: false, error: '只支持 GET / POST' })
|
|
1445
|
+
return
|
|
1446
|
+
}
|
|
1447
|
+
const raw = await readBody(req, 4_000)
|
|
1448
|
+
let body: { position?: unknown; clear?: unknown } = {}
|
|
1449
|
+
try {
|
|
1450
|
+
body = raw ? (JSON.parse(raw) as { position?: unknown; clear?: unknown }) : {}
|
|
1451
|
+
} catch {
|
|
1452
|
+
sendJson(res, 400, { ok: false, error: '请求体不是合法 JSON' })
|
|
1453
|
+
return
|
|
1454
|
+
}
|
|
1455
|
+
if (body.clear === true) {
|
|
1456
|
+
pillPos = null
|
|
1457
|
+
schedulePillPosWrite()
|
|
1458
|
+
sendJson(res, 200, { ok: true, position: null })
|
|
1459
|
+
return
|
|
1460
|
+
}
|
|
1461
|
+
const next = normalizePillPos(body.position ?? body)
|
|
1462
|
+
if (!next) {
|
|
1463
|
+
sendJson(res, 400, { ok: false, error: 'position 需要 { right, y } 两个有限数值' })
|
|
1464
|
+
return
|
|
1465
|
+
}
|
|
1466
|
+
pillPos = next
|
|
1467
|
+
schedulePillPosWrite()
|
|
1468
|
+
sendJson(res, 200, { ok: true, position: pillPos })
|
|
1469
|
+
})()
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1384
1472
|
const clampText = (value: unknown, max: number): string => (typeof value === 'string' ? value.slice(0, max) : '')
|
|
1385
1473
|
|
|
1386
1474
|
async function loadHistory(): Promise<void> {
|
|
@@ -3004,6 +3092,10 @@ export function apply(ctx: Context, rawConfig: Config): void {
|
|
|
3004
3092
|
() => ctx.webServer.register({ kind: 'exact', path: `${API_PREFIX}/history`, handler: handleHistory }),
|
|
3005
3093
|
`${name}: history route`,
|
|
3006
3094
|
)
|
|
3095
|
+
ctx.effect(
|
|
3096
|
+
() => ctx.webServer.register({ kind: 'exact', path: `${API_PREFIX}/pill`, handler: handlePillPos }),
|
|
3097
|
+
`${name}: pill position route`,
|
|
3098
|
+
)
|
|
3007
3099
|
ctx.effect(
|
|
3008
3100
|
() => ctx.webServer.register({ kind: 'exact', path: `${API_PREFIX}/models`, handler: handleModels }),
|
|
3009
3101
|
`${name}: models route`,
|