dsh-neotui 0.1.4 → 0.1.5
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/package.json +1 -1
- package/src/panels.js +37 -7
- package/src/widgets.js +37 -1
package/package.json
CHANGED
package/src/panels.js
CHANGED
|
@@ -1386,6 +1386,7 @@ export class JobsPanel extends Popup {
|
|
|
1386
1386
|
title: "后台任务(Ctrl+J 查看详情)", lines: [],
|
|
1387
1387
|
buttons: [{ label: "关闭(q)", action: "close" }],
|
|
1388
1388
|
onAction: () => app.closeOverlay(),
|
|
1389
|
+
scrollable: true, // expanded details scroll instead of being clipped
|
|
1389
1390
|
});
|
|
1390
1391
|
this.app = app;
|
|
1391
1392
|
this.jobs = jobs;
|
|
@@ -1394,19 +1395,40 @@ export class JobsPanel extends Popup {
|
|
|
1394
1395
|
this.rowOf = []; // rendered line → job index (-1 = chrome/detail)
|
|
1395
1396
|
this.rebuild();
|
|
1396
1397
|
}
|
|
1398
|
+
/** Width-aware character cut (no ellipsis — continuation chunks follow). */
|
|
1399
|
+
static #cutWidth(s, w) {
|
|
1400
|
+
let out = "", cw = 0;
|
|
1401
|
+
for (const ch of s) {
|
|
1402
|
+
const c = strWidth(ch);
|
|
1403
|
+
if (cw + c > w) break;
|
|
1404
|
+
out += ch; cw += c;
|
|
1405
|
+
}
|
|
1406
|
+
return out;
|
|
1407
|
+
}
|
|
1397
1408
|
#detailLines(j) {
|
|
1409
|
+
// Expanded = EVERYTHING: every field, full values — long commands wrap
|
|
1410
|
+
// across lines instead of being truncated (the web clips them; we don't).
|
|
1398
1411
|
const lines = [];
|
|
1412
|
+
const budget = Math.max(20, this.w - 10);
|
|
1399
1413
|
for (const [k, v] of Object.entries(j)) {
|
|
1400
1414
|
if (["status", "kind", "label"].includes(k)) continue;
|
|
1401
1415
|
const s = v !== null && typeof v === "object" ? JSON.stringify(v) : String(v ?? "");
|
|
1402
1416
|
if (s === "") continue;
|
|
1403
|
-
|
|
1404
|
-
|
|
1417
|
+
let rest = s;
|
|
1418
|
+
let first = true;
|
|
1419
|
+
while (rest.length > 0 || first) {
|
|
1420
|
+
const head = first ? `${k}: ` : " ";
|
|
1421
|
+
const take = JobsPanel.#cutWidth(rest, budget - strWidth(head));
|
|
1422
|
+
lines.push([{ t: ` ${head}${take}`, fg: K.DIM }]);
|
|
1423
|
+
rest = rest.slice(take.length);
|
|
1424
|
+
first = false;
|
|
1425
|
+
if (lines.length > 200) break; // pathological safety, never reached in practice
|
|
1426
|
+
}
|
|
1405
1427
|
}
|
|
1406
1428
|
return lines;
|
|
1407
1429
|
}
|
|
1408
1430
|
rebuild() {
|
|
1409
|
-
let lines = [[{ t: " 这些任务在后台运行,不阻塞会话 — Enter/→/l 展开,←/h 折叠,q 关闭", fg: K.DIM }]];
|
|
1431
|
+
let lines = [[{ t: " 这些任务在后台运行,不阻塞会话 — Enter/→/l 展开,←/h 折叠,PgUp/PgDn 滚动,q 关闭", fg: K.DIM }]];
|
|
1410
1432
|
const rowOf = [-1];
|
|
1411
1433
|
const jobs = this.jobs;
|
|
1412
1434
|
if (jobs.length === 0) {
|
|
@@ -1427,11 +1449,19 @@ export class JobsPanel extends Popup {
|
|
|
1427
1449
|
rowOf.push(i);
|
|
1428
1450
|
if (open) for (const fl of this.#detailLines(j)) { lines.push(fl); rowOf.push(-1); }
|
|
1429
1451
|
}
|
|
1430
|
-
|
|
1431
|
-
lines = [...lines.slice(0, this.h - 4), [{ t: ` …共 ${jobs.length} 个任务`, fg: K.FAINT }]];
|
|
1432
|
-
}
|
|
1452
|
+
// keep the full content — the panel scrolls now (no more hard clip)
|
|
1433
1453
|
this.lines = lines;
|
|
1434
1454
|
this.rowOf = rowOf;
|
|
1455
|
+
this.#ensureVisible();
|
|
1456
|
+
}
|
|
1457
|
+
/** Keep the selected job row inside the scrollable viewport. */
|
|
1458
|
+
#ensureVisible() {
|
|
1459
|
+
const avail = this.contentRows();
|
|
1460
|
+
const row = this.rowOf.findIndex((r) => r === this.sel);
|
|
1461
|
+
if (row < 0) return;
|
|
1462
|
+
if (row < this.scrollY) this.scrollY = row;
|
|
1463
|
+
else if (row >= this.scrollY + avail) this.scrollY = row - avail + 1;
|
|
1464
|
+
this.scrollY = Math.max(0, Math.min(this.scrollY, this.maxScroll()));
|
|
1435
1465
|
}
|
|
1436
1466
|
#toggle(i) {
|
|
1437
1467
|
if (this.expanded.has(i)) this.expanded.delete(i);
|
|
@@ -1459,7 +1489,7 @@ export class JobsPanel extends Popup {
|
|
|
1459
1489
|
return super.onKey(ev);
|
|
1460
1490
|
}
|
|
1461
1491
|
onMouse(ev) {
|
|
1462
|
-
if (super.onMouse(ev)) return true; // buttons
|
|
1492
|
+
if (super.onMouse(ev)) return true; // buttons + wheel scrolling
|
|
1463
1493
|
if (ev.kind === "press" && ev.button === 0) {
|
|
1464
1494
|
const i = ev.y - this.y - 1;
|
|
1465
1495
|
const jIdx = this.rowOf[i];
|
package/src/widgets.js
CHANGED
|
@@ -510,12 +510,23 @@ export class Popup extends Widget {
|
|
|
510
510
|
this.btnIdx = 0;
|
|
511
511
|
this.onAction = opts.onAction ?? null;
|
|
512
512
|
this.fg = opts.fg;
|
|
513
|
+
// opt-in vertical scrolling: content longer than the box scrolls with
|
|
514
|
+
// PgUp/PgDn/up/down/wheel instead of being clipped away
|
|
515
|
+
this.scrollable = opts.scrollable ?? false;
|
|
516
|
+
this.scrollY = 0;
|
|
517
|
+
}
|
|
518
|
+
contentRows() {
|
|
519
|
+
// rows between the border and the buttons row
|
|
520
|
+
return this.h - 2 - (this.buttons.length ? 1 : 0);
|
|
521
|
+
}
|
|
522
|
+
maxScroll() {
|
|
523
|
+
return Math.max(0, this.lines.length - this.contentRows());
|
|
513
524
|
}
|
|
514
525
|
render(screen) {
|
|
515
526
|
screen.fillRect(this.x, this.y, this.x + this.w - 1, this.y + this.h - 1, " ", { bg: T.BG2 });
|
|
516
527
|
screen.box(this.x, this.y, this.x + this.w - 1, this.y + this.h - 1, { fg: this.fg ?? 0x67b7ff, bg: T.BG2 }, this.title);
|
|
517
528
|
let ly = this.y + 1;
|
|
518
|
-
|
|
529
|
+
const draw = (line) => {
|
|
519
530
|
if (Array.isArray(line)) {
|
|
520
531
|
// styled line: array of segments
|
|
521
532
|
let px = this.x + 2;
|
|
@@ -532,6 +543,23 @@ export class Popup extends Widget {
|
|
|
532
543
|
screen.text(this.x + 2, ly, truncate(String(line), this.w - 4), { fg: T.TXT, bg: T.BG2 });
|
|
533
544
|
}
|
|
534
545
|
ly++;
|
|
546
|
+
};
|
|
547
|
+
if (this.scrollable) {
|
|
548
|
+
const avail = this.contentRows();
|
|
549
|
+
this.scrollY = Math.max(0, Math.min(this.scrollY, this.maxScroll()));
|
|
550
|
+
const start = this.scrollY;
|
|
551
|
+
for (let i = 0; i < avail; i++) {
|
|
552
|
+
const line = this.lines[start + i];
|
|
553
|
+
if (line === undefined) break;
|
|
554
|
+
draw(line);
|
|
555
|
+
}
|
|
556
|
+
if (this.maxScroll() > 0) {
|
|
557
|
+
// "↑N" / "↓N" overflow indicators on the border row
|
|
558
|
+
if (this.scrollY > 0) screen.text(this.x + this.w - 6, this.y, ` ↑${this.scrollY}`, { fg: T.ACCENT, bg: T.BG2 });
|
|
559
|
+
if (this.scrollY < this.maxScroll()) screen.text(this.x + this.w - 8, this.y + this.h - 1, ` ↓${this.maxScroll() - this.scrollY}`, { fg: T.ACCENT, bg: T.BG2 });
|
|
560
|
+
}
|
|
561
|
+
} else {
|
|
562
|
+
for (const line of this.lines) draw(line);
|
|
535
563
|
}
|
|
536
564
|
// buttons on last row
|
|
537
565
|
if (this.buttons.length) {
|
|
@@ -550,6 +578,10 @@ export class Popup extends Widget {
|
|
|
550
578
|
}
|
|
551
579
|
}
|
|
552
580
|
onMouse(ev) {
|
|
581
|
+
if (this.scrollable) {
|
|
582
|
+
if (ev.kind === "wheel-up") { this.scrollY = Math.max(0, this.scrollY - 3); return true; }
|
|
583
|
+
if (ev.kind === "wheel-down") { this.scrollY = Math.min(this.maxScroll(), this.scrollY + 3); return true; }
|
|
584
|
+
}
|
|
553
585
|
if (ev.kind === "press" && ev.button === 0 && this.buttons.length) {
|
|
554
586
|
let bx = this.x + 2;
|
|
555
587
|
const by = this.y + this.h - 2;
|
|
@@ -566,6 +598,10 @@ export class Popup extends Widget {
|
|
|
566
598
|
}
|
|
567
599
|
onKey(ev) {
|
|
568
600
|
if (ev.type !== "key") return false;
|
|
601
|
+
if (this.scrollable) {
|
|
602
|
+
if (ev.name === "pgup") { this.scrollY = Math.max(0, this.scrollY - this.contentRows()); return true; }
|
|
603
|
+
if (ev.name === "pgdn") { this.scrollY = Math.min(this.maxScroll(), this.scrollY + this.contentRows()); return true; }
|
|
604
|
+
}
|
|
569
605
|
if (ev.name === "escape") { this.onAction?.({ label: "__cancel__", action: "__cancel__" }, -1); return true; }
|
|
570
606
|
if (ev.name === "tab") { this.btnIdx = (this.btnIdx + 1) % Math.max(1, this.buttons.length); return true; }
|
|
571
607
|
if (ev.name === "left") { this.btnIdx = Math.max(0, this.btnIdx - 1); return true; }
|