pilotswarm 0.4.1 → 0.5.1
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/mcp/README.md +88 -4
- package/mcp/dist/src/context.d.ts +36 -6
- package/mcp/dist/src/context.d.ts.map +1 -1
- package/mcp/dist/src/context.js +102 -8
- package/mcp/dist/src/context.js.map +1 -1
- package/mcp/dist/src/resources/artifacts.d.ts +5 -0
- package/mcp/dist/src/resources/artifacts.d.ts.map +1 -0
- package/mcp/dist/src/resources/artifacts.js +39 -0
- package/mcp/dist/src/resources/artifacts.js.map +1 -0
- package/mcp/dist/src/resources/capabilities.d.ts +4 -0
- package/mcp/dist/src/resources/capabilities.d.ts.map +1 -0
- package/mcp/dist/src/resources/capabilities.js +17 -0
- package/mcp/dist/src/resources/capabilities.js.map +1 -0
- package/mcp/dist/src/resources/graph.d.ts +5 -0
- package/mcp/dist/src/resources/graph.d.ts.map +1 -0
- package/mcp/dist/src/resources/graph.js +45 -0
- package/mcp/dist/src/resources/graph.js.map +1 -0
- package/mcp/dist/src/server.d.ts.map +1 -1
- package/mcp/dist/src/server.js +61 -2
- package/mcp/dist/src/server.js.map +1 -1
- package/mcp/dist/src/tools/agents.d.ts.map +1 -1
- package/mcp/dist/src/tools/agents.js +3 -2
- package/mcp/dist/src/tools/agents.js.map +1 -1
- package/mcp/dist/src/tools/artifacts.d.ts +12 -0
- package/mcp/dist/src/tools/artifacts.d.ts.map +1 -0
- package/mcp/dist/src/tools/artifacts.js +92 -0
- package/mcp/dist/src/tools/artifacts.js.map +1 -0
- package/mcp/dist/src/tools/capabilities.d.ts +27 -0
- package/mcp/dist/src/tools/capabilities.d.ts.map +1 -0
- package/mcp/dist/src/tools/capabilities.js +69 -0
- package/mcp/dist/src/tools/capabilities.js.map +1 -0
- package/mcp/dist/src/tools/debug.d.ts +4 -0
- package/mcp/dist/src/tools/debug.d.ts.map +1 -0
- package/mcp/dist/src/tools/debug.js +94 -0
- package/mcp/dist/src/tools/debug.js.map +1 -0
- package/mcp/dist/src/tools/facts-enhanced.d.ts +13 -0
- package/mcp/dist/src/tools/facts-enhanced.d.ts.map +1 -0
- package/mcp/dist/src/tools/facts-enhanced.js +80 -0
- package/mcp/dist/src/tools/facts-enhanced.js.map +1 -0
- package/mcp/dist/src/tools/graph.d.ts +12 -0
- package/mcp/dist/src/tools/graph.d.ts.map +1 -0
- package/mcp/dist/src/tools/graph.js +237 -0
- package/mcp/dist/src/tools/graph.js.map +1 -0
- package/mcp/dist/src/tools/groups.d.ts +10 -0
- package/mcp/dist/src/tools/groups.d.ts.map +1 -0
- package/mcp/dist/src/tools/groups.js +107 -0
- package/mcp/dist/src/tools/groups.js.map +1 -0
- package/mcp/dist/src/tools/models.d.ts.map +1 -1
- package/mcp/dist/src/tools/models.js +16 -4
- package/mcp/dist/src/tools/models.js.map +1 -1
- package/mcp/dist/src/tools/observability.d.ts +13 -0
- package/mcp/dist/src/tools/observability.d.ts.map +1 -0
- package/mcp/dist/src/tools/observability.js +174 -0
- package/mcp/dist/src/tools/observability.js.map +1 -0
- package/mcp/dist/src/tools/sessions.d.ts.map +1 -1
- package/mcp/dist/src/tools/sessions.js +120 -94
- package/mcp/dist/src/tools/sessions.js.map +1 -1
- package/mcp/dist/src/tools/system.d.ts +14 -0
- package/mcp/dist/src/tools/system.d.ts.map +1 -0
- package/mcp/dist/src/tools/system.js +110 -0
- package/mcp/dist/src/tools/system.js.map +1 -0
- package/mcp/dist/src/tools/turn-control.d.ts +13 -0
- package/mcp/dist/src/tools/turn-control.d.ts.map +1 -0
- package/mcp/dist/src/tools/turn-control.js +98 -0
- package/mcp/dist/src/tools/turn-control.js.map +1 -0
- package/mcp/dist/src/util/respond.d.ts +21 -0
- package/mcp/dist/src/util/respond.d.ts.map +1 -0
- package/mcp/dist/src/util/respond.js +42 -0
- package/mcp/dist/src/util/respond.js.map +1 -0
- package/package.json +5 -4
- package/tui/bin/tui.js +0 -3
- package/tui/src/app.js +36 -5
- package/tui/src/node-sdk-transport.js +24 -0
- package/ui/core/src/commands.js +6 -0
- package/ui/core/src/controller.js +137 -2
- package/ui/core/src/reducer.js +31 -1
- package/ui/core/src/selectors.js +414 -17
- package/ui/core/src/state.js +2 -0
- package/ui/react/src/chat-status.js +22 -0
- package/ui/react/src/components.js +68 -6
- package/ui/react/src/web-app.js +69 -8
- package/web/dist/assets/{index-xJ8IzIZY.js → index-BmqU2WsR.js} +4 -4
- package/web/dist/assets/index-D2-7fi56.css +1 -0
- package/web/dist/assets/pilotswarm-cIaYAE9e.js +90 -0
- package/web/dist/assets/{react-CEPDSRB6.js → react-CRYBfNR4.js} +1 -1
- package/web/dist/index.html +4 -4
- package/web/runtime.js +30 -0
- package/web/dist/assets/index-DmGOcKR-.css +0 -1
- package/web/dist/assets/pilotswarm-D9pEmenA.js +0 -90
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { appendAnimatedDotsToRuns, useAnimatedDots } from "./chat-status.js";
|
|
2
|
+
import { appendAnimatedDotsToRuns, useAnimatedDots, useSpinnerFrame } from "./chat-status.js";
|
|
3
3
|
import {
|
|
4
4
|
applyActiveHighlightRuns,
|
|
5
5
|
computeLegacyLayout,
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
selectAdminConsole,
|
|
9
9
|
selectAdminGhcpKeyEditorModal,
|
|
10
10
|
selectChatPaneChrome,
|
|
11
|
+
selectLiveActivityLines,
|
|
11
12
|
selectChatLines,
|
|
12
13
|
selectActivityPane,
|
|
13
14
|
selectArtifactUploadModal,
|
|
@@ -27,6 +28,7 @@ import {
|
|
|
27
28
|
selectSessionOwnerFilterModal,
|
|
28
29
|
selectStatusBar,
|
|
29
30
|
selectThemePickerModal,
|
|
31
|
+
selectHelpModal,
|
|
30
32
|
selectConfirmModal,
|
|
31
33
|
selectVisibleSessionRows,
|
|
32
34
|
} from "pilotswarm/ui-core";
|
|
@@ -375,6 +377,20 @@ const ChatPane = React.memo(function ChatPane({ controller, width, height, frame
|
|
|
375
377
|
{ text: "Check env credentials and model provider config, then relaunch.", color: "yellow" },
|
|
376
378
|
]
|
|
377
379
|
: selectChatLines(selectorState, contentWidth)), [chatView.branding.splash, chatView.connectionError, contentWidth, selectorState, startupError]);
|
|
380
|
+
const running = String(chatView.activeSession?.status || "").toLowerCase() === "running";
|
|
381
|
+
const spinnerFrame = useSpinnerFrame(running);
|
|
382
|
+
const liveActivityLines = React.useMemo(
|
|
383
|
+
() => (startupError ? [] : selectLiveActivityLines(selectorState, { spinnerFrame, maxWidth: contentWidth })),
|
|
384
|
+
[selectorState, startupError, spinnerFrame, contentWidth],
|
|
385
|
+
);
|
|
386
|
+
// Concat the isolated live-activity block so the spinner tick only recomputes
|
|
387
|
+
// the small block, not the whole transcript.
|
|
388
|
+
const chatLines = React.useMemo(
|
|
389
|
+
() => (liveActivityLines.length > 0
|
|
390
|
+
? [...elements, [{ text: "", color: null }], ...liveActivityLines]
|
|
391
|
+
: elements),
|
|
392
|
+
[elements, liveActivityLines],
|
|
393
|
+
);
|
|
378
394
|
|
|
379
395
|
return React.createElement(platform.Panel, {
|
|
380
396
|
title: chrome.title,
|
|
@@ -383,7 +399,7 @@ const ChatPane = React.memo(function ChatPane({ controller, width, height, frame
|
|
|
383
399
|
focused: chatView.focused,
|
|
384
400
|
width,
|
|
385
401
|
height,
|
|
386
|
-
lines:
|
|
402
|
+
lines: chatLines,
|
|
387
403
|
scrollOffset: chatView.chatScroll,
|
|
388
404
|
scrollMode: "bottom",
|
|
389
405
|
paneId: "chat",
|
|
@@ -568,6 +584,8 @@ const InspectorSequencePane = React.memo(function InspectorSequencePane({ contro
|
|
|
568
584
|
? state.orchestration.bySessionId?.[state.sessions.activeSessionId] || null
|
|
569
585
|
: null,
|
|
570
586
|
histories: state.history.bySessionId,
|
|
587
|
+
expandedTurns: state.ui.sequenceExpandedTurns,
|
|
588
|
+
selectedTurn: state.ui.sequenceSelectedTurn,
|
|
571
589
|
}), shallowEqualObject);
|
|
572
590
|
const selectorState = React.useMemo(() => ({
|
|
573
591
|
sessions: {
|
|
@@ -592,8 +610,14 @@ const InspectorSequencePane = React.memo(function InspectorSequencePane({ contro
|
|
|
592
610
|
sequenceState.histories,
|
|
593
611
|
]);
|
|
594
612
|
const inspector = React.useMemo(
|
|
595
|
-
() => selectInspector(selectorState, {
|
|
596
|
-
|
|
613
|
+
() => selectInspector(selectorState, {
|
|
614
|
+
width: contentWidth,
|
|
615
|
+
sequenceExpansion: {
|
|
616
|
+
expandedTurns: sequenceState.expandedTurns || [],
|
|
617
|
+
selectedTurn: sequenceState.selectedTurn ?? null,
|
|
618
|
+
},
|
|
619
|
+
}),
|
|
620
|
+
[contentWidth, selectorState, sequenceState.expandedTurns, sequenceState.selectedTurn],
|
|
597
621
|
);
|
|
598
622
|
|
|
599
623
|
return renderInspectorPanel(platform, inspector, meta, width, height, frame);
|
|
@@ -1497,6 +1521,41 @@ function ArtifactPickerModalContainer({ controller }) {
|
|
|
1497
1521
|
return React.createElement(ArtifactPickerModal, { state });
|
|
1498
1522
|
}
|
|
1499
1523
|
|
|
1524
|
+
function HelpModal({ state }) {
|
|
1525
|
+
const platform = useUiPlatform();
|
|
1526
|
+
const modal = selectHelpModal(state);
|
|
1527
|
+
if (!modal) return null;
|
|
1528
|
+
const viewport = typeof platform.getViewport === "function"
|
|
1529
|
+
? platform.getViewport()
|
|
1530
|
+
: { width: 120, height: 40 };
|
|
1531
|
+
const width = Math.max(56, Math.min(modal.idealWidth || 80, (viewport.width || 120) - 8));
|
|
1532
|
+
const height = Math.max(10, Math.min(modal.rows.length + 2, (viewport.height || 40) - 6));
|
|
1533
|
+
const contentRows = Math.max(1, height - 2);
|
|
1534
|
+
const maxOffset = Math.max(0, modal.rows.length - contentRows);
|
|
1535
|
+
const scrollOffset = Math.max(0, Math.min(modal.selectedRowIndex - Math.floor(contentRows / 2), maxOffset));
|
|
1536
|
+
return React.createElement(platform.Overlay, null,
|
|
1537
|
+
React.createElement(platform.Column, { width },
|
|
1538
|
+
React.createElement(platform.Panel, {
|
|
1539
|
+
title: modal.title,
|
|
1540
|
+
color: "cyan",
|
|
1541
|
+
focused: false,
|
|
1542
|
+
width,
|
|
1543
|
+
height,
|
|
1544
|
+
lines: modal.rows,
|
|
1545
|
+
scrollOffset,
|
|
1546
|
+
scrollMode: "top",
|
|
1547
|
+
fillColor: "surface",
|
|
1548
|
+
}),
|
|
1549
|
+
));
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
function HelpModalContainer({ controller }) {
|
|
1553
|
+
const state = useControllerSelector(controller, (rootState) => ({
|
|
1554
|
+
ui: { modal: rootState.ui.modal },
|
|
1555
|
+
}), shallowEqualObject);
|
|
1556
|
+
return React.createElement(HelpModal, { state });
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1500
1559
|
function renderFilterModal(platform, modal) {
|
|
1501
1560
|
if (!modal) return null;
|
|
1502
1561
|
const viewport = typeof platform.getViewport === "function"
|
|
@@ -1849,6 +1908,8 @@ export function SharedPilotSwarmApp({ controller, versionLabel = null }) {
|
|
|
1849
1908
|
const platform = useUiPlatform();
|
|
1850
1909
|
const layoutState = useControllerSelector(controller, (state) => ({
|
|
1851
1910
|
paneAdjust: state.ui.layout?.paneAdjust ?? 0,
|
|
1911
|
+
sessionPaneAdjust: state.ui.layout?.sessionPaneAdjust ?? 0,
|
|
1912
|
+
activityPaneAdjust: state.ui.layout?.activityPaneAdjust ?? 0,
|
|
1852
1913
|
promptRows: getPromptInputRows(state.ui.prompt),
|
|
1853
1914
|
inspectorTab: state.ui.inspectorTab,
|
|
1854
1915
|
filesFullscreen: Boolean(state.files?.fullscreen),
|
|
@@ -1862,8 +1923,8 @@ export function SharedPilotSwarmApp({ controller, versionLabel = null }) {
|
|
|
1862
1923
|
const viewportWidth = layoutState.viewportWidth;
|
|
1863
1924
|
const viewportHeight = layoutState.viewportHeight;
|
|
1864
1925
|
const layout = React.useMemo(
|
|
1865
|
-
() => computeLegacyLayout({ width: viewportWidth, height: viewportHeight }, layoutState.paneAdjust, layoutState.promptRows,
|
|
1866
|
-
[layoutState.fullscreenPane, layoutState.paneAdjust, layoutState.promptRows, viewportHeight, viewportWidth],
|
|
1926
|
+
() => computeLegacyLayout({ width: viewportWidth, height: viewportHeight }, layoutState.paneAdjust, layoutState.promptRows, layoutState.sessionPaneAdjust, layoutState.activityPaneAdjust, layoutState.fullscreenPane),
|
|
1927
|
+
[layoutState.fullscreenPane, layoutState.paneAdjust, layoutState.sessionPaneAdjust, layoutState.activityPaneAdjust, layoutState.promptRows, viewportHeight, viewportWidth],
|
|
1867
1928
|
);
|
|
1868
1929
|
const frames = buildWorkspacePaneFrames(layout);
|
|
1869
1930
|
const sessionRows = Math.max(3, (layout.fullscreenPane === "sessions" ? layout.bodyHeight : layout.sessionPaneHeight) - 2);
|
|
@@ -1973,6 +2034,7 @@ export function SharedPilotSwarmApp({ controller, versionLabel = null }) {
|
|
|
1973
2034
|
React.createElement(RenameSessionModalContainer, { controller }),
|
|
1974
2035
|
React.createElement(ArtifactUploadModalContainer, { controller }),
|
|
1975
2036
|
React.createElement(ArtifactPickerModalContainer, { controller }),
|
|
2037
|
+
React.createElement(HelpModalContainer, { controller }),
|
|
1976
2038
|
React.createElement(ModelPickerModalContainer, { controller }),
|
|
1977
2039
|
React.createElement(ReasoningEffortPickerModalContainer, { controller }),
|
|
1978
2040
|
React.createElement(ThemePickerModalContainer, { controller }),
|
package/ui/react/src/web-app.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { appendAnimatedDotsToRuns, useAnimatedDots } from "./chat-status.js";
|
|
2
|
+
import { appendAnimatedDotsToRuns, useAnimatedDots, useSpinnerFrame } from "./chat-status.js";
|
|
3
3
|
import {
|
|
4
4
|
UI_COMMANDS,
|
|
5
5
|
INSPECTOR_TABS,
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
selectAdminConsole,
|
|
18
18
|
selectArtifactPickerModal,
|
|
19
19
|
selectArtifactUploadModal,
|
|
20
|
+
selectLiveActivityLines,
|
|
20
21
|
selectChatLines,
|
|
21
22
|
selectChatPaneChrome,
|
|
22
23
|
selectOutboxOverlayLines,
|
|
@@ -342,9 +343,17 @@ function normalizeLines(lines) {
|
|
|
342
343
|
// Markup lines carry preformatted art (splash screens). Tag them
|
|
343
344
|
// so the chat renderer keeps each line intact: the pane's
|
|
344
345
|
// pre-wrap/overflow-wrap rules would rewrap wide art lines at
|
|
345
|
-
// arbitrary points and scramble the image.
|
|
346
|
-
|
|
347
|
-
|
|
346
|
+
// arbitrary points and scramble the image. When a splashAlt is
|
|
347
|
+
// present, emit BOTH variants tagged by name — CSS shows exactly
|
|
348
|
+
// one based on the real device viewport, which beats character
|
|
349
|
+
// metrics at deciding what actually fits.
|
|
350
|
+
const variants = line.splashAlt
|
|
351
|
+
? [{ text: line.value, variant: "desktop" }, { text: line.splashAlt, variant: "mobile" }]
|
|
352
|
+
: [{ text: line.value, variant: null }];
|
|
353
|
+
for (const v of variants) {
|
|
354
|
+
for (const parsedLine of parseTerminalMarkupRuns(v.text || "")) {
|
|
355
|
+
normalized.push({ kind: "runs", runs: parsedLine, preserve: true, splashVariant: v.variant });
|
|
356
|
+
}
|
|
348
357
|
}
|
|
349
358
|
continue;
|
|
350
359
|
}
|
|
@@ -1322,12 +1331,13 @@ function parseStructuredChatBlocks(lines = []) {
|
|
|
1322
1331
|
// detection entirely and render as one preserve block, so no line
|
|
1323
1332
|
// ever rewraps or gets reinterpreted as a structured element.
|
|
1324
1333
|
if (currentLine?.preserve) {
|
|
1334
|
+
const variant = currentLine.splashVariant || null;
|
|
1325
1335
|
const preserveLines = [];
|
|
1326
|
-
while (index < lines.length && lines[index]?.preserve) {
|
|
1336
|
+
while (index < lines.length && lines[index]?.preserve && (lines[index].splashVariant || null) === variant) {
|
|
1327
1337
|
preserveLines.push(lines[index]);
|
|
1328
1338
|
index += 1;
|
|
1329
1339
|
}
|
|
1330
|
-
blocks.push({ type: "preserve", lines: preserveLines });
|
|
1340
|
+
blocks.push({ type: "preserve", lines: preserveLines, splashVariant: variant });
|
|
1331
1341
|
continue;
|
|
1332
1342
|
}
|
|
1333
1343
|
|
|
@@ -1483,7 +1493,8 @@ function StructuredChatBlocks({ lines, theme }) {
|
|
|
1483
1493
|
return React.createElement(React.Fragment, null,
|
|
1484
1494
|
blocks.map((block, index) => {
|
|
1485
1495
|
if (block.type === "preserve") {
|
|
1486
|
-
|
|
1496
|
+
const variantClass = block.splashVariant ? ` is-splash-${block.splashVariant}` : "";
|
|
1497
|
+
return React.createElement("div", { key: `preserve:${index}`, className: `ps-chat-preserve-block${variantClass}` },
|
|
1487
1498
|
block.lines.map((line, lineIndex) => React.createElement(Line, {
|
|
1488
1499
|
key: `preserve:${index}:${lineIndex}`,
|
|
1489
1500
|
line,
|
|
@@ -2049,6 +2060,7 @@ function ChatPane({ controller, mobile = false, fullWidth = false, showComposer
|
|
|
2049
2060
|
inspectorTab: state.ui.inspectorTab,
|
|
2050
2061
|
chatViewMode: state.ui.chatViewMode || "transcript",
|
|
2051
2062
|
activeSessionIsGroup: Boolean(activeSessionId && state.sessions.byId[activeSessionId]?.isGroup),
|
|
2063
|
+
activeSessionStatus: activeSessionId ? String(state.sessions.byId[activeSessionId]?.status || "").toLowerCase() : "",
|
|
2052
2064
|
focused: state.ui.focusRegion === "chat",
|
|
2053
2065
|
scroll: state.ui.scroll.chat,
|
|
2054
2066
|
contentWidth,
|
|
@@ -2100,6 +2112,20 @@ function ChatPane({ controller, mobile = false, fullWidth = false, showComposer
|
|
|
2100
2112
|
() => selectChatLines(selectorState, viewState.contentWidth, { tableMode: "sentinel" }),
|
|
2101
2113
|
[selectorState, viewState.contentWidth],
|
|
2102
2114
|
);
|
|
2115
|
+
const spinnerFrame = useSpinnerFrame(viewState.activeSessionStatus === "running");
|
|
2116
|
+
const liveActivityLines = React.useMemo(
|
|
2117
|
+
() => selectLiveActivityLines(selectorState, { spinnerFrame, maxWidth: viewState.contentWidth }),
|
|
2118
|
+
[selectorState, spinnerFrame, viewState.contentWidth],
|
|
2119
|
+
);
|
|
2120
|
+
// Concatenate the isolated live-activity block onto the (expensive) message
|
|
2121
|
+
// lines so the spinner tick only recomputes the small block, not the whole
|
|
2122
|
+
// transcript. The block clears itself once the reply lands.
|
|
2123
|
+
const chatLines = React.useMemo(
|
|
2124
|
+
() => (liveActivityLines.length > 0
|
|
2125
|
+
? [...lines, [{ text: "", color: null }], ...liveActivityLines]
|
|
2126
|
+
: lines),
|
|
2127
|
+
[lines, liveActivityLines],
|
|
2128
|
+
);
|
|
2103
2129
|
const outboxLines = React.useMemo(
|
|
2104
2130
|
() => selectOutboxOverlayLines(selectorState, viewState.contentWidth, { tableMode: "sentinel" }),
|
|
2105
2131
|
[selectorState, viewState.contentWidth],
|
|
@@ -2118,7 +2144,7 @@ function ChatPane({ controller, mobile = false, fullWidth = false, showComposer
|
|
|
2118
2144
|
actions: null,
|
|
2119
2145
|
color: chrome.color,
|
|
2120
2146
|
focused: viewState.focused,
|
|
2121
|
-
lines,
|
|
2147
|
+
lines: chatLines,
|
|
2122
2148
|
bottomStickyLines: outboxLines,
|
|
2123
2149
|
scrollOffset: viewState.scroll,
|
|
2124
2150
|
scrollMode: "bottom",
|
|
@@ -4145,6 +4171,41 @@ export function PilotSwarmWebApp({ controller }) {
|
|
|
4145
4171
|
const viewport = useMeasuredViewport(viewportRef);
|
|
4146
4172
|
const mainGridViewport = useMeasuredViewport(mainGridRef);
|
|
4147
4173
|
const gridViewport = computeGridViewport(viewport);
|
|
4174
|
+
|
|
4175
|
+
// Publish the real viewport in character cells. Without this the ui-core
|
|
4176
|
+
// layout model runs on its 120×40 fallback everywhere in the browser, so
|
|
4177
|
+
// width-derived behavior (markdown wrap width, render metrics, the
|
|
4178
|
+
// splashMobile width swap) assumed a ~120-column terminal regardless of
|
|
4179
|
+
// device — phones kept getting desktop splash art narrower than ~76 cols.
|
|
4180
|
+
React.useEffect(() => {
|
|
4181
|
+
const publish = () => {
|
|
4182
|
+
const probe = document.createElement("span");
|
|
4183
|
+
probe.style.cssText = "position:absolute;visibility:hidden;white-space:pre;";
|
|
4184
|
+
// Measure with the font real lines render in — an off-context
|
|
4185
|
+
// probe can inherit a different size and skew the column count.
|
|
4186
|
+
const sample = document.querySelector(".ps-scroll-panel .ps-line") || document.querySelector(".ps-line");
|
|
4187
|
+
if (sample) probe.style.font = window.getComputedStyle(sample).font;
|
|
4188
|
+
probe.textContent = "0".repeat(100);
|
|
4189
|
+
(sample?.parentElement || document.body).appendChild(probe);
|
|
4190
|
+
const charWidth = (probe.getBoundingClientRect().width / 100) || 8;
|
|
4191
|
+
probe.remove();
|
|
4192
|
+
controller.dispatch({
|
|
4193
|
+
type: "ui/viewport",
|
|
4194
|
+
width: Math.floor(window.innerWidth / charWidth),
|
|
4195
|
+
height: Math.floor(window.innerHeight / SCROLL_ROW_HEIGHT),
|
|
4196
|
+
});
|
|
4197
|
+
};
|
|
4198
|
+
publish();
|
|
4199
|
+
let timer;
|
|
4200
|
+
const onResize = () => { clearTimeout(timer); timer = setTimeout(publish, 150); };
|
|
4201
|
+
window.addEventListener("resize", onResize);
|
|
4202
|
+
window.addEventListener("orientationchange", onResize);
|
|
4203
|
+
return () => {
|
|
4204
|
+
clearTimeout(timer);
|
|
4205
|
+
window.removeEventListener("resize", onResize);
|
|
4206
|
+
window.removeEventListener("orientationchange", onResize);
|
|
4207
|
+
};
|
|
4208
|
+
}, [controller]);
|
|
4148
4209
|
const [chatFocusMode, setChatFocusMode] = React.useState(false);
|
|
4149
4210
|
const [chatFocusPane, setChatFocusPane] = React.useState(null);
|
|
4150
4211
|
const state = useControllerSelector(controller, (rootState) => ({
|