@h-rig/cli 0.0.6-alpha.85 → 0.0.6-alpha.87
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/bin/rig.js +79 -1027
- package/dist/src/app-opentui/bootstrap.js +79 -1027
- package/dist/src/app-opentui/command-palette.js +1 -0
- package/dist/src/app-opentui/drone.js +1 -0
- package/dist/src/app-opentui/index.js +61 -1024
- package/dist/src/app-opentui/list-search.d.ts +5 -1
- package/dist/src/app-opentui/list-search.js +2 -2
- package/dist/src/app-opentui/react/App.js +62 -970
- package/dist/src/app-opentui/react/Backdrop.js +3 -9
- package/dist/src/app-opentui/react/ChromeHost.js +6 -1038
- package/dist/src/app-opentui/react/SceneFrameView.js +55 -5
- package/dist/src/app-opentui/react/launch.js +62 -970
- package/dist/src/app-opentui/react/syntax.js +1 -0
- package/dist/src/app-opentui/registry.js +3 -5
- package/dist/src/app-opentui/render/constants.d.ts +0 -1
- package/dist/src/app-opentui/render/constants.js +0 -2
- package/dist/src/app-opentui/render/graphics.js +3 -9
- package/dist/src/app-opentui/render/hover.d.ts +11 -0
- package/dist/src/app-opentui/render/hover.js +34 -0
- package/dist/src/app-opentui/render/native-host.js +1 -0
- package/dist/src/app-opentui/render/panels.js +50 -3
- package/dist/src/app-opentui/render/preloader.js +1 -0
- package/dist/src/app-opentui/render/scene.js +1 -0
- package/dist/src/app-opentui/render/text.d.ts +13 -0
- package/dist/src/app-opentui/render/text.js +54 -5
- package/dist/src/app-opentui/render/type-bar.js +1 -0
- package/dist/src/app-opentui/runtime.js +62 -1025
- package/dist/src/app-opentui/scenes/command.js +1 -0
- package/dist/src/app-opentui/scenes/doctor.js +1 -0
- package/dist/src/app-opentui/scenes/error.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/agent.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/browser.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/dist.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/git.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/github.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/harness.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/index.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/kit.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/profile.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/queue.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/remote.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/review.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/setup.js +1 -0
- package/dist/src/app-opentui/scenes/family-domains/stats.js +1 -0
- package/dist/src/app-opentui/scenes/family.js +1 -0
- package/dist/src/app-opentui/scenes/fleet.js +3 -5
- package/dist/src/app-opentui/scenes/handoff.js +1 -0
- package/dist/src/app-opentui/scenes/help.js +1 -0
- package/dist/src/app-opentui/scenes/inbox.js +1 -0
- package/dist/src/app-opentui/scenes/init.js +1 -0
- package/dist/src/app-opentui/scenes/inspect.js +1 -0
- package/dist/src/app-opentui/scenes/main.js +1 -0
- package/dist/src/app-opentui/scenes/pi.js +1 -0
- package/dist/src/app-opentui/scenes/plugin.js +1 -0
- package/dist/src/app-opentui/scenes/repo.js +1 -0
- package/dist/src/app-opentui/scenes/run-detail.js +1 -0
- package/dist/src/app-opentui/scenes/server.js +1 -0
- package/dist/src/app-opentui/scenes/tasks.js +3 -5
- package/dist/src/app-opentui/scenes/workspace.js +1 -0
- package/dist/src/app-opentui/selectable.js +1 -0
- package/dist/src/app-opentui/theme.d.ts +1 -0
- package/dist/src/app-opentui/theme.js +1 -0
- package/package.json +8 -8
- package/dist/src/app-opentui/render/image-visual-layer-worker.d.ts +0 -1
- package/dist/src/app-opentui/render/image-visual-layer-worker.js +0 -1084
- package/dist/src/app-opentui/render/image-visual-layer.d.ts +0 -109
- package/dist/src/app-opentui/render/image-visual-layer.js +0 -1068
- package/dist/src/app-opentui/terminal-capabilities.d.ts +0 -7
- package/dist/src/app-opentui/terminal-capabilities.js +0 -15
|
@@ -2245,6 +2245,7 @@ var RIG_UI = {
|
|
|
2245
2245
|
bg2: "#0b0c0e",
|
|
2246
2246
|
panel: "#101115",
|
|
2247
2247
|
panel2: "#14161b",
|
|
2248
|
+
hover: "#181b24",
|
|
2248
2249
|
glass: "#1e2230",
|
|
2249
2250
|
border: "#2a2e3a",
|
|
2250
2251
|
ink: "#f4f5f8",
|
|
@@ -2719,19 +2720,6 @@ function panelContentRect(layout, panel) {
|
|
|
2719
2720
|
const contentWidth = Math.max(1, width - paddingX * 2 - 1);
|
|
2720
2721
|
return { left, top, width, height, paddingX, paddingY, contentWidth };
|
|
2721
2722
|
}
|
|
2722
|
-
function panelPixelPlacement(layout, panel, size, minPixels = 8) {
|
|
2723
|
-
const cellW = size.width / layout.width;
|
|
2724
|
-
const cellH = size.height / layout.height;
|
|
2725
|
-
const leftCells = layout.centerLeft + (panel.left ?? 0);
|
|
2726
|
-
const topCells = layout.centerTop + panel.top;
|
|
2727
|
-
const widthCells = panel.width ?? layout.centerWidth;
|
|
2728
|
-
const heightCells = panel.height;
|
|
2729
|
-
const left = Math.round(leftCells * cellW);
|
|
2730
|
-
const top = Math.round(topCells * cellH);
|
|
2731
|
-
const width = Math.round(widthCells * cellW);
|
|
2732
|
-
const height = Math.round(heightCells * cellH);
|
|
2733
|
-
return width > minPixels && height > minPixels ? { left, top, width, height, leftCells, topCells, widthCells, heightCells } : null;
|
|
2734
|
-
}
|
|
2735
2723
|
|
|
2736
2724
|
// packages/cli/src/app-opentui/render/constants.ts
|
|
2737
2725
|
var LCG_MULTIPLIER = 1664525;
|
|
@@ -2764,7 +2752,6 @@ var RAIL_YS_LOOP = [0.18, 0.5, 0.82];
|
|
|
2764
2752
|
var RAIL_YS_CARRIER = [0.24, 0.54, 0.82];
|
|
2765
2753
|
var RAIL_YS_DISPATCH = [0.18, 0.58, 0.82];
|
|
2766
2754
|
var GLOW_FALLOFF_EXP_ASCII = 2.2;
|
|
2767
|
-
var GLOW_FALLOFF_EXP_IMAGE = 2.4;
|
|
2768
2755
|
|
|
2769
2756
|
// packages/cli/src/app-opentui/render/graphics.ts
|
|
2770
2757
|
var BRAILLE_SAMPLES_X = 2;
|
|
@@ -3196,27 +3183,20 @@ function writeCell(layer, x, y, char, fg, bg) {
|
|
|
3196
3183
|
function drawFullCell(layer, x, y, bg) {
|
|
3197
3184
|
writeCell(layer, x, y, " ", TRANSPARENT, bg);
|
|
3198
3185
|
}
|
|
3199
|
-
function
|
|
3200
|
-
writeCell(layer, x, y, char, fg, BACKDROP);
|
|
3201
|
-
}
|
|
3202
|
-
function drawRoundedFill(layer, left, top, right, bottom, dividerY) {
|
|
3186
|
+
function drawPanelFill(layer, left, top, right, bottom, dividerY) {
|
|
3203
3187
|
const bgForY = (y) => y < dividerY ? PANEL_HEADER_BG : PANEL_BG;
|
|
3204
3188
|
for (let y = top;y <= bottom; y += 1) {
|
|
3205
3189
|
const bg = bgForY(y);
|
|
3206
3190
|
for (let x = left;x <= right; x += 1)
|
|
3207
3191
|
drawFullCell(layer, x, y, bg);
|
|
3208
3192
|
}
|
|
3209
|
-
drawCornerCut(layer, left, top, "\u2597", PANEL_HEADER_BG);
|
|
3210
|
-
drawCornerCut(layer, right, top, "\u2596", PANEL_HEADER_BG);
|
|
3211
|
-
drawCornerCut(layer, left, bottom, "\u259D", PANEL_BG);
|
|
3212
|
-
drawCornerCut(layer, right, bottom, "\u2598", PANEL_BG);
|
|
3213
3193
|
}
|
|
3214
3194
|
function drawAdTerminalPanel(layer, layout, panel) {
|
|
3215
3195
|
const rect = panelCellRect(layout, panel);
|
|
3216
3196
|
if (!rect)
|
|
3217
3197
|
return;
|
|
3218
3198
|
const { left, top, width, right, bottom, headerHeight, dividerY } = rect;
|
|
3219
|
-
|
|
3199
|
+
drawPanelFill(layer, left, top, right, bottom, dividerY);
|
|
3220
3200
|
if (dividerY > top && dividerY < bottom) {
|
|
3221
3201
|
for (let x = left + 2;x < right - 1; x += 1) {
|
|
3222
3202
|
writeCell(layer, x, dividerY, "\u2500", PANEL_LINE_DIM, PANEL_BG);
|
|
@@ -3363,982 +3343,68 @@ function sparklineString(values, width) {
|
|
|
3363
3343
|
}).join("");
|
|
3364
3344
|
}
|
|
3365
3345
|
|
|
3366
|
-
// packages/cli/src/app-opentui/render/
|
|
3367
|
-
import {
|
|
3368
|
-
import { deflateSync } from "zlib";
|
|
3369
|
-
import { allocateImageId, deleteKittyImage } from "@earendil-works/pi-tui";
|
|
3370
|
-
|
|
3371
|
-
// packages/cli/src/app-opentui/terminal-capabilities.ts
|
|
3372
|
-
import { getCapabilities } from "@earendil-works/pi-tui";
|
|
3373
|
-
function getRigTerminalCapabilities() {
|
|
3374
|
-
const imageProtocol = getCapabilities().images;
|
|
3375
|
-
const supportsKittyImages = imageProtocol === "kitty";
|
|
3376
|
-
return {
|
|
3377
|
-
imageProtocol,
|
|
3378
|
-
supportsKittyImages,
|
|
3379
|
-
visualMode: supportsKittyImages ? "kitty-images" : "framebuffer-fallback"
|
|
3380
|
-
};
|
|
3381
|
-
}
|
|
3346
|
+
// packages/cli/src/app-opentui/render/text.ts
|
|
3347
|
+
import { RGBA as RGBA4, TextAttributes as TextAttributes4, TextRenderable } from "@opentui/core";
|
|
3382
3348
|
|
|
3383
|
-
// packages/cli/src/app-opentui/render/
|
|
3384
|
-
var
|
|
3385
|
-
var
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
var PANEL = [16, 17, 21, 184];
|
|
3392
|
-
var PANEL_HEADER = [16, 17, 21, 190];
|
|
3393
|
-
var PANEL_LINE2 = [255, 255, 255, 24];
|
|
3394
|
-
var LIME = [204, 255, 77, 255];
|
|
3395
|
-
var LIME_DIM = [169, 214, 63, 255];
|
|
3396
|
-
var CYAN = [86, 216, 255, 255];
|
|
3397
|
-
var MAGENTA = [255, 121, 176, 255];
|
|
3398
|
-
var INK_DIM = [108, 110, 121, 255];
|
|
3399
|
-
var PNG_SIGNATURE = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
3400
|
-
var CRC_TABLE = makeCrcTable();
|
|
3401
|
-
var MAX_IMAGE_BYTES = 48 * 1024 * 1024;
|
|
3402
|
-
var KITTY_CHUNK_SIZE = 4096;
|
|
3403
|
-
var PANEL_BLUR_SCALE = 1;
|
|
3404
|
-
var PANEL_MASK_CACHE_LIMIT = 16;
|
|
3405
|
-
var ZLIB_LEVEL = 1;
|
|
3406
|
-
var DEFAULT_IMAGE_RESOLUTION_SCALE = 1;
|
|
3407
|
-
var LOAD_BUCKETS = 5;
|
|
3408
|
-
function loadBucket(load) {
|
|
3409
|
-
if (!Number.isFinite(load ?? NaN))
|
|
3410
|
-
return 0;
|
|
3411
|
-
return Math.max(0, Math.min(LOAD_BUCKETS, Math.round((load ?? 0) * LOAD_BUCKETS)));
|
|
3412
|
-
}
|
|
3413
|
-
var panelMaskCache = new Map;
|
|
3414
|
-
var BG_WORD = rgbaWord(BG);
|
|
3415
|
-
function imageTransport() {
|
|
3416
|
-
const forced = process.env.RIG_OPENTUI_IMAGE_TRANSPORT;
|
|
3417
|
-
if (forced === "rgba-zlib")
|
|
3418
|
-
return "rgba-zlib";
|
|
3419
|
-
return "png";
|
|
3420
|
-
}
|
|
3421
|
-
function imageResolutionScale() {
|
|
3422
|
-
const raw = Number.parseFloat(process.env.RIG_OPENTUI_IMAGE_SCALE ?? "");
|
|
3423
|
-
if (Number.isFinite(raw) && raw > 0)
|
|
3424
|
-
return Math.max(0.25, Math.min(1, raw));
|
|
3425
|
-
return DEFAULT_IMAGE_RESOLUTION_SCALE;
|
|
3426
|
-
}
|
|
3427
|
-
function rgbaWord(color) {
|
|
3428
|
-
return (color[3] << 24 | color[2] << 16 | color[1] << 8 | color[0]) >>> 0;
|
|
3429
|
-
}
|
|
3430
|
-
function fillRgba(data, color, word = rgbaWord(color)) {
|
|
3431
|
-
if (data.byteOffset % 4 === 0 && data.byteLength % 4 === 0) {
|
|
3432
|
-
new Uint32Array(data.buffer, data.byteOffset, data.byteLength / 4).fill(word);
|
|
3349
|
+
// packages/cli/src/app-opentui/render/hover.ts
|
|
3350
|
+
var hoveredId;
|
|
3351
|
+
var listeners = new Set;
|
|
3352
|
+
function isHovered(id) {
|
|
3353
|
+
return id !== undefined && id === hoveredId;
|
|
3354
|
+
}
|
|
3355
|
+
function setHoveredSelectableId(id) {
|
|
3356
|
+
if (hoveredId === id)
|
|
3433
3357
|
return;
|
|
3434
|
-
|
|
3435
|
-
for (
|
|
3436
|
-
|
|
3437
|
-
data[i + 1] = color[1];
|
|
3438
|
-
data[i + 2] = color[2];
|
|
3439
|
-
data[i + 3] = color[3];
|
|
3440
|
-
}
|
|
3441
|
-
}
|
|
3442
|
-
var ASCII_GLYPHS = {
|
|
3443
|
-
".": ["00000", "00000", "00000", "00000", "00000", "00100", "00100"],
|
|
3444
|
-
"'": ["00100", "00100", "00000", "00000", "00000", "00000", "00000"],
|
|
3445
|
-
"-": ["00000", "00000", "00000", "11111", "00000", "00000", "00000"],
|
|
3446
|
-
_: ["00000", "00000", "00000", "00000", "00000", "00000", "11111"],
|
|
3447
|
-
"=": ["00000", "11111", "00000", "00000", "11111", "00000", "00000"],
|
|
3448
|
-
"|": ["00100", "00100", "00100", "00100", "00100", "00100", "00100"],
|
|
3449
|
-
"/": ["00001", "00010", "00010", "00100", "01000", "01000", "10000"],
|
|
3450
|
-
"\\": ["10000", "01000", "01000", "00100", "00010", "00010", "00001"],
|
|
3451
|
-
"(": ["00010", "00100", "01000", "01000", "01000", "00100", "00010"],
|
|
3452
|
-
")": ["01000", "00100", "00010", "00010", "00010", "00100", "01000"],
|
|
3453
|
-
"!": ["00100", "00100", "00100", "00100", "00100", "00000", "00100"],
|
|
3454
|
-
"%": ["11001", "11010", "00100", "01000", "10110", "00110", "00000"],
|
|
3455
|
-
$: ["00100", "11110", "10100", "11110", "00101", "11110", "00100"],
|
|
3456
|
-
"@": ["01110", "10001", "10111", "10101", "10111", "10000", "01111"],
|
|
3457
|
-
o: ["00000", "01110", "10001", "10001", "10001", "01110", "00000"],
|
|
3458
|
-
"*": ["00100", "10101", "01110", "11111", "01110", "10101", "00100"]
|
|
3459
|
-
};
|
|
3460
|
-
function sceneStaticTick(scene) {
|
|
3461
|
-
let hash = 0;
|
|
3462
|
-
for (let index = 0;index < scene.length; index += 1)
|
|
3463
|
-
hash = hash * 33 + scene.charCodeAt(index) >>> 0;
|
|
3464
|
-
return 11 + hash % 97;
|
|
3465
|
-
}
|
|
3466
|
-
function charColor(char, row, totalRows) {
|
|
3467
|
-
if (char === "@" || char === "$" || char === "o")
|
|
3468
|
-
return LIME;
|
|
3469
|
-
if (char === "%" || char === "!" || char === "/" || char === "\\" || char === "|")
|
|
3470
|
-
return CYAN;
|
|
3471
|
-
if (char === "." || char === "'" || char === "_" || row < 3 || row > totalRows - 4)
|
|
3472
|
-
return INK_DIM;
|
|
3473
|
-
if (char === "-" || char === "=" || char === "(" || char === ")")
|
|
3474
|
-
return LIME_DIM;
|
|
3475
|
-
return MAGENTA;
|
|
3476
|
-
}
|
|
3477
|
-
function fillRectAlpha(data, width, height, left, top, rectWidth, rectHeight, color, alpha) {
|
|
3478
|
-
const x0 = Math.max(0, Math.floor(left));
|
|
3479
|
-
const y0 = Math.max(0, Math.floor(top));
|
|
3480
|
-
const x1 = Math.min(width, Math.ceil(left + rectWidth));
|
|
3481
|
-
const y1 = Math.min(height, Math.ceil(top + rectHeight));
|
|
3482
|
-
for (let y = y0;y < y1; y += 1) {
|
|
3483
|
-
for (let x = x0;x < x1; x += 1)
|
|
3484
|
-
blendOver(data, (y * width + x) * 4, color, alpha);
|
|
3485
|
-
}
|
|
3486
|
-
}
|
|
3487
|
-
function drawAsciiGlyph(data, width, height, x, y, cellWidth, cellHeight, char, color, alpha) {
|
|
3488
|
-
const pattern = ASCII_GLYPHS[char] ?? ASCII_GLYPHS["*"];
|
|
3489
|
-
const glyphWidth = Math.max(1, cellWidth * 0.56);
|
|
3490
|
-
const glyphHeight = Math.max(1, cellHeight * 0.7);
|
|
3491
|
-
const originX = x + (cellWidth - glyphWidth) * 0.5;
|
|
3492
|
-
const originY = y + (cellHeight - glyphHeight) * 0.5;
|
|
3493
|
-
const pixelWidth = Math.max(1, glyphWidth / pattern[0].length);
|
|
3494
|
-
const pixelHeight = Math.max(1, glyphHeight / pattern.length);
|
|
3495
|
-
for (let row = 0;row < pattern.length; row += 1) {
|
|
3496
|
-
const bits = pattern[row];
|
|
3497
|
-
for (let col = 0;col < bits.length; col += 1) {
|
|
3498
|
-
if (bits[col] !== "1")
|
|
3499
|
-
continue;
|
|
3500
|
-
fillRectAlpha(data, width, height, originX + col * pixelWidth, originY + row * pixelHeight, pixelWidth * 0.72, pixelHeight * 0.72, color, alpha);
|
|
3501
|
-
}
|
|
3502
|
-
}
|
|
3503
|
-
}
|
|
3504
|
-
function drawStaticAsciiFleetBackground(data, width, height, scene, layout, load, activeRuns) {
|
|
3505
|
-
const rows = fleetRows(sceneStaticTick(scene), { animate: true });
|
|
3506
|
-
const cellWidth = width / Math.max(1, layout.width);
|
|
3507
|
-
const cellHeight = height / Math.max(1, layout.height);
|
|
3508
|
-
const sideBySide = layout.width >= FLEET_GRID_WIDTH * 1.9;
|
|
3509
|
-
const leftCells = sideBySide ? Math.max(1, Math.floor(layout.width * 0.3 - FLEET_GRID_WIDTH / 2)) : Math.floor((layout.width - FLEET_GRID_WIDTH) / 2);
|
|
3510
|
-
const topCells = Math.floor((layout.height - FLEET_GRID_HEIGHT) / 2);
|
|
3511
|
-
const fleetLeft = leftCells * cellWidth;
|
|
3512
|
-
const fleetTop = topCells * cellHeight;
|
|
3513
|
-
drawGlow2(data, width, height, width * 0.5, height * 0.5, Math.min(width, height) * 0.4, LIME, 0.022 + load * 0.03);
|
|
3514
|
-
drawGlow2(data, width, height, width * 0.18, height * 0.22, Math.min(width, height) * 0.28, CYAN, 0.018 + load * 0.012);
|
|
3515
|
-
drawGlow2(data, width, height, width * 0.82, height * 0.78, Math.min(width, height) * 0.22, MAGENTA, 0.012 + load * 0.008);
|
|
3516
|
-
let droneTotal = 0;
|
|
3517
|
-
for (const line2 of rows)
|
|
3518
|
-
for (const char of line2)
|
|
3519
|
-
if (char === "@" || char === "$" || char === "%")
|
|
3520
|
-
droneTotal += 1;
|
|
3521
|
-
const litTarget = Math.max(0, Math.min(droneTotal, activeRuns));
|
|
3522
|
-
let droneIndex = 0;
|
|
3523
|
-
for (let row = 0;row < rows.length; row += 1) {
|
|
3524
|
-
const line2 = rows[row];
|
|
3525
|
-
for (let col = 0;col < line2.length; col += 1) {
|
|
3526
|
-
const char = line2[col];
|
|
3527
|
-
if (char === " ")
|
|
3528
|
-
continue;
|
|
3529
|
-
const x = fleetLeft + col * cellWidth;
|
|
3530
|
-
const y = fleetTop + row * cellHeight;
|
|
3531
|
-
const color = charColor(char, row, rows.length);
|
|
3532
|
-
const isDrone = char === "@" || char === "$" || char === "%";
|
|
3533
|
-
const lit = isDrone && droneIndex < litTarget;
|
|
3534
|
-
drawAsciiGlyph(data, width, height, x + cellWidth * 0.07, y + cellHeight * 0.05, cellWidth, cellHeight, char, color, lit ? 0.72 : 0.46);
|
|
3535
|
-
if (isDrone) {
|
|
3536
|
-
droneIndex += 1;
|
|
3537
|
-
drawGlow2(data, width, height, x + cellWidth * 0.5, y + cellHeight * 0.5, Math.max(cellWidth, cellHeight) * 0.95, color, lit ? 0.044 : 0.014);
|
|
3538
|
-
}
|
|
3539
|
-
}
|
|
3540
|
-
}
|
|
3541
|
-
}
|
|
3542
|
-
function applyStaticAtmosphere(data, width, height, load) {
|
|
3543
|
-
const cx = width * 0.5;
|
|
3544
|
-
const cy = height * 0.42;
|
|
3545
|
-
const radius = Math.max(width, height) * 0.78;
|
|
3546
|
-
const minVignette = 0.1 - load * 0.02;
|
|
3547
|
-
for (let y = 0;y < height; y += 1) {
|
|
3548
|
-
for (let x = 0;x < width; x += 1) {
|
|
3549
|
-
const distance = Math.hypot(x - cx, y - cy) / radius;
|
|
3550
|
-
const alpha = Math.max(minVignette, Math.min(0.82, 0.12 + Math.pow(distance, 1.65) * 0.62));
|
|
3551
|
-
blendOver(data, (y * width + x) * 4, BG, alpha);
|
|
3552
|
-
}
|
|
3553
|
-
}
|
|
3554
|
-
drawGlow2(data, width, height, width * 0.82, height * 0.04, Math.min(width, height) * 0.2, LIME, 0.024 + load * 0.012);
|
|
3555
|
-
drawGlow2(data, width, height, width * 0.12, height * 0.18, Math.min(width, height) * 0.16, CYAN, 0.018 + load * 0.008);
|
|
3556
|
-
}
|
|
3557
|
-
function makeCrcTable() {
|
|
3558
|
-
const table = new Uint32Array(256);
|
|
3559
|
-
for (let n = 0;n < 256; n += 1) {
|
|
3560
|
-
let c = n;
|
|
3561
|
-
for (let k = 0;k < 8; k += 1)
|
|
3562
|
-
c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
3563
|
-
table[n] = c >>> 0;
|
|
3564
|
-
}
|
|
3565
|
-
return table;
|
|
3566
|
-
}
|
|
3567
|
-
function crc32(bytes) {
|
|
3568
|
-
let c = 4294967295;
|
|
3569
|
-
for (let i = 0;i < bytes.length; i += 1)
|
|
3570
|
-
c = CRC_TABLE[(c ^ bytes[i]) & 255] ^ c >>> 8;
|
|
3571
|
-
return (c ^ 4294967295) >>> 0;
|
|
3572
|
-
}
|
|
3573
|
-
function chunk2(type, data) {
|
|
3574
|
-
const out = new Uint8Array(12 + data.length);
|
|
3575
|
-
const view = new DataView(out.buffer);
|
|
3576
|
-
view.setUint32(0, data.length);
|
|
3577
|
-
out[4] = type.charCodeAt(0);
|
|
3578
|
-
out[5] = type.charCodeAt(1);
|
|
3579
|
-
out[6] = type.charCodeAt(2);
|
|
3580
|
-
out[7] = type.charCodeAt(3);
|
|
3581
|
-
out.set(data, 8);
|
|
3582
|
-
view.setUint32(8 + data.length, crc32(out.subarray(4, 8 + data.length)));
|
|
3583
|
-
return out;
|
|
3584
|
-
}
|
|
3585
|
-
function encodePng(width, height, rgba) {
|
|
3586
|
-
const ihdr = new Uint8Array(13);
|
|
3587
|
-
const ihdrView = new DataView(ihdr.buffer);
|
|
3588
|
-
ihdrView.setUint32(0, width);
|
|
3589
|
-
ihdrView.setUint32(4, height);
|
|
3590
|
-
ihdr[8] = 8;
|
|
3591
|
-
ihdr[9] = 6;
|
|
3592
|
-
ihdr[10] = 0;
|
|
3593
|
-
ihdr[11] = 0;
|
|
3594
|
-
ihdr[12] = 0;
|
|
3595
|
-
const stride = width * 4;
|
|
3596
|
-
const raw = new Uint8Array((stride + 1) * height);
|
|
3597
|
-
for (let y = 0;y < height; y += 1) {
|
|
3598
|
-
const rawOffset = y * (stride + 1);
|
|
3599
|
-
raw[rawOffset] = 0;
|
|
3600
|
-
raw.set(rgba.subarray(y * stride, y * stride + stride), rawOffset + 1);
|
|
3601
|
-
}
|
|
3602
|
-
const compressed = deflateSync(raw, { level: ZLIB_LEVEL });
|
|
3603
|
-
return Buffer.concat([
|
|
3604
|
-
Buffer.from(PNG_SIGNATURE),
|
|
3605
|
-
Buffer.from(chunk2("IHDR", ihdr)),
|
|
3606
|
-
Buffer.from(chunk2("IDAT", compressed)),
|
|
3607
|
-
Buffer.from(chunk2("IEND", new Uint8Array(0)))
|
|
3608
|
-
]);
|
|
3609
|
-
}
|
|
3610
|
-
function kittyImageSequence(base64, options) {
|
|
3611
|
-
const params = [`a=T`, `f=${options.format}`, `q=2`, `C=1`, `c=${options.columns}`, `r=${options.rows}`, `i=${options.imageId}`, `z=${options.zIndex}`];
|
|
3612
|
-
if (options.format === 32) {
|
|
3613
|
-
params.push(`s=${options.pixelWidth ?? 1}`, `v=${options.pixelHeight ?? 1}`);
|
|
3614
|
-
if (options.compressed)
|
|
3615
|
-
params.push("o=z");
|
|
3616
|
-
}
|
|
3617
|
-
if (base64.length <= KITTY_CHUNK_SIZE)
|
|
3618
|
-
return `\x1B_G${params.join(",")};${base64}\x1B\\`;
|
|
3619
|
-
const chunks = [];
|
|
3620
|
-
for (let offset = 0;offset < base64.length; offset += KITTY_CHUNK_SIZE) {
|
|
3621
|
-
const part = base64.slice(offset, offset + KITTY_CHUNK_SIZE);
|
|
3622
|
-
const last = offset + KITTY_CHUNK_SIZE >= base64.length;
|
|
3623
|
-
chunks.push(`\x1B_G${offset === 0 ? `${params.join(",")},` : ""}m=${last ? 0 : 1};${part}\x1B\\`);
|
|
3624
|
-
}
|
|
3625
|
-
return chunks.join("");
|
|
3626
|
-
}
|
|
3627
|
-
function clampByte(value) {
|
|
3628
|
-
return Math.max(0, Math.min(255, Math.round(value)));
|
|
3629
|
-
}
|
|
3630
|
-
function blendOver(data, index, color, alphaScale = 1) {
|
|
3631
|
-
const alpha = color[3] / 255 * alphaScale;
|
|
3632
|
-
const inv = 1 - alpha;
|
|
3633
|
-
data[index] = clampByte(color[0] * alpha + data[index] * inv);
|
|
3634
|
-
data[index + 1] = clampByte(color[1] * alpha + data[index + 1] * inv);
|
|
3635
|
-
data[index + 2] = clampByte(color[2] * alpha + data[index + 2] * inv);
|
|
3636
|
-
data[index + 3] = 255;
|
|
3637
|
-
}
|
|
3638
|
-
function drawDisc(data, width, height, cx, cy, radius, color, alpha = 1) {
|
|
3639
|
-
const minX = Math.max(0, Math.floor(cx - radius));
|
|
3640
|
-
const maxX = Math.min(width - 1, Math.ceil(cx + radius));
|
|
3641
|
-
const minY = Math.max(0, Math.floor(cy - radius));
|
|
3642
|
-
const maxY = Math.min(height - 1, Math.ceil(cy + radius));
|
|
3643
|
-
for (let y = minY;y <= maxY; y += 1) {
|
|
3644
|
-
for (let x = minX;x <= maxX; x += 1) {
|
|
3645
|
-
const dx = x + 0.5 - cx;
|
|
3646
|
-
const dy = y + 0.5 - cy;
|
|
3647
|
-
const d = Math.sqrt(dx * dx + dy * dy);
|
|
3648
|
-
if (d > radius)
|
|
3649
|
-
continue;
|
|
3650
|
-
const edge = Math.max(0, Math.min(1, radius - d));
|
|
3651
|
-
blendOver(data, (y * width + x) * 4, color, alpha * Math.min(1, 0.35 + edge));
|
|
3652
|
-
}
|
|
3653
|
-
}
|
|
3358
|
+
hoveredId = id;
|
|
3359
|
+
for (const listener of listeners)
|
|
3360
|
+
listener();
|
|
3654
3361
|
}
|
|
3655
|
-
function
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
const minY = Math.max(0, Math.floor(cy - radius));
|
|
3659
|
-
const maxY = Math.min(height - 1, Math.ceil(cy + radius));
|
|
3660
|
-
for (let y = minY;y <= maxY; y += 1) {
|
|
3661
|
-
for (let x = minX;x <= maxX; x += 1) {
|
|
3662
|
-
const dx = x + 0.5 - cx;
|
|
3663
|
-
const dy = y + 0.5 - cy;
|
|
3664
|
-
const d = Math.sqrt(dx * dx + dy * dy);
|
|
3665
|
-
if (d > radius)
|
|
3666
|
-
continue;
|
|
3667
|
-
const falloff = Math.pow(1 - d / Math.max(1, radius), GLOW_FALLOFF_EXP_IMAGE);
|
|
3668
|
-
blendOver(data, (y * width + x) * 4, color, alpha * falloff);
|
|
3669
|
-
}
|
|
3670
|
-
}
|
|
3362
|
+
function clearHoveredSelectableId(id) {
|
|
3363
|
+
if (id !== undefined && hoveredId === id)
|
|
3364
|
+
setHoveredSelectableId(undefined);
|
|
3671
3365
|
}
|
|
3672
|
-
function
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
for (let i = 0;i <= steps; i += 1) {
|
|
3677
|
-
const t2 = i / steps;
|
|
3678
|
-
drawDisc(data, width, height, x0 + dx * t2, y0 + dy * t2, thickness, color, alpha);
|
|
3679
|
-
}
|
|
3680
|
-
}
|
|
3681
|
-
function roundRectAlpha(px, py, x, y, w, h, r) {
|
|
3682
|
-
const rx = Math.max(x + r, Math.min(px, x + w - r));
|
|
3683
|
-
const ry = Math.max(y + r, Math.min(py, y + h - r));
|
|
3684
|
-
const dx = px - rx;
|
|
3685
|
-
const dy = py - ry;
|
|
3686
|
-
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
3687
|
-
return Math.max(0, Math.min(1, r + 0.75 - dist));
|
|
3688
|
-
}
|
|
3689
|
-
function panelRect(layout, panel, size) {
|
|
3690
|
-
return panelPixelPlacement(layout, panel, size);
|
|
3691
|
-
}
|
|
3692
|
-
function downsampleRegion(data, canvasW, canvasH, rect, scale) {
|
|
3693
|
-
const w = Math.max(1, Math.ceil(rect.width / scale));
|
|
3694
|
-
const h = Math.max(1, Math.ceil(rect.height / scale));
|
|
3695
|
-
const out = new Uint8ClampedArray(w * h * 3);
|
|
3696
|
-
for (let y = 0;y < h; y += 1) {
|
|
3697
|
-
for (let x = 0;x < w; x += 1) {
|
|
3698
|
-
let r = 0, g = 0, b = 0, count = 0;
|
|
3699
|
-
const sx0 = rect.left + x * scale;
|
|
3700
|
-
const sy0 = rect.top + y * scale;
|
|
3701
|
-
for (let yy = 0;yy < scale; yy += 1) {
|
|
3702
|
-
const sy = sy0 + yy;
|
|
3703
|
-
if (sy < 0 || sy >= canvasH)
|
|
3704
|
-
continue;
|
|
3705
|
-
for (let xx = 0;xx < scale; xx += 1) {
|
|
3706
|
-
const sx = sx0 + xx;
|
|
3707
|
-
if (sx < 0 || sx >= canvasW)
|
|
3708
|
-
continue;
|
|
3709
|
-
const i = (sy * canvasW + sx) * 4;
|
|
3710
|
-
r += data[i];
|
|
3711
|
-
g += data[i + 1];
|
|
3712
|
-
b += data[i + 2];
|
|
3713
|
-
count += 1;
|
|
3714
|
-
}
|
|
3715
|
-
}
|
|
3716
|
-
const oi = (y * w + x) * 3;
|
|
3717
|
-
out[oi] = count ? r / count : BG[0];
|
|
3718
|
-
out[oi + 1] = count ? g / count : BG[1];
|
|
3719
|
-
out[oi + 2] = count ? b / count : BG[2];
|
|
3720
|
-
}
|
|
3721
|
-
}
|
|
3722
|
-
return { pixels: out, width: w, height: h };
|
|
3723
|
-
}
|
|
3724
|
-
function blurRgb(buffer, width, height, radius) {
|
|
3725
|
-
const tmp = new Uint8ClampedArray(buffer.length);
|
|
3726
|
-
const out = new Uint8ClampedArray(buffer.length);
|
|
3727
|
-
const window = radius * 2 + 1;
|
|
3728
|
-
for (let y = 0;y < height; y += 1) {
|
|
3729
|
-
let r = 0, g = 0, b = 0;
|
|
3730
|
-
for (let x = -radius;x <= radius; x += 1) {
|
|
3731
|
-
const sx = Math.max(0, Math.min(width - 1, x));
|
|
3732
|
-
const i = (y * width + sx) * 3;
|
|
3733
|
-
r += buffer[i];
|
|
3734
|
-
g += buffer[i + 1];
|
|
3735
|
-
b += buffer[i + 2];
|
|
3736
|
-
}
|
|
3737
|
-
for (let x = 0;x < width; x += 1) {
|
|
3738
|
-
const oi = (y * width + x) * 3;
|
|
3739
|
-
tmp[oi] = r / window;
|
|
3740
|
-
tmp[oi + 1] = g / window;
|
|
3741
|
-
tmp[oi + 2] = b / window;
|
|
3742
|
-
const removeX = Math.max(0, x - radius);
|
|
3743
|
-
const addX = Math.min(width - 1, x + radius + 1);
|
|
3744
|
-
const ri = (y * width + removeX) * 3;
|
|
3745
|
-
const ai = (y * width + addX) * 3;
|
|
3746
|
-
r += tmp.length ? buffer[ai] - buffer[ri] : 0;
|
|
3747
|
-
g += tmp.length ? buffer[ai + 1] - buffer[ri + 1] : 0;
|
|
3748
|
-
b += tmp.length ? buffer[ai + 2] - buffer[ri + 2] : 0;
|
|
3749
|
-
}
|
|
3750
|
-
}
|
|
3751
|
-
for (let x = 0;x < width; x += 1) {
|
|
3752
|
-
let r = 0, g = 0, b = 0;
|
|
3753
|
-
for (let y = -radius;y <= radius; y += 1) {
|
|
3754
|
-
const sy = Math.max(0, Math.min(height - 1, y));
|
|
3755
|
-
const i = (sy * width + x) * 3;
|
|
3756
|
-
r += tmp[i];
|
|
3757
|
-
g += tmp[i + 1];
|
|
3758
|
-
b += tmp[i + 2];
|
|
3759
|
-
}
|
|
3760
|
-
for (let y = 0;y < height; y += 1) {
|
|
3761
|
-
const oi = (y * width + x) * 3;
|
|
3762
|
-
out[oi] = r / window;
|
|
3763
|
-
out[oi + 1] = g / window;
|
|
3764
|
-
out[oi + 2] = b / window;
|
|
3765
|
-
const removeY = Math.max(0, y - radius);
|
|
3766
|
-
const addY = Math.min(height - 1, y + radius + 1);
|
|
3767
|
-
const ri = (removeY * width + x) * 3;
|
|
3768
|
-
const ai = (addY * width + x) * 3;
|
|
3769
|
-
r += tmp[ai] - tmp[ri];
|
|
3770
|
-
g += tmp[ai + 1] - tmp[ri + 1];
|
|
3771
|
-
b += tmp[ai + 2] - tmp[ri + 2];
|
|
3772
|
-
}
|
|
3773
|
-
}
|
|
3774
|
-
return out;
|
|
3775
|
-
}
|
|
3776
|
-
function panelMaskKey(width, height, rect, headerPx, blurScale) {
|
|
3777
|
-
return `${width}x${height}:${rect.left},${rect.top},${rect.width},${rect.height}:${Math.round(headerPx * 100) / 100}:${blurScale}`;
|
|
3778
|
-
}
|
|
3779
|
-
function getPanelMask(width, height, rect, headerPx, blurScale) {
|
|
3780
|
-
const key = panelMaskKey(width, height, rect, headerPx, blurScale);
|
|
3781
|
-
const cached = panelMaskCache.get(key);
|
|
3782
|
-
if (cached)
|
|
3783
|
-
return cached;
|
|
3784
|
-
const radius = Math.max(PANEL_MASK_RADIUS_MIN, Math.min(PANEL_MASK_RADIUS_MAX, Math.round(Math.min(rect.width, rect.height) * PANEL_MASK_RADIUS_FACTOR)));
|
|
3785
|
-
const sampledWidth = Math.max(1, Math.ceil(rect.width / blurScale));
|
|
3786
|
-
const sampledHeight = Math.max(1, Math.ceil(rect.height / blurScale));
|
|
3787
|
-
const x0 = Math.max(0, rect.left);
|
|
3788
|
-
const y0 = Math.max(0, rect.top);
|
|
3789
|
-
const x1 = Math.min(width - 1, rect.left + rect.width - 1);
|
|
3790
|
-
const y1 = Math.min(height - 1, rect.top + rect.height - 1);
|
|
3791
|
-
const indices = [];
|
|
3792
|
-
const sampleIndices = [];
|
|
3793
|
-
const panelAlpha = [];
|
|
3794
|
-
const edgeAlpha = [];
|
|
3795
|
-
const headerFlags = [];
|
|
3796
|
-
const dividerIndices = [];
|
|
3797
|
-
for (let y = y0;y <= y1; y += 1) {
|
|
3798
|
-
for (let x = x0;x <= x1; x += 1) {
|
|
3799
|
-
const a = roundRectAlpha(x + 0.5, y + 0.5, rect.left, rect.top, rect.width, rect.height, radius);
|
|
3800
|
-
if (a <= 0)
|
|
3801
|
-
continue;
|
|
3802
|
-
const edge = roundRectAlpha(x + 0.5, y + 0.5, rect.left + 1.2, rect.top + 1.2, rect.width - 2.4, rect.height - 2.4, Math.max(1, radius - 1.2));
|
|
3803
|
-
const sx = Math.max(0, Math.min(sampledWidth - 1, Math.floor((x - rect.left) / blurScale)));
|
|
3804
|
-
const sy = Math.max(0, Math.min(sampledHeight - 1, Math.floor((y - rect.top) / blurScale)));
|
|
3805
|
-
indices.push((y * width + x) * 4);
|
|
3806
|
-
sampleIndices.push((sy * sampledWidth + sx) * 3);
|
|
3807
|
-
panelAlpha.push(a);
|
|
3808
|
-
edgeAlpha.push(edge < 0.96 ? Math.min(1, (1 - edge) * 0.7) : 0);
|
|
3809
|
-
headerFlags.push(y < rect.top + headerPx ? 1 : 0);
|
|
3810
|
-
}
|
|
3811
|
-
}
|
|
3812
|
-
const dividerY = Math.round(rect.top + headerPx);
|
|
3813
|
-
if (dividerY > y0 && dividerY < y1) {
|
|
3814
|
-
for (let x = x0 + radius;x <= x1 - radius; x += 1) {
|
|
3815
|
-
dividerIndices.push((dividerY * width + x) * 4);
|
|
3816
|
-
}
|
|
3817
|
-
}
|
|
3818
|
-
const mask = {
|
|
3819
|
-
key,
|
|
3820
|
-
indices: Uint32Array.from(indices),
|
|
3821
|
-
sampleIndices: Uint32Array.from(sampleIndices),
|
|
3822
|
-
panelAlpha: Float32Array.from(panelAlpha),
|
|
3823
|
-
edgeAlpha: Float32Array.from(edgeAlpha),
|
|
3824
|
-
headerFlags: Uint8Array.from(headerFlags),
|
|
3825
|
-
dividerIndices: Uint32Array.from(dividerIndices)
|
|
3366
|
+
function subscribeHover(listener) {
|
|
3367
|
+
listeners.add(listener);
|
|
3368
|
+
return () => {
|
|
3369
|
+
listeners.delete(listener);
|
|
3826
3370
|
};
|
|
3827
|
-
panelMaskCache.set(key, mask);
|
|
3828
|
-
while (panelMaskCache.size > PANEL_MASK_CACHE_LIMIT) {
|
|
3829
|
-
const oldest = panelMaskCache.keys().next().value;
|
|
3830
|
-
if (!oldest)
|
|
3831
|
-
break;
|
|
3832
|
-
panelMaskCache.delete(oldest);
|
|
3833
|
-
}
|
|
3834
|
-
return mask;
|
|
3835
|
-
}
|
|
3836
|
-
function compositePanel(data, width, height, rect, headerPx) {
|
|
3837
|
-
const blurScale = PANEL_BLUR_SCALE;
|
|
3838
|
-
const sampled = downsampleRegion(data, width, height, rect, blurScale);
|
|
3839
|
-
const blurRadius = Math.max(PANEL_BLUR_RADIUS_MIN, Math.min(PANEL_BLUR_RADIUS_MAX, Math.round(Math.min(rect.width, rect.height) * PANEL_BLUR_RADIUS_FACTOR)));
|
|
3840
|
-
const blurred = blurRgb(sampled.pixels, sampled.width, sampled.height, blurRadius);
|
|
3841
|
-
const mask = getPanelMask(width, height, rect, headerPx, blurScale);
|
|
3842
|
-
for (let i = 0;i < mask.indices.length; i += 1) {
|
|
3843
|
-
const idx = mask.indices[i];
|
|
3844
|
-
const sampleIdx = mask.sampleIndices[i];
|
|
3845
|
-
data[idx] = blurred[sampleIdx];
|
|
3846
|
-
data[idx + 1] = blurred[sampleIdx + 1];
|
|
3847
|
-
data[idx + 2] = blurred[sampleIdx + 2];
|
|
3848
|
-
data[idx + 3] = 255;
|
|
3849
|
-
blendOver(data, idx, mask.headerFlags[i] ? PANEL_HEADER : PANEL, mask.panelAlpha[i]);
|
|
3850
|
-
const edge = mask.edgeAlpha[i];
|
|
3851
|
-
if (edge > 0)
|
|
3852
|
-
blendOver(data, idx, PANEL_LINE2, edge);
|
|
3853
|
-
}
|
|
3854
|
-
for (let i = 0;i < mask.dividerIndices.length; i += 1) {
|
|
3855
|
-
blendOver(data, mask.dividerIndices[i], PANEL_LINE2, 0.28);
|
|
3856
|
-
}
|
|
3857
|
-
}
|
|
3858
|
-
var RIG_GLYPH_STROKES = {
|
|
3859
|
-
R: [[0, 0, 0, 1], [0, 0, 0.82, 0], [0.82, 0, 0.82, 0.5], [0, 0.5, 0.82, 0.5], [0.36, 0.5, 0.92, 1]],
|
|
3860
|
-
I: [[0.5, 0, 0.5, 1], [0.18, 0, 0.82, 0], [0.18, 1, 0.82, 1]],
|
|
3861
|
-
G: [[0.92, 0.08, 0.12, 0], [0.12, 0, 0.12, 1], [0.12, 1, 0.92, 1], [0.92, 0.5, 0.92, 1], [0.5, 0.5, 0.92, 0.5]]
|
|
3862
|
-
};
|
|
3863
|
-
function drawBigRigWordmark(data, width, height, layout, load) {
|
|
3864
|
-
const sideBySide = layout.width >= FLEET_GRID_WIDTH * 1.9;
|
|
3865
|
-
if (!sideBySide)
|
|
3866
|
-
return;
|
|
3867
|
-
const letters = "RIG";
|
|
3868
|
-
const min = Math.min(width, height);
|
|
3869
|
-
const glyphH = height * 0.42;
|
|
3870
|
-
const glyphW = width * 0.13;
|
|
3871
|
-
const gap = glyphW * 0.3;
|
|
3872
|
-
const totalW = letters.length * glyphW + (letters.length - 1) * gap;
|
|
3873
|
-
const centerX = width * 0.71;
|
|
3874
|
-
const startX = centerX - totalW / 2;
|
|
3875
|
-
const top = (height - glyphH) / 2;
|
|
3876
|
-
const stroke = Math.max(2, min * 0.013);
|
|
3877
|
-
drawGlow2(data, width, height, centerX, top + glyphH / 2, Math.max(glyphW, glyphH) * 0.9, LIME, 0.022 + load * 0.02);
|
|
3878
|
-
const strokeAlpha = 0.1 + load * 0.06;
|
|
3879
|
-
letters.split("").forEach((letter, index) => {
|
|
3880
|
-
const segments = RIG_GLYPH_STROKES[letter];
|
|
3881
|
-
if (!segments)
|
|
3882
|
-
return;
|
|
3883
|
-
const ox = startX + index * (glyphW + gap);
|
|
3884
|
-
for (const [x0, y0, x1, y1] of segments) {
|
|
3885
|
-
drawLine2(data, width, height, ox + x0 * glyphW, top + y0 * glyphH, ox + x1 * glyphW, top + y1 * glyphH, LIME_DIM, strokeAlpha, stroke);
|
|
3886
|
-
}
|
|
3887
|
-
});
|
|
3888
|
-
}
|
|
3889
|
-
function drawBackground(data, width, height, _tick, scene, layout, load, activeRuns) {
|
|
3890
|
-
fillRgba(data, BG, BG_WORD);
|
|
3891
|
-
drawStaticAsciiFleetBackground(data, width, height, scene, layout, load, activeRuns);
|
|
3892
|
-
drawBigRigWordmark(data, width, height, layout, load);
|
|
3893
|
-
applyStaticAtmosphere(data, width, height, load);
|
|
3894
|
-
}
|
|
3895
|
-
function imageVisualFrameKey(input) {
|
|
3896
|
-
const width = Math.max(1, Math.floor(input.pixelSize.width));
|
|
3897
|
-
const height = Math.max(1, Math.floor(input.pixelSize.height));
|
|
3898
|
-
const layer = "layer" in input ? input.layer ?? "full" : "full";
|
|
3899
|
-
const load = loadBucket(input.load);
|
|
3900
|
-
return `${layer}:${width}x${height}:${input.layout.width}x${input.layout.height}:${input.scene}:${input.tick}:l${load}:${input.panels.map((panel) => `${panel.id}:${panel.top}:${panel.height}:${panel.width ?? ""}:${panel.left ?? ""}:${panel.headerHeight ?? ""}`).join("|")}`;
|
|
3901
|
-
}
|
|
3902
|
-
function encodeKittyFrame(width, height, data, input) {
|
|
3903
|
-
if (input.transport === "png") {
|
|
3904
|
-
const png = encodePng(width, height, data);
|
|
3905
|
-
return kittyImageSequence(png.toString("base64"), { columns: input.layout.width, rows: input.layout.height, imageId: input.imageId, zIndex: input.zIndex, format: 100 });
|
|
3906
|
-
}
|
|
3907
|
-
const compressed = deflateSync(data, { level: ZLIB_LEVEL });
|
|
3908
|
-
return kittyImageSequence(compressed.toString("base64"), {
|
|
3909
|
-
columns: input.layout.width,
|
|
3910
|
-
rows: input.layout.height,
|
|
3911
|
-
imageId: input.imageId,
|
|
3912
|
-
zIndex: input.zIndex,
|
|
3913
|
-
format: 32,
|
|
3914
|
-
pixelWidth: width,
|
|
3915
|
-
pixelHeight: height,
|
|
3916
|
-
compressed: true
|
|
3917
|
-
});
|
|
3918
|
-
}
|
|
3919
|
-
function cropRgba(data, sourceWidth, rect) {
|
|
3920
|
-
const out = new Uint8ClampedArray(rect.width * rect.height * 4);
|
|
3921
|
-
for (let y = 0;y < rect.height; y += 1) {
|
|
3922
|
-
const sourceStart = ((rect.top + y) * sourceWidth + rect.left) * 4;
|
|
3923
|
-
out.set(data.subarray(sourceStart, sourceStart + rect.width * 4), y * rect.width * 4);
|
|
3924
|
-
}
|
|
3925
|
-
return out;
|
|
3926
|
-
}
|
|
3927
|
-
function encodePanelPatch(data, sourceWidth, placement, imageId, zIndex) {
|
|
3928
|
-
const patch = cropRgba(data, sourceWidth, placement);
|
|
3929
|
-
const png = encodePng(placement.width, placement.height, patch);
|
|
3930
|
-
const sequence = kittyImageSequence(png.toString("base64"), {
|
|
3931
|
-
columns: Math.max(1, Math.round(placement.widthCells)),
|
|
3932
|
-
rows: Math.max(1, Math.round(placement.heightCells)),
|
|
3933
|
-
imageId,
|
|
3934
|
-
zIndex,
|
|
3935
|
-
format: 100
|
|
3936
|
-
});
|
|
3937
|
-
return `\x1B[${Math.max(1, Math.round(placement.topCells) + 1)};${Math.max(1, Math.round(placement.leftCells) + 1)}H${sequence}`;
|
|
3938
|
-
}
|
|
3939
|
-
function renderImageVisualFrame(input) {
|
|
3940
|
-
const width = Math.max(1, Math.floor(input.pixelSize.width));
|
|
3941
|
-
const height = Math.max(1, Math.floor(input.pixelSize.height));
|
|
3942
|
-
const byteCount = width * height * 4;
|
|
3943
|
-
if (byteCount > MAX_IMAGE_BYTES)
|
|
3944
|
-
return null;
|
|
3945
|
-
const layer = input.layer ?? "full";
|
|
3946
|
-
const data = new Uint8ClampedArray(byteCount);
|
|
3947
|
-
const load = loadBucket(input.load) / LOAD_BUCKETS;
|
|
3948
|
-
const activeRuns = Math.max(0, Math.floor(input.activeRuns ?? 0));
|
|
3949
|
-
drawBackground(data, width, height, 0, input.scene, input.layout, load, activeRuns);
|
|
3950
|
-
if (layer === "split") {
|
|
3951
|
-
const sequences = [encodeKittyFrame(width, height, data, input)];
|
|
3952
|
-
let panelIndex = 0;
|
|
3953
|
-
for (const panel of input.panels) {
|
|
3954
|
-
if (panel.chrome !== "ad-terminal")
|
|
3955
|
-
continue;
|
|
3956
|
-
const placement = panelPixelPlacement(input.layout, panel, { width, height });
|
|
3957
|
-
if (!placement)
|
|
3958
|
-
continue;
|
|
3959
|
-
const cellH = height / input.layout.height;
|
|
3960
|
-
const headerPx = Math.max(cellH * 3, (panel.headerHeight ?? 3) * cellH);
|
|
3961
|
-
compositePanel(data, width, height, placement, headerPx);
|
|
3962
|
-
const imageId = input.imageIds?.[panelIndex] ?? (input.imageId + 1000 + panelIndex >>> 0 || panelIndex + 1);
|
|
3963
|
-
sequences.push(encodePanelPatch(data, width, placement, imageId, -12));
|
|
3964
|
-
panelIndex += 1;
|
|
3965
|
-
}
|
|
3966
|
-
return { key: imageVisualFrameKey(input), sequence: sequences.join("") };
|
|
3967
|
-
}
|
|
3968
|
-
if (layer === "panel-chrome")
|
|
3969
|
-
return { key: imageVisualFrameKey(input), sequence: "" };
|
|
3970
|
-
if (layer === "background") {
|
|
3971
|
-
return { key: imageVisualFrameKey(input), sequence: encodeKittyFrame(width, height, data, input) };
|
|
3972
|
-
}
|
|
3973
|
-
if (layer === "panel-patches") {
|
|
3974
|
-
const sequences = [];
|
|
3975
|
-
let panelIndex = 0;
|
|
3976
|
-
for (const panel of input.panels) {
|
|
3977
|
-
if (panel.chrome !== "ad-terminal")
|
|
3978
|
-
continue;
|
|
3979
|
-
const placement = panelPixelPlacement(input.layout, panel, { width, height });
|
|
3980
|
-
if (!placement)
|
|
3981
|
-
continue;
|
|
3982
|
-
const cellH = height / input.layout.height;
|
|
3983
|
-
const headerPx = Math.max(cellH * 3, (panel.headerHeight ?? 3) * cellH);
|
|
3984
|
-
compositePanel(data, width, height, placement, headerPx);
|
|
3985
|
-
const imageId = input.imageIds?.[panelIndex] ?? (input.imageId + 1000 + panelIndex >>> 0 || panelIndex + 1);
|
|
3986
|
-
sequences.push(encodePanelPatch(data, width, placement, imageId, input.zIndex));
|
|
3987
|
-
panelIndex += 1;
|
|
3988
|
-
}
|
|
3989
|
-
return { key: imageVisualFrameKey(input), sequence: sequences.join("") };
|
|
3990
|
-
}
|
|
3991
|
-
for (const panel of input.panels) {
|
|
3992
|
-
if (panel.chrome !== "ad-terminal")
|
|
3993
|
-
continue;
|
|
3994
|
-
const rect = panelRect(input.layout, panel, { width, height });
|
|
3995
|
-
if (!rect)
|
|
3996
|
-
continue;
|
|
3997
|
-
const cellH = height / input.layout.height;
|
|
3998
|
-
const headerPx = Math.max(cellH * 3, (panel.headerHeight ?? 3) * cellH);
|
|
3999
|
-
compositePanel(data, width, height, rect, headerPx);
|
|
4000
|
-
}
|
|
4001
|
-
return { key: imageVisualFrameKey(input), sequence: encodeKittyFrame(width, height, data, input) };
|
|
4002
|
-
}
|
|
4003
|
-
function shouldRenderImagesInline() {
|
|
4004
|
-
return process.env.RIG_OPENTUI_IMAGE_WORKER === "0" || import.meta.url.includes("$bunfs");
|
|
4005
|
-
}
|
|
4006
|
-
function createImageWorker() {
|
|
4007
|
-
if (shouldRenderImagesInline())
|
|
4008
|
-
return null;
|
|
4009
|
-
const isTypeScriptRuntime = import.meta.url.endsWith(".ts");
|
|
4010
|
-
const workerUrl = new URL(isTypeScriptRuntime ? "./image-visual-layer-worker.ts" : "./image-visual-layer-worker.js", import.meta.url);
|
|
4011
|
-
try {
|
|
4012
|
-
return new Worker(workerUrl);
|
|
4013
|
-
} catch {
|
|
4014
|
-
if (typeof process.versions.bun === "string") {
|
|
4015
|
-
try {
|
|
4016
|
-
return new Worker("./src/app-opentui/render/image-visual-layer-worker.ts");
|
|
4017
|
-
} catch {}
|
|
4018
|
-
}
|
|
4019
|
-
return null;
|
|
4020
|
-
}
|
|
4021
|
-
}
|
|
4022
|
-
var RECENT_WRITTEN_LIMIT = 24;
|
|
4023
|
-
var DEFAULT_IMAGE_OUTPUT_BACKLOG_BYTES = 256 * 1024;
|
|
4024
|
-
var DEFAULT_IMAGE_OUTPUT_BYTES_PER_SECOND = 850 * 1024;
|
|
4025
|
-
function imageOutputBacklogBytes() {
|
|
4026
|
-
const raw = Number.parseInt(process.env.RIG_OPENTUI_IMAGE_BACKLOG_BYTES ?? "", 10);
|
|
4027
|
-
if (Number.isFinite(raw) && raw > 0)
|
|
4028
|
-
return Math.max(64 * 1024, Math.min(2 * 1024 * 1024, raw));
|
|
4029
|
-
return DEFAULT_IMAGE_OUTPUT_BACKLOG_BYTES;
|
|
4030
|
-
}
|
|
4031
|
-
function imageOutputBytesPerSecond() {
|
|
4032
|
-
const raw = Number.parseInt(process.env.RIG_OPENTUI_IMAGE_BYTES_PER_SECOND ?? "", 10);
|
|
4033
|
-
if (Number.isFinite(raw) && raw > 0)
|
|
4034
|
-
return Math.max(128 * 1024, Math.min(2 * 1024 * 1024, raw));
|
|
4035
|
-
return DEFAULT_IMAGE_OUTPUT_BYTES_PER_SECOND;
|
|
4036
|
-
}
|
|
4037
|
-
function pendingLayerKey(key) {
|
|
4038
|
-
return key.split(":", 1)[0] || "full";
|
|
4039
3371
|
}
|
|
4040
3372
|
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
onFrameReady;
|
|
4047
|
-
protocol;
|
|
4048
|
-
transport = imageTransport();
|
|
4049
|
-
maxOutputBacklogBytes = imageOutputBacklogBytes();
|
|
4050
|
-
outputBytesPerSecond = imageOutputBytesPerSecond();
|
|
4051
|
-
outputBudgetBytes = imageOutputBytesPerSecond();
|
|
4052
|
-
lastBudgetAtMs = Date.now();
|
|
4053
|
-
motionSlot = this.createSlot();
|
|
4054
|
-
workersStarted = false;
|
|
4055
|
-
pendingFrames = new Map;
|
|
4056
|
-
recentWrittenKeys = [];
|
|
4057
|
-
imagesVisible = false;
|
|
4058
|
-
flushPaused = false;
|
|
4059
|
-
requestId = 0;
|
|
4060
|
-
lastBackgroundKey = "";
|
|
4061
|
-
fatalWorkerError = null;
|
|
4062
|
-
destroyed = false;
|
|
4063
|
-
constructor(stdout = process.stdout, onFrameReady) {
|
|
4064
|
-
this.stdout = stdout;
|
|
4065
|
-
this.onFrameReady = onFrameReady;
|
|
4066
|
-
this.protocol = getRigTerminalCapabilities().imageProtocol;
|
|
4067
|
-
}
|
|
4068
|
-
get enabled() {
|
|
4069
|
-
return this.protocol === "kitty";
|
|
4070
|
-
}
|
|
4071
|
-
clear() {
|
|
4072
|
-
if (this.enabled) {
|
|
4073
|
-
if (this.imagesVisible)
|
|
4074
|
-
this.stdout.write([this.imageId, ...this.panelImageIds, ...this.panelChromeImageIds].map((id) => deleteKittyImage(id)).join("") + "\x1B[?25h");
|
|
4075
|
-
this.imagesVisible = false;
|
|
4076
|
-
this.pendingFrames.clear();
|
|
4077
|
-
this.recentWrittenKeys.length = 0;
|
|
4078
|
-
this.resetSlot(this.motionSlot);
|
|
4079
|
-
this.lastBackgroundKey = "";
|
|
4080
|
-
this.outputBudgetBytes = this.outputBytesPerSecond;
|
|
4081
|
-
this.lastBudgetAtMs = Date.now();
|
|
4082
|
-
this.motionSlot.clearedThroughRequestId = this.requestId;
|
|
4083
|
-
}
|
|
4084
|
-
}
|
|
4085
|
-
destroy() {
|
|
4086
|
-
if (this.destroyed)
|
|
4087
|
-
return;
|
|
4088
|
-
this.clear();
|
|
4089
|
-
this.destroyed = true;
|
|
4090
|
-
const workers = [this.motionSlot.worker];
|
|
4091
|
-
this.motionSlot.worker = null;
|
|
4092
|
-
for (const worker of workers)
|
|
4093
|
-
if (worker)
|
|
4094
|
-
worker.terminate().catch(() => {
|
|
4095
|
-
return;
|
|
4096
|
-
});
|
|
4097
|
-
}
|
|
4098
|
-
setFlushPaused(paused) {
|
|
4099
|
-
this.flushPaused = paused;
|
|
4100
|
-
}
|
|
4101
|
-
isReady(input) {
|
|
4102
|
-
if (!this.enabled || this.destroyed)
|
|
4103
|
-
return true;
|
|
4104
|
-
this.throwIfFatal();
|
|
4105
|
-
const splitKey = imageVisualFrameKey({ ...input, tick: 0, layer: "split" });
|
|
4106
|
-
return this.recentWrittenKeys.includes(splitKey) || Array.from(this.pendingFrames.values()).some((frame) => frame.key === splitKey);
|
|
4107
|
-
}
|
|
4108
|
-
render(input) {
|
|
4109
|
-
if (!this.enabled || this.destroyed)
|
|
4110
|
-
return;
|
|
4111
|
-
this.throwIfFatal();
|
|
4112
|
-
if (this.transport !== "png")
|
|
4113
|
-
this.failWorker("renderer", new Error("OpenTUI image visuals require PNG transport for static-background rendering."));
|
|
4114
|
-
this.ensureWorkersStarted();
|
|
4115
|
-
const width = Math.max(1, Math.floor(input.pixelSize.width));
|
|
4116
|
-
const height = Math.max(1, Math.floor(input.pixelSize.height));
|
|
4117
|
-
if (width * height * 4 > MAX_IMAGE_BYTES)
|
|
4118
|
-
return;
|
|
4119
|
-
if (this.isOutputBackedUp())
|
|
4120
|
-
return;
|
|
4121
|
-
this.refillOutputBudget();
|
|
4122
|
-
const splitInput = { ...input, tick: 0, layer: "split" };
|
|
4123
|
-
const splitKey = imageVisualFrameKey(splitInput);
|
|
4124
|
-
if (input.force || splitKey !== this.lastBackgroundKey) {
|
|
4125
|
-
this.scheduleRender(this.motionSlot, splitInput, this.imageId, -30, input.force, this.panelImageIds);
|
|
4126
|
-
this.lastBackgroundKey = splitKey;
|
|
4127
|
-
}
|
|
4128
|
-
}
|
|
4129
|
-
flush() {
|
|
4130
|
-
this.throwIfFatal();
|
|
4131
|
-
if (this.flushPaused)
|
|
4132
|
-
return false;
|
|
4133
|
-
if (this.pendingFrames.size === 0)
|
|
4134
|
-
return false;
|
|
4135
|
-
this.refillOutputBudget();
|
|
4136
|
-
if (this.isOutputBackedUp()) {
|
|
4137
|
-
const retainedSplit = this.pendingFrames.get("split");
|
|
4138
|
-
this.pendingFrames.clear();
|
|
4139
|
-
if (retainedSplit)
|
|
4140
|
-
this.pendingFrames.set("split", retainedSplit);
|
|
4141
|
-
return false;
|
|
4142
|
-
}
|
|
4143
|
-
const framePriority = (frame) => {
|
|
4144
|
-
const layer = pendingLayerKey(frame.key);
|
|
4145
|
-
if (layer === "split")
|
|
4146
|
-
return 1;
|
|
4147
|
-
return 4;
|
|
4148
|
-
};
|
|
4149
|
-
const frames = Array.from(this.pendingFrames.values()).sort((a, b) => framePriority(a) - framePriority(b));
|
|
4150
|
-
const prefix = "\x1B[?25l\x1B7\x1B[s\x1B[H";
|
|
4151
|
-
const suffix = "\x1B[u\x1B8";
|
|
4152
|
-
const selected = [];
|
|
4153
|
-
const retained = new Map;
|
|
4154
|
-
let sequence = "";
|
|
4155
|
-
let byteLength = Buffer.byteLength(prefix + suffix);
|
|
4156
|
-
for (const frame of frames) {
|
|
4157
|
-
const candidate = sequence + frame.sequence;
|
|
4158
|
-
const candidateBytes = Buffer.byteLength(prefix + candidate + suffix);
|
|
4159
|
-
const layer = pendingLayerKey(frame.key);
|
|
4160
|
-
const allowOneStaticFrame = selected.length === 0 && layer === "split";
|
|
4161
|
-
if (candidateBytes <= this.outputBudgetBytes || allowOneStaticFrame) {
|
|
4162
|
-
selected.push(frame);
|
|
4163
|
-
sequence = candidate;
|
|
4164
|
-
byteLength = candidateBytes;
|
|
4165
|
-
} else if (layer === "split") {
|
|
4166
|
-
retained.set(layer, frame);
|
|
4167
|
-
}
|
|
4168
|
-
}
|
|
4169
|
-
this.pendingFrames.clear();
|
|
4170
|
-
for (const [layer, frame] of retained)
|
|
4171
|
-
this.pendingFrames.set(layer, frame);
|
|
4172
|
-
if (!sequence || selected.length === 0)
|
|
4173
|
-
return false;
|
|
4174
|
-
const payload = `${prefix}${sequence}${suffix}`;
|
|
4175
|
-
for (const frame of selected)
|
|
4176
|
-
this.markWritten(frame.key);
|
|
4177
|
-
this.outputBudgetBytes -= byteLength;
|
|
4178
|
-
this.stdout.cork?.();
|
|
4179
|
-
const accepted = this.stdout.write(payload);
|
|
4180
|
-
this.stdout.uncork?.();
|
|
4181
|
-
this.imagesVisible = true;
|
|
4182
|
-
return accepted;
|
|
4183
|
-
}
|
|
4184
|
-
createSlot() {
|
|
4185
|
-
return { worker: null, busy: false, queuedRequest: null, inFlightKey: "", queuedKey: "", clearedThroughRequestId: 0, latestRequestId: 0 };
|
|
4186
|
-
}
|
|
4187
|
-
resetSlot(slot) {
|
|
4188
|
-
slot.busy = false;
|
|
4189
|
-
slot.queuedRequest = null;
|
|
4190
|
-
slot.inFlightKey = "";
|
|
4191
|
-
slot.queuedKey = "";
|
|
4192
|
-
}
|
|
4193
|
-
markWritten(key) {
|
|
4194
|
-
this.recentWrittenKeys.push(key);
|
|
4195
|
-
while (this.recentWrittenKeys.length > RECENT_WRITTEN_LIMIT)
|
|
4196
|
-
this.recentWrittenKeys.shift();
|
|
4197
|
-
}
|
|
4198
|
-
refillOutputBudget() {
|
|
4199
|
-
const now = Date.now();
|
|
4200
|
-
const elapsedMs = Math.max(0, now - this.lastBudgetAtMs);
|
|
4201
|
-
this.lastBudgetAtMs = now;
|
|
4202
|
-
this.outputBudgetBytes = Math.min(this.outputBytesPerSecond, this.outputBudgetBytes + elapsedMs / 1000 * this.outputBytesPerSecond);
|
|
4203
|
-
}
|
|
4204
|
-
isOutputBackedUp() {
|
|
4205
|
-
return Boolean(this.stdout.writableNeedDrain) || this.stdout.writableLength > this.maxOutputBacklogBytes;
|
|
4206
|
-
}
|
|
4207
|
-
stageFrame(frame) {
|
|
4208
|
-
this.pendingFrames.set(pendingLayerKey(frame.key), frame);
|
|
4209
|
-
}
|
|
4210
|
-
isKnownKey(slot, key) {
|
|
4211
|
-
return this.recentWrittenKeys.includes(key) || Array.from(this.pendingFrames.values()).some((frame) => frame.key === key) || key === slot.inFlightKey || key === slot.queuedKey;
|
|
4212
|
-
}
|
|
4213
|
-
ensureWorkersStarted() {
|
|
4214
|
-
if (this.workersStarted)
|
|
4215
|
-
return;
|
|
4216
|
-
this.workersStarted = true;
|
|
4217
|
-
this.startWorker(this.motionSlot, "static-visuals");
|
|
4218
|
-
}
|
|
4219
|
-
failWorker(name, error) {
|
|
4220
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
4221
|
-
this.fatalWorkerError ??= new Error(`OpenTUI image worker ${name} failed: ${detail}`);
|
|
4222
|
-
throw this.fatalWorkerError;
|
|
4223
|
-
}
|
|
4224
|
-
throwIfFatal() {
|
|
4225
|
-
if (this.fatalWorkerError)
|
|
4226
|
-
throw this.fatalWorkerError;
|
|
4227
|
-
}
|
|
4228
|
-
scheduleRender(slot, input, imageId, zIndex, force, imageIds) {
|
|
4229
|
-
this.throwIfFatal();
|
|
4230
|
-
const key = imageVisualFrameKey(input);
|
|
4231
|
-
if (!force && this.isKnownKey(slot, key))
|
|
4232
|
-
return;
|
|
4233
|
-
const request = {
|
|
4234
|
-
...input,
|
|
4235
|
-
imageId,
|
|
4236
|
-
...imageIds ? { imageIds } : {},
|
|
4237
|
-
zIndex,
|
|
4238
|
-
transport: this.transport,
|
|
4239
|
-
requestId: ++this.requestId,
|
|
4240
|
-
key
|
|
4241
|
-
};
|
|
4242
|
-
slot.latestRequestId = request.requestId;
|
|
4243
|
-
if (!slot.worker) {
|
|
4244
|
-
this.renderInline(slot, request);
|
|
4245
|
-
return;
|
|
4246
|
-
}
|
|
4247
|
-
this.enqueueWorkerRender(slot, request);
|
|
4248
|
-
}
|
|
4249
|
-
startWorker(slot, name) {
|
|
4250
|
-
const worker = createImageWorker();
|
|
4251
|
-
if (!worker)
|
|
4252
|
-
return;
|
|
4253
|
-
slot.worker = worker;
|
|
4254
|
-
worker.on("message", (message) => this.handleWorkerMessage(slot, message));
|
|
4255
|
-
worker.once("error", (error) => {
|
|
4256
|
-
if (!this.destroyed)
|
|
4257
|
-
this.fatalWorkerError ??= new Error(`OpenTUI image worker ${name} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4258
|
-
});
|
|
4259
|
-
worker.once("exit", (code) => {
|
|
4260
|
-
if (!this.destroyed)
|
|
4261
|
-
this.fatalWorkerError ??= new Error(`OpenTUI image worker ${name} exited unexpectedly with code ${code}.`);
|
|
4262
|
-
});
|
|
4263
|
-
}
|
|
4264
|
-
renderInline(slot, request) {
|
|
4265
|
-
try {
|
|
4266
|
-
const result = renderImageVisualFrame(request);
|
|
4267
|
-
if (result?.sequence && !this.isKnownKey(slot, result.key) && (!this.isOutputBackedUp() || pendingLayerKey(result.key) === "split")) {
|
|
4268
|
-
this.stageFrame({ key: result.key, sequence: result.sequence });
|
|
4269
|
-
this.onFrameReady?.();
|
|
4270
|
-
}
|
|
4271
|
-
} catch (error) {
|
|
4272
|
-
this.failWorker(String(request.layer ?? "visual"), error);
|
|
4273
|
-
}
|
|
4274
|
-
}
|
|
4275
|
-
enqueueWorkerRender(slot, request) {
|
|
4276
|
-
this.throwIfFatal();
|
|
4277
|
-
if (slot.busy) {
|
|
4278
|
-
slot.queuedRequest = request;
|
|
4279
|
-
slot.queuedKey = request.key;
|
|
4280
|
-
return;
|
|
4281
|
-
}
|
|
4282
|
-
this.dispatchWorkerRender(slot, request);
|
|
4283
|
-
}
|
|
4284
|
-
dispatchWorkerRender(slot, request) {
|
|
4285
|
-
this.throwIfFatal();
|
|
4286
|
-
if (!slot.worker)
|
|
4287
|
-
this.failWorker(String(request.layer ?? "visual"), new Error("worker is unavailable"));
|
|
4288
|
-
slot.busy = true;
|
|
4289
|
-
slot.inFlightKey = request.key;
|
|
4290
|
-
try {
|
|
4291
|
-
slot.worker.postMessage(request);
|
|
4292
|
-
} catch (error) {
|
|
4293
|
-
this.failWorker(String(request.layer ?? "visual"), error);
|
|
4294
|
-
}
|
|
4295
|
-
}
|
|
4296
|
-
handleWorkerMessage(slot, message) {
|
|
4297
|
-
if (this.destroyed || !message || typeof message !== "object")
|
|
4298
|
-
return;
|
|
4299
|
-
const response = message;
|
|
4300
|
-
slot.busy = false;
|
|
4301
|
-
slot.inFlightKey = "";
|
|
4302
|
-
const isStale = response.requestId <= slot.clearedThroughRequestId || response.requestId < slot.latestRequestId;
|
|
4303
|
-
if (!isStale) {
|
|
4304
|
-
if (response.error)
|
|
4305
|
-
this.failWorker(pendingLayerKey(response.key), new Error(response.error));
|
|
4306
|
-
if (response.sequence && !this.isKnownKey(slot, response.key) && (!this.isOutputBackedUp() || pendingLayerKey(response.key) === "split")) {
|
|
4307
|
-
this.stageFrame({ key: response.key, sequence: response.sequence });
|
|
4308
|
-
this.onFrameReady?.();
|
|
4309
|
-
}
|
|
4310
|
-
}
|
|
4311
|
-
const next = slot.queuedRequest;
|
|
4312
|
-
slot.queuedRequest = null;
|
|
4313
|
-
slot.queuedKey = "";
|
|
4314
|
-
if (next && !this.isKnownKey(slot, next.key))
|
|
4315
|
-
this.dispatchWorkerRender(slot, next);
|
|
4316
|
-
}
|
|
3373
|
+
// packages/cli/src/app-opentui/render/text.ts
|
|
3374
|
+
var TRANSPARENT2 = RGBA4.fromInts(0, 0, 0, 0);
|
|
3375
|
+
var HOVER_BG = RGBA4.fromHex(RIG_UI.hover);
|
|
3376
|
+
function lineIsClickable(line2) {
|
|
3377
|
+
return line2?.selectable !== undefined || line2?.selectableIndex !== undefined;
|
|
4317
3378
|
}
|
|
4318
|
-
function
|
|
4319
|
-
|
|
4320
|
-
const base = resolution?.width && resolution?.height ? resolution : { width: renderer.width * 12, height: renderer.height * 24 };
|
|
4321
|
-
const scale = imageResolutionScale();
|
|
4322
|
-
return {
|
|
4323
|
-
width: Math.max(renderer.width, Math.floor(base.width * scale)),
|
|
4324
|
-
height: Math.max(renderer.height, Math.floor(base.height * scale))
|
|
4325
|
-
};
|
|
3379
|
+
function lineSelectableId(line2) {
|
|
3380
|
+
return line2?.selectable?.id ?? line2?.selectableId;
|
|
4326
3381
|
}
|
|
4327
|
-
function
|
|
4328
|
-
|
|
3382
|
+
function lineBackground(line2) {
|
|
3383
|
+
if (line2?.bg)
|
|
3384
|
+
return RGBA4.fromHex(line2.bg);
|
|
3385
|
+
const id = lineSelectableId(line2);
|
|
3386
|
+
if (id !== undefined && isHovered(id))
|
|
3387
|
+
return HOVER_BG;
|
|
3388
|
+
return TRANSPARENT2;
|
|
4329
3389
|
}
|
|
4330
|
-
|
|
4331
|
-
// packages/cli/src/app-opentui/render/text.ts
|
|
4332
|
-
import { RGBA as RGBA4, TextAttributes as TextAttributes4, TextRenderable } from "@opentui/core";
|
|
4333
|
-
var TRANSPARENT2 = RGBA4.fromInts(0, 0, 0, 0);
|
|
4334
3390
|
function selectableMouseHandler(currentLineRef, onLineMouseDown) {
|
|
4335
3391
|
return (event) => {
|
|
4336
3392
|
const currentLine = currentLineRef();
|
|
4337
|
-
if (currentLine
|
|
3393
|
+
if (!lineIsClickable(currentLine))
|
|
4338
3394
|
return;
|
|
4339
3395
|
onLineMouseDown?.(currentLine, event);
|
|
4340
3396
|
};
|
|
4341
3397
|
}
|
|
3398
|
+
function hoverHandlers(currentLineRef) {
|
|
3399
|
+
return {
|
|
3400
|
+
onMouseOver: () => {
|
|
3401
|
+
const id = lineSelectableId(currentLineRef());
|
|
3402
|
+
if (id !== undefined)
|
|
3403
|
+
setHoveredSelectableId(id);
|
|
3404
|
+
},
|
|
3405
|
+
onMouseOut: () => clearHoveredSelectableId(lineSelectableId(currentLineRef()))
|
|
3406
|
+
};
|
|
3407
|
+
}
|
|
4342
3408
|
function createTextLine(renderer, id, top, onLineMouseDown) {
|
|
4343
3409
|
let currentLine;
|
|
4344
3410
|
const renderable = new TextRenderable(renderer, {
|
|
@@ -4350,7 +3416,8 @@ function createTextLine(renderer, id, top, onLineMouseDown) {
|
|
|
4350
3416
|
width: 1,
|
|
4351
3417
|
fg: RIG_UI.ink2,
|
|
4352
3418
|
selectable: true,
|
|
4353
|
-
onMouseDown: selectableMouseHandler(() => currentLine, onLineMouseDown)
|
|
3419
|
+
onMouseDown: selectableMouseHandler(() => currentLine, onLineMouseDown),
|
|
3420
|
+
...hoverHandlers(() => currentLine)
|
|
4354
3421
|
});
|
|
4355
3422
|
renderable.setRigSceneLine = (line2) => {
|
|
4356
3423
|
currentLine = line2;
|
|
@@ -4367,7 +3434,8 @@ function createFlowTextLine(renderer, id, onLineMouseDown) {
|
|
|
4367
3434
|
flexShrink: 0,
|
|
4368
3435
|
fg: RIG_UI.ink2,
|
|
4369
3436
|
selectable: true,
|
|
4370
|
-
onMouseDown: selectableMouseHandler(() => currentLine, onLineMouseDown)
|
|
3437
|
+
onMouseDown: selectableMouseHandler(() => currentLine, onLineMouseDown),
|
|
3438
|
+
...hoverHandlers(() => currentLine)
|
|
4371
3439
|
});
|
|
4372
3440
|
renderable.setRigSceneLine = (line2) => {
|
|
4373
3441
|
currentLine = line2;
|
|
@@ -4381,7 +3449,7 @@ function applyTextLine(renderable, line2, top, left, width) {
|
|
|
4381
3449
|
renderable.width = width;
|
|
4382
3450
|
renderable.content = line2?.styledText ?? line2?.text ?? "";
|
|
4383
3451
|
renderable.fg = line2?.fg ?? RIG_UI.ink2;
|
|
4384
|
-
renderable.bg = line2
|
|
3452
|
+
renderable.bg = lineBackground(line2);
|
|
4385
3453
|
renderable.attributes = line2?.bold ? TextAttributes4.BOLD : line2?.dim ? TextAttributes4.DIM : 0;
|
|
4386
3454
|
}
|
|
4387
3455
|
function applyFlowTextLine(renderable, line2, width) {
|
|
@@ -4391,7 +3459,7 @@ function applyFlowTextLine(renderable, line2, width) {
|
|
|
4391
3459
|
renderable.width = Math.max(1, width);
|
|
4392
3460
|
renderable.content = line2?.styledText ?? line2?.text ?? "";
|
|
4393
3461
|
renderable.fg = line2?.fg ?? RIG_UI.ink2;
|
|
4394
|
-
renderable.bg = line2
|
|
3462
|
+
renderable.bg = lineBackground(line2);
|
|
4395
3463
|
renderable.attributes = line2?.bold ? TextAttributes4.BOLD : line2?.dim ? TextAttributes4.DIM : 0;
|
|
4396
3464
|
}
|
|
4397
3465
|
function clearTextLines(renderables, from = 0) {
|
|
@@ -5522,7 +4590,7 @@ function __renderSceneForTest(renderer, state, sceneRenderers) {
|
|
|
5522
4590
|
renderer.root.add(navText);
|
|
5523
4591
|
const registry = buildSceneRendererRegistry(sceneRenderers);
|
|
5524
4592
|
const transition = { scene: state.scene, startedAtMs: 0, minUntilMs: 0 };
|
|
5525
|
-
const selectables = renderNow(renderer, null, null,
|
|
4593
|
+
const selectables = renderNow(renderer, null, null, scrollPanels, null, null, navText, textLines, nativeHost, state, registry, transition);
|
|
5526
4594
|
return { selectables, nativeHost };
|
|
5527
4595
|
}
|
|
5528
4596
|
async function launchRigOpenTuiApp(options) {
|
|
@@ -5534,7 +4602,6 @@ async function launchRigOpenTuiApp(options) {
|
|
|
5534
4602
|
const events = createAppEventBus();
|
|
5535
4603
|
let renderer = null;
|
|
5536
4604
|
let graphics = null;
|
|
5537
|
-
let imageVisuals = null;
|
|
5538
4605
|
let preloaderBackdrop = null;
|
|
5539
4606
|
const scrollPanels = [];
|
|
5540
4607
|
let typeBar = null;
|
|
@@ -5562,7 +4629,7 @@ async function launchRigOpenTuiApp(options) {
|
|
|
5562
4629
|
if (!renderer || destroyed)
|
|
5563
4630
|
return;
|
|
5564
4631
|
const currentState = store.getState();
|
|
5565
|
-
renderedSelectableItems = renderNow(renderer, graphics,
|
|
4632
|
+
renderedSelectableItems = renderNow(renderer, graphics, preloaderBackdrop, scrollPanels, typeBar, brandText, navText, textLines, nativeHost, currentState, sceneRendererRegistry, preloaderTransition, scheduleAppRender);
|
|
5566
4633
|
focusManager.restoreTypeBarFocus(typeBar, { hidden: currentState.scene === "handoff" });
|
|
5567
4634
|
maybeRedirectFromUnresolvedScene(currentState);
|
|
5568
4635
|
};
|
|
@@ -5722,18 +4789,8 @@ async function launchRigOpenTuiApp(options) {
|
|
|
5722
4789
|
});
|
|
5723
4790
|
renderer.setTerminalTitle?.("Operator cockpit");
|
|
5724
4791
|
const layout = computeStageLayout(renderer.width, renderer.height);
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
const flushImageVisuals = async () => {
|
|
5728
|
-
imageVisuals?.flush();
|
|
5729
|
-
};
|
|
5730
|
-
renderer.setFrameCallback(flushImageVisuals);
|
|
5731
|
-
resources.add(() => renderer?.removeFrameCallback(flushImageVisuals));
|
|
5732
|
-
}
|
|
5733
|
-
if (!imageVisuals?.enabled) {
|
|
5734
|
-
graphics = createGraphicsLayer(renderer, layout);
|
|
5735
|
-
renderer.root.add(graphics);
|
|
5736
|
-
}
|
|
4792
|
+
graphics = createGraphicsLayer(renderer, layout);
|
|
4793
|
+
renderer.root.add(graphics);
|
|
5737
4794
|
preloaderBackdrop = new BoxRenderable3(renderer, {
|
|
5738
4795
|
id: "rig-preloader-backdrop",
|
|
5739
4796
|
position: "absolute",
|
|
@@ -5756,8 +4813,7 @@ async function launchRigOpenTuiApp(options) {
|
|
|
5756
4813
|
} else if (!selectById(store, id, renderedSelectableItems)) {
|
|
5757
4814
|
return;
|
|
5758
4815
|
}
|
|
5759
|
-
|
|
5760
|
-
runAppAction("Activate selection", () => activateSelection(runtime, renderedSelectableItems));
|
|
4816
|
+
runAppAction("Activate selection", () => activateSelection(runtime, renderedSelectableItems));
|
|
5761
4817
|
};
|
|
5762
4818
|
for (let index = 0;index < MAX_SCENE_PANELS; index += 1) {
|
|
5763
4819
|
const panel = createScrollPanelLayer(renderer, `rig-scroll-panel-${index}`, onLineClick);
|
|
@@ -5824,6 +4880,7 @@ async function launchRigOpenTuiApp(options) {
|
|
|
5824
4880
|
};
|
|
5825
4881
|
renderer.keyInput.on("keypress", onKeyPress);
|
|
5826
4882
|
resources.add(() => renderer?.keyInput.off("keypress", onKeyPress));
|
|
4883
|
+
resources.add(subscribeHover(() => scheduleAppRender(0)));
|
|
5827
4884
|
const onPaste = (event) => {
|
|
5828
4885
|
const state = store.getState();
|
|
5829
4886
|
if (state.scene === "handoff") {
|
|
@@ -5859,7 +4916,6 @@ async function launchRigOpenTuiApp(options) {
|
|
|
5859
4916
|
stopActivePiHost("renderer destroyed");
|
|
5860
4917
|
stopActiveCommandHost("renderer destroyed");
|
|
5861
4918
|
destroyNativeHost(nativeHost);
|
|
5862
|
-
imageVisuals?.destroy();
|
|
5863
4919
|
if (renderTimer)
|
|
5864
4920
|
clearTimeout(renderTimer);
|
|
5865
4921
|
renderTimer = null;
|
|
@@ -5908,14 +4964,13 @@ async function launchRigOpenTuiApp(options) {
|
|
|
5908
4964
|
clearTimeout(renderTimer);
|
|
5909
4965
|
renderTimer = null;
|
|
5910
4966
|
resources.disposeAll();
|
|
5911
|
-
imageVisuals?.destroy();
|
|
5912
4967
|
stopActivePiHost("app shutdown");
|
|
5913
4968
|
stopActiveCommandHost("app shutdown");
|
|
5914
4969
|
if (renderer && !renderer.isDestroyed)
|
|
5915
4970
|
renderer.destroy();
|
|
5916
4971
|
}
|
|
5917
4972
|
}
|
|
5918
|
-
function renderNow(renderer, graphics,
|
|
4973
|
+
function renderNow(renderer, graphics, preloaderBackdrop, scrollPanels, typeBar, brandText, navText, textLines, nativeHost, state, sceneRendererRegistry, preloaderTransition, scheduleRenderAfter) {
|
|
5919
4974
|
const layout = computeStageLayout(renderer.width, renderer.height);
|
|
5920
4975
|
const hideNav = () => {
|
|
5921
4976
|
if (navText)
|
|
@@ -5924,7 +4979,6 @@ function renderNow(renderer, graphics, imageVisuals, preloaderBackdrop, scrollPa
|
|
|
5924
4979
|
if (layout.tooSmall) {
|
|
5925
4980
|
if (graphics)
|
|
5926
4981
|
clearGraphicsLayer(graphics);
|
|
5927
|
-
imageVisuals?.clear();
|
|
5928
4982
|
if (preloaderBackdrop)
|
|
5929
4983
|
preloaderBackdrop.visible = false;
|
|
5930
4984
|
if (brandText)
|
|
@@ -5958,8 +5012,7 @@ function renderNow(renderer, graphics, imageVisuals, preloaderBackdrop, scrollPa
|
|
|
5958
5012
|
preloaderTransition.minUntilMs = now;
|
|
5959
5013
|
const targetFrame = renderState.typeBar.mode === "command" ? commandPaletteFrame(renderState, layout) : rendererForScene(renderState.scene, sceneRendererRegistry)(renderState, layout);
|
|
5960
5014
|
const targetContentLayout = targetFrame.fullScreen ? { ...layout, centerWidth: layout.width, centerLeft: 0, centerTop: 0, centerHeight: Math.min(layout.height, MAX_SCENE_LINES) } : layout;
|
|
5961
|
-
const
|
|
5962
|
-
let targetVisualReady = true;
|
|
5015
|
+
const targetVisualReady = true;
|
|
5963
5016
|
const fleetStats = fleetStatsFromState(state);
|
|
5964
5017
|
const fleetLoad = fleetStats.load;
|
|
5965
5018
|
const fleetActive = fleetStats.active;
|
|
@@ -5969,23 +5022,7 @@ function renderNow(renderer, graphics, imageVisuals, preloaderBackdrop, scrollPa
|
|
|
5969
5022
|
if (renderState.scene === "command") {
|
|
5970
5023
|
getActiveCommandHost()?.resize(Math.max(40, targetContentLayout.centerWidth - 12), Math.max(10, targetContentLayout.centerHeight - 6));
|
|
5971
5024
|
}
|
|
5972
|
-
if (imageVisuals && targetFrame.terminalActive) {
|
|
5973
|
-
imageVisuals.clear();
|
|
5974
|
-
} else if (imageVisuals && !targetFrame.fullScreen && !sceneJustChanged) {
|
|
5975
|
-
const visualInput = {
|
|
5976
|
-
layout: targetContentLayout,
|
|
5977
|
-
pixelSize: pixelSizeForRenderer(renderer),
|
|
5978
|
-
panels: targetPanelsForImages,
|
|
5979
|
-
scene: state.scene,
|
|
5980
|
-
tick: state.tick,
|
|
5981
|
-
load: fleetLoad,
|
|
5982
|
-
activeRuns: fleetActive
|
|
5983
|
-
};
|
|
5984
|
-
imageVisuals.render(visualInput);
|
|
5985
|
-
targetVisualReady = imageVisuals.isReady(visualInput);
|
|
5986
|
-
}
|
|
5987
5025
|
const preloaderActive = sceneJustChanged && !cachedFastPath || isPreloaderActive(preloaderTransition, targetVisualReady, now);
|
|
5988
|
-
imageVisuals?.setFlushPaused(preloaderActive);
|
|
5989
5026
|
const frame = preloaderActive ? renderAsciiPreloaderFrame(renderState.scene, layout, state.tick) : targetFrame;
|
|
5990
5027
|
if (graphics) {
|
|
5991
5028
|
resizeGraphicsLayer(graphics, layout);
|