green-screen-react 1.2.1 → 1.2.3
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/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +114 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +129 -22
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +35 -0
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -277,6 +277,7 @@ declare class WebSocketAdapter implements TerminalAdapter {
|
|
|
277
277
|
private pendingScreenResolver;
|
|
278
278
|
private pendingConnectResolver;
|
|
279
279
|
private pendingMdtResolver;
|
|
280
|
+
private disconnectAckResolver;
|
|
280
281
|
private connectingPromise;
|
|
281
282
|
private screenListeners;
|
|
282
283
|
private statusListeners;
|
package/dist/index.d.ts
CHANGED
|
@@ -277,6 +277,7 @@ declare class WebSocketAdapter implements TerminalAdapter {
|
|
|
277
277
|
private pendingScreenResolver;
|
|
278
278
|
private pendingConnectResolver;
|
|
279
279
|
private pendingMdtResolver;
|
|
280
|
+
private disconnectAckResolver;
|
|
280
281
|
private connectingPromise;
|
|
281
282
|
private screenListeners;
|
|
282
283
|
private statusListeners;
|
package/dist/index.js
CHANGED
|
@@ -131,6 +131,7 @@ var WebSocketAdapter = class _WebSocketAdapter {
|
|
|
131
131
|
this.pendingScreenResolver = null;
|
|
132
132
|
this.pendingConnectResolver = null;
|
|
133
133
|
this.pendingMdtResolver = null;
|
|
134
|
+
this.disconnectAckResolver = null;
|
|
134
135
|
this.connectingPromise = null;
|
|
135
136
|
this.screenListeners = /* @__PURE__ */ new Set();
|
|
136
137
|
this.statusListeners = /* @__PURE__ */ new Set();
|
|
@@ -265,7 +266,19 @@ var WebSocketAdapter = class _WebSocketAdapter {
|
|
|
265
266
|
});
|
|
266
267
|
}
|
|
267
268
|
async disconnect() {
|
|
269
|
+
const acked = new Promise((resolve) => {
|
|
270
|
+
const timer = setTimeout(resolve, 3e3);
|
|
271
|
+
this.disconnectAckResolver = () => {
|
|
272
|
+
clearTimeout(timer);
|
|
273
|
+
resolve();
|
|
274
|
+
};
|
|
275
|
+
});
|
|
268
276
|
this.wsSend({ type: "disconnect" });
|
|
277
|
+
try {
|
|
278
|
+
await acked;
|
|
279
|
+
} finally {
|
|
280
|
+
this.disconnectAckResolver = null;
|
|
281
|
+
}
|
|
269
282
|
this.status = { connected: false, status: "disconnected" };
|
|
270
283
|
this._sessionId = null;
|
|
271
284
|
if (this.ws) {
|
|
@@ -321,6 +334,14 @@ var WebSocketAdapter = class _WebSocketAdapter {
|
|
|
321
334
|
break;
|
|
322
335
|
}
|
|
323
336
|
case "cursor": {
|
|
337
|
+
if (this.screen) {
|
|
338
|
+
this.screen = {
|
|
339
|
+
...this.screen,
|
|
340
|
+
cursor_row: msg.data.cursor_row,
|
|
341
|
+
cursor_col: msg.data.cursor_col
|
|
342
|
+
};
|
|
343
|
+
for (const listener of this.screenListeners) listener(this.screen);
|
|
344
|
+
}
|
|
324
345
|
if (this.pendingScreenResolver) {
|
|
325
346
|
const resolver = this.pendingScreenResolver;
|
|
326
347
|
this.pendingScreenResolver = null;
|
|
@@ -361,6 +382,13 @@ var WebSocketAdapter = class _WebSocketAdapter {
|
|
|
361
382
|
resolver({ success: true });
|
|
362
383
|
}
|
|
363
384
|
break;
|
|
385
|
+
case "disconnected":
|
|
386
|
+
if (this.disconnectAckResolver) {
|
|
387
|
+
const resolver = this.disconnectAckResolver;
|
|
388
|
+
this.disconnectAckResolver = null;
|
|
389
|
+
resolver();
|
|
390
|
+
}
|
|
391
|
+
break;
|
|
364
392
|
case "error": {
|
|
365
393
|
if (this.pendingConnectResolver) {
|
|
366
394
|
const resolver = this.pendingConnectResolver;
|
|
@@ -1034,6 +1062,25 @@ var MinimizeIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { w
|
|
|
1034
1062
|
// src/components/InlineSignIn.tsx
|
|
1035
1063
|
var import_react4 = require("react");
|
|
1036
1064
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
1065
|
+
var STORAGE_KEY = "green-screen:inline-signin";
|
|
1066
|
+
function loadStored() {
|
|
1067
|
+
if (typeof window === "undefined") return {};
|
|
1068
|
+
try {
|
|
1069
|
+
const raw = window.localStorage.getItem(STORAGE_KEY);
|
|
1070
|
+
if (!raw) return {};
|
|
1071
|
+
const parsed = JSON.parse(raw);
|
|
1072
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
1073
|
+
} catch {
|
|
1074
|
+
return {};
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
function saveStored(value) {
|
|
1078
|
+
if (typeof window === "undefined") return;
|
|
1079
|
+
try {
|
|
1080
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(value));
|
|
1081
|
+
} catch {
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1037
1084
|
var PROTOCOL_OPTIONS = [
|
|
1038
1085
|
{ value: "tn5250", label: "TN5250 (IBM i)" },
|
|
1039
1086
|
{ value: "tn3270", label: "TN3270 (Mainframe)" },
|
|
@@ -1053,12 +1100,22 @@ var TERMINAL_TYPE_OPTIONS = {
|
|
|
1053
1100
|
]
|
|
1054
1101
|
};
|
|
1055
1102
|
function InlineSignIn({ defaultProtocol, loading: externalLoading, error, onConnect }) {
|
|
1056
|
-
const
|
|
1057
|
-
const [
|
|
1058
|
-
const [
|
|
1059
|
-
const [
|
|
1060
|
-
const [
|
|
1103
|
+
const stored = loadStored();
|
|
1104
|
+
const [host, setHost] = (0, import_react4.useState)(stored.host ?? "");
|
|
1105
|
+
const [port, setPort] = (0, import_react4.useState)(stored.port ?? "");
|
|
1106
|
+
const [selectedProtocol, setSelectedProtocol] = (0, import_react4.useState)(stored.protocol ?? defaultProtocol);
|
|
1107
|
+
const [terminalType, setTerminalType] = (0, import_react4.useState)(stored.terminalType ?? "");
|
|
1108
|
+
const [username, setUsername] = (0, import_react4.useState)(stored.username ?? "");
|
|
1061
1109
|
const [password, setPassword] = (0, import_react4.useState)("");
|
|
1110
|
+
(0, import_react4.useEffect)(() => {
|
|
1111
|
+
saveStored({
|
|
1112
|
+
host,
|
|
1113
|
+
port,
|
|
1114
|
+
protocol: selectedProtocol,
|
|
1115
|
+
terminalType,
|
|
1116
|
+
username
|
|
1117
|
+
});
|
|
1118
|
+
}, [host, port, selectedProtocol, terminalType, username]);
|
|
1062
1119
|
const termTypeOptions = TERMINAL_TYPE_OPTIONS[selectedProtocol];
|
|
1063
1120
|
const [submitted, setSubmitted] = (0, import_react4.useState)(false);
|
|
1064
1121
|
const loading = externalLoading || submitted;
|
|
@@ -1336,6 +1393,22 @@ function validateMod11(value) {
|
|
|
1336
1393
|
|
|
1337
1394
|
// src/components/GreenScreenTerminal.tsx
|
|
1338
1395
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1396
|
+
function isBlankContent(content) {
|
|
1397
|
+
if (!content) return true;
|
|
1398
|
+
for (let i = 0; i < content.length; i++) {
|
|
1399
|
+
const ch = content.charCodeAt(i);
|
|
1400
|
+
if (ch !== 32 && ch !== 9 && ch !== 10 && ch !== 13 && ch !== 0 && ch !== 160) {
|
|
1401
|
+
return false;
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
return true;
|
|
1405
|
+
}
|
|
1406
|
+
function formatBusyClock(ms) {
|
|
1407
|
+
const sec = Math.floor(ms / 1e3);
|
|
1408
|
+
const m = Math.floor(sec / 60);
|
|
1409
|
+
const s = sec % 60;
|
|
1410
|
+
return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
1411
|
+
}
|
|
1339
1412
|
function aidByteToKeyName(aid) {
|
|
1340
1413
|
if (aid === 241) return "ENTER";
|
|
1341
1414
|
if (aid === 243) return "HELP";
|
|
@@ -1410,7 +1483,33 @@ function GreenScreenTerminal({
|
|
|
1410
1483
|
const { data: polledScreenData, error: screenError } = useTerminalScreen(adapter, pollInterval, shouldPoll);
|
|
1411
1484
|
const { sendText: _sendText, sendKey: _sendKey } = useTerminalInput(adapter);
|
|
1412
1485
|
const { connect, reconnect, loading: reconnecting, error: connectError } = useTerminalConnection(adapter);
|
|
1413
|
-
const
|
|
1486
|
+
const incomingScreenData = externalScreenData ?? polledScreenData;
|
|
1487
|
+
const lastContentfulRef = (0, import_react5.useRef)(null);
|
|
1488
|
+
(0, import_react5.useEffect)(() => {
|
|
1489
|
+
if (incomingScreenData && !isBlankContent(incomingScreenData.content)) {
|
|
1490
|
+
lastContentfulRef.current = incomingScreenData;
|
|
1491
|
+
}
|
|
1492
|
+
}, [incomingScreenData]);
|
|
1493
|
+
const isBlankLocked = !!(incomingScreenData && incomingScreenData.keyboard_locked && isBlankContent(incomingScreenData.content));
|
|
1494
|
+
const rawScreenData = (0, import_react5.useMemo)(() => {
|
|
1495
|
+
if (isBlankLocked && lastContentfulRef.current) {
|
|
1496
|
+
return { ...lastContentfulRef.current, keyboard_locked: true };
|
|
1497
|
+
}
|
|
1498
|
+
return incomingScreenData;
|
|
1499
|
+
}, [incomingScreenData, isBlankLocked]);
|
|
1500
|
+
const BUSY_OVERLAY_DELAY_MS = 600;
|
|
1501
|
+
const [lockElapsedMs, setLockElapsedMs] = (0, import_react5.useState)(0);
|
|
1502
|
+
(0, import_react5.useEffect)(() => {
|
|
1503
|
+
if (!isBlankLocked) {
|
|
1504
|
+
setLockElapsedMs(0);
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
const start = Date.now();
|
|
1508
|
+
setLockElapsedMs(0);
|
|
1509
|
+
const id = setInterval(() => setLockElapsedMs(Date.now() - start), 250);
|
|
1510
|
+
return () => clearInterval(id);
|
|
1511
|
+
}, [isBlankLocked]);
|
|
1512
|
+
const showBusyOverlay = isBlankLocked && lockElapsedMs >= BUSY_OVERLAY_DELAY_MS;
|
|
1414
1513
|
const connStatus = externalStatus ?? (rawScreenData ? { connected: true, status: "authenticated" } : { connected: false, status: "disconnected" });
|
|
1415
1514
|
const { displayedContent, animatedCursorPos } = useTypingAnimation(
|
|
1416
1515
|
rawScreenData?.content,
|
|
@@ -1958,7 +2057,7 @@ function GreenScreenTerminal({
|
|
|
1958
2057
|
const cursor = getCursorPos();
|
|
1959
2058
|
const hasCursor = screenData.cursor_row !== void 0 && screenData.cursor_col !== void 0;
|
|
1960
2059
|
const cursorInInputField = hasCursor && fields.some(
|
|
1961
|
-
(f) => f.is_input &&
|
|
2060
|
+
(f) => f.is_input && f.row === cursor.row && cursor.col >= f.col && cursor.col < f.col + f.length
|
|
1962
2061
|
);
|
|
1963
2062
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { fontFamily: "var(--gs-font)", fontSize: "13px", position: "relative", width: `${cols}ch` }, children: [
|
|
1964
2063
|
rows.map((line, index) => {
|
|
@@ -2168,6 +2267,14 @@ function GreenScreenTerminal({
|
|
|
2168
2267
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: isAutoReconnecting || reconnecting ? "Reconnecting..." : connStatus?.status === "connecting" ? "Connecting..." : "Disconnected" }),
|
|
2169
2268
|
connStatus.error && !isAutoReconnecting && !reconnecting && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { fontSize: "0.75em", opacity: 0.7, maxWidth: "80%", textAlign: "center", wordBreak: "break-word" }, children: connStatus.error })
|
|
2170
2269
|
] }),
|
|
2270
|
+
showBusyOverlay && connStatus?.connected && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "gs-busy-overlay", role: "status", "aria-live": "polite", children: [
|
|
2271
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RefreshIcon, { size: 22, className: "gs-spin" }),
|
|
2272
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "gs-busy-clock", children: [
|
|
2273
|
+
"X CLOCK\xA0\xA0",
|
|
2274
|
+
formatBusyClock(lockElapsedMs)
|
|
2275
|
+
] }),
|
|
2276
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "gs-busy-hint", children: "Waiting for host\u2026" })
|
|
2277
|
+
] }),
|
|
2171
2278
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2172
2279
|
"input",
|
|
2173
2280
|
{
|