green-screen-react 0.3.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -97
- package/dist/index.d.mts +112 -99
- package/dist/index.d.ts +112 -99
- package/dist/index.js +559 -204
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +527 -178
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +28 -1
- package/package.json +7 -3
- package/LICENSE +0 -21
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,292 @@
|
|
|
1
1
|
// src/components/GreenScreenTerminal.tsx
|
|
2
|
-
import { useState as
|
|
2
|
+
import { useState as useState5, useEffect as useEffect4, useRef as useRef4, useCallback as useCallback3, useMemo as useMemo2 } from "react";
|
|
3
3
|
|
|
4
|
-
// src/
|
|
4
|
+
// src/adapters/RestAdapter.ts
|
|
5
|
+
var RestAdapter = class {
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
8
|
+
this.staticHeaders = options.headers || {};
|
|
9
|
+
this.getHeaders = options.getHeaders;
|
|
10
|
+
}
|
|
11
|
+
async buildHeaders() {
|
|
12
|
+
const dynamic = this.getHeaders ? await this.getHeaders() : {};
|
|
13
|
+
return {
|
|
14
|
+
"Content-Type": "application/json",
|
|
15
|
+
...this.staticHeaders,
|
|
16
|
+
...dynamic
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
async request(method, path, body) {
|
|
20
|
+
const headers = await this.buildHeaders();
|
|
21
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
22
|
+
method,
|
|
23
|
+
headers,
|
|
24
|
+
body: body ? JSON.stringify(body) : void 0
|
|
25
|
+
});
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
const detail = await response.json().catch(() => ({}));
|
|
28
|
+
throw new Error(detail?.detail || `HTTP ${response.status}`);
|
|
29
|
+
}
|
|
30
|
+
return response.json();
|
|
31
|
+
}
|
|
32
|
+
async getScreen() {
|
|
33
|
+
try {
|
|
34
|
+
return await this.request("GET", "/screen");
|
|
35
|
+
} catch (e) {
|
|
36
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
37
|
+
if (message.includes("503") || message.includes("404")) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
throw e;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async getStatus() {
|
|
44
|
+
return this.request("GET", "/status");
|
|
45
|
+
}
|
|
46
|
+
async sendText(text) {
|
|
47
|
+
return this.request("POST", "/send-text", { text });
|
|
48
|
+
}
|
|
49
|
+
async sendKey(key) {
|
|
50
|
+
return this.request("POST", "/send-key", { key });
|
|
51
|
+
}
|
|
52
|
+
async connect(config) {
|
|
53
|
+
return this.request("POST", "/connect", config);
|
|
54
|
+
}
|
|
55
|
+
async disconnect() {
|
|
56
|
+
return this.request("POST", "/disconnect");
|
|
57
|
+
}
|
|
58
|
+
async reconnect() {
|
|
59
|
+
return this.request("POST", "/reconnect");
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// src/adapters/WebSocketAdapter.ts
|
|
64
|
+
var WebSocketAdapter = class _WebSocketAdapter {
|
|
65
|
+
constructor(options = {}) {
|
|
66
|
+
this.ws = null;
|
|
67
|
+
this.screen = null;
|
|
68
|
+
this.status = { connected: false, status: "disconnected" };
|
|
69
|
+
this.pendingScreenResolver = null;
|
|
70
|
+
this.pendingConnectResolver = null;
|
|
71
|
+
this.connectingPromise = null;
|
|
72
|
+
this.screenListeners = /* @__PURE__ */ new Set();
|
|
73
|
+
this.statusListeners = /* @__PURE__ */ new Set();
|
|
74
|
+
this._sessionId = null;
|
|
75
|
+
this.workerUrl = (options.workerUrl || _WebSocketAdapter.detectEnvUrl() || "http://localhost:3001").replace(/\/+$/, "");
|
|
76
|
+
}
|
|
77
|
+
static detectEnvUrl() {
|
|
78
|
+
try {
|
|
79
|
+
if (typeof import.meta !== "undefined" && import.meta.env) {
|
|
80
|
+
const env = import.meta.env;
|
|
81
|
+
return env.VITE_GREEN_SCREEN_URL || env.VITE_WORKER_URL || void 0;
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const p = typeof globalThis !== "undefined" && globalThis.process;
|
|
87
|
+
if (p && p.env) {
|
|
88
|
+
return p.env.NEXT_PUBLIC_GREEN_SCREEN_URL || p.env.REACT_APP_GREEN_SCREEN_URL || void 0;
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
return void 0;
|
|
93
|
+
}
|
|
94
|
+
/** The proxy-side session ID (available after connect or reattach) */
|
|
95
|
+
get sessionId() {
|
|
96
|
+
return this._sessionId;
|
|
97
|
+
}
|
|
98
|
+
/** Subscribe to real-time screen updates */
|
|
99
|
+
onScreen(listener) {
|
|
100
|
+
this.screenListeners.add(listener);
|
|
101
|
+
return () => this.screenListeners.delete(listener);
|
|
102
|
+
}
|
|
103
|
+
/** Subscribe to status changes */
|
|
104
|
+
onStatus(listener) {
|
|
105
|
+
this.statusListeners.add(listener);
|
|
106
|
+
return () => this.statusListeners.delete(listener);
|
|
107
|
+
}
|
|
108
|
+
async getScreen() {
|
|
109
|
+
return this.screen;
|
|
110
|
+
}
|
|
111
|
+
async getStatus() {
|
|
112
|
+
return this.status;
|
|
113
|
+
}
|
|
114
|
+
async sendText(text) {
|
|
115
|
+
return this.sendAndWaitForScreen({ type: "text", text });
|
|
116
|
+
}
|
|
117
|
+
async sendKey(key) {
|
|
118
|
+
return this.sendAndWaitForScreen({ type: "key", key });
|
|
119
|
+
}
|
|
120
|
+
async connect(config) {
|
|
121
|
+
await this.ensureWebSocket();
|
|
122
|
+
if (!config) {
|
|
123
|
+
return { success: false, error: "ConnectConfig required" };
|
|
124
|
+
}
|
|
125
|
+
return new Promise((resolve) => {
|
|
126
|
+
const timeout = setTimeout(() => {
|
|
127
|
+
this.pendingConnectResolver = null;
|
|
128
|
+
resolve({ success: false, error: "Connection timeout" });
|
|
129
|
+
}, 3e4);
|
|
130
|
+
this.pendingConnectResolver = (result) => {
|
|
131
|
+
clearTimeout(timeout);
|
|
132
|
+
resolve(result);
|
|
133
|
+
};
|
|
134
|
+
this.wsSend({
|
|
135
|
+
type: "connect",
|
|
136
|
+
host: config.host,
|
|
137
|
+
port: config.port,
|
|
138
|
+
protocol: config.protocol,
|
|
139
|
+
username: config.username,
|
|
140
|
+
password: config.password
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Reattach to an existing proxy session (e.g. after page reload).
|
|
146
|
+
* The proxy keeps the TCP connection alive; this just reconnects the
|
|
147
|
+
* WebSocket and receives the current screen.
|
|
148
|
+
*/
|
|
149
|
+
async reattach(sessionId) {
|
|
150
|
+
await this.ensureWebSocket();
|
|
151
|
+
return new Promise((resolve) => {
|
|
152
|
+
const timeout = setTimeout(() => {
|
|
153
|
+
this.pendingConnectResolver = null;
|
|
154
|
+
resolve({ success: false, error: "Reattach timeout" });
|
|
155
|
+
}, 1e4);
|
|
156
|
+
this.pendingConnectResolver = (result) => {
|
|
157
|
+
clearTimeout(timeout);
|
|
158
|
+
resolve(result);
|
|
159
|
+
};
|
|
160
|
+
this.wsSend({ type: "reattach", sessionId });
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
async disconnect() {
|
|
164
|
+
this.wsSend({ type: "disconnect" });
|
|
165
|
+
this.status = { connected: false, status: "disconnected" };
|
|
166
|
+
this._sessionId = null;
|
|
167
|
+
if (this.ws) {
|
|
168
|
+
this.ws.close();
|
|
169
|
+
this.ws = null;
|
|
170
|
+
}
|
|
171
|
+
return { success: true };
|
|
172
|
+
}
|
|
173
|
+
async reconnect() {
|
|
174
|
+
return { success: false, error: "Use disconnect() then connect() instead" };
|
|
175
|
+
}
|
|
176
|
+
/** Close the WebSocket without sending disconnect (session stays alive on proxy) */
|
|
177
|
+
dispose() {
|
|
178
|
+
if (this.ws) {
|
|
179
|
+
this.ws.close();
|
|
180
|
+
this.ws = null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
async ensureWebSocket() {
|
|
184
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) return;
|
|
185
|
+
if (this.connectingPromise) return this.connectingPromise;
|
|
186
|
+
this.connectingPromise = new Promise((resolve, reject) => {
|
|
187
|
+
const wsUrl = this.workerUrl.replace(/^http/, "ws") + "/ws";
|
|
188
|
+
this.ws = new WebSocket(wsUrl);
|
|
189
|
+
this.ws.onopen = () => resolve();
|
|
190
|
+
this.ws.onerror = () => reject(new Error("WebSocket connection failed"));
|
|
191
|
+
this.ws.onmessage = (event) => {
|
|
192
|
+
try {
|
|
193
|
+
const msg = JSON.parse(event.data);
|
|
194
|
+
this.handleMessage(msg);
|
|
195
|
+
} catch {
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
this.ws.onclose = () => {
|
|
199
|
+
this.status = { connected: false, status: "disconnected" };
|
|
200
|
+
for (const listener of this.statusListeners) listener(this.status);
|
|
201
|
+
};
|
|
202
|
+
}).finally(() => {
|
|
203
|
+
this.connectingPromise = null;
|
|
204
|
+
});
|
|
205
|
+
return this.connectingPromise;
|
|
206
|
+
}
|
|
207
|
+
handleMessage(msg) {
|
|
208
|
+
switch (msg.type) {
|
|
209
|
+
case "screen": {
|
|
210
|
+
this.screen = msg.data;
|
|
211
|
+
for (const listener of this.screenListeners) listener(msg.data);
|
|
212
|
+
if (this.pendingScreenResolver) {
|
|
213
|
+
const resolver = this.pendingScreenResolver;
|
|
214
|
+
this.pendingScreenResolver = null;
|
|
215
|
+
resolver(msg.data);
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
case "status":
|
|
220
|
+
this.status = msg.data;
|
|
221
|
+
for (const listener of this.statusListeners) listener(msg.data);
|
|
222
|
+
break;
|
|
223
|
+
case "connected":
|
|
224
|
+
this._sessionId = msg.sessionId ?? null;
|
|
225
|
+
if (this.pendingConnectResolver) {
|
|
226
|
+
const resolver = this.pendingConnectResolver;
|
|
227
|
+
this.pendingConnectResolver = null;
|
|
228
|
+
resolver({ success: true });
|
|
229
|
+
}
|
|
230
|
+
break;
|
|
231
|
+
case "error": {
|
|
232
|
+
if (this.pendingConnectResolver) {
|
|
233
|
+
const resolver = this.pendingConnectResolver;
|
|
234
|
+
this.pendingConnectResolver = null;
|
|
235
|
+
resolver({ success: false, error: msg.message });
|
|
236
|
+
} else if (this.pendingScreenResolver) {
|
|
237
|
+
const resolver = this.pendingScreenResolver;
|
|
238
|
+
this.pendingScreenResolver = null;
|
|
239
|
+
resolver(null);
|
|
240
|
+
}
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
sendAndWaitForScreen(msg) {
|
|
246
|
+
return new Promise((resolve) => {
|
|
247
|
+
if (this.pendingScreenResolver) {
|
|
248
|
+
const old = this.pendingScreenResolver;
|
|
249
|
+
this.pendingScreenResolver = null;
|
|
250
|
+
old(this.screen);
|
|
251
|
+
}
|
|
252
|
+
const timeout = setTimeout(() => {
|
|
253
|
+
this.pendingScreenResolver = null;
|
|
254
|
+
resolve({ success: true, ...this.screenToResult() });
|
|
255
|
+
}, 5e3);
|
|
256
|
+
this.pendingScreenResolver = (screen) => {
|
|
257
|
+
clearTimeout(timeout);
|
|
258
|
+
if (screen) {
|
|
259
|
+
resolve({
|
|
260
|
+
success: true,
|
|
261
|
+
cursor_row: screen.cursor_row,
|
|
262
|
+
cursor_col: screen.cursor_col,
|
|
263
|
+
content: screen.content,
|
|
264
|
+
screen_signature: screen.screen_signature
|
|
265
|
+
});
|
|
266
|
+
} else {
|
|
267
|
+
resolve({ success: false, error: "No screen data received" });
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
this.wsSend(msg);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
screenToResult() {
|
|
274
|
+
if (!this.screen) return {};
|
|
275
|
+
return {
|
|
276
|
+
cursor_row: this.screen.cursor_row,
|
|
277
|
+
cursor_col: this.screen.cursor_col,
|
|
278
|
+
content: this.screen.content,
|
|
279
|
+
screen_signature: this.screen.screen_signature
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
wsSend(data) {
|
|
283
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
284
|
+
this.ws.send(JSON.stringify(data));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
// src/hooks/useTerminal.ts
|
|
5
290
|
import { useState, useCallback, useEffect, useRef } from "react";
|
|
6
291
|
function useTerminalConnection(adapter) {
|
|
7
292
|
const [status, setStatus] = useState(null);
|
|
@@ -122,9 +407,6 @@ function useTerminalInput(adapter) {
|
|
|
122
407
|
}, [adapter]);
|
|
123
408
|
return { loading, error, sendText, sendKey };
|
|
124
409
|
}
|
|
125
|
-
var useTN5250Connection = useTerminalConnection;
|
|
126
|
-
var useTN5250Screen = useTerminalScreen;
|
|
127
|
-
var useTN5250Terminal = useTerminalInput;
|
|
128
410
|
|
|
129
411
|
// src/hooks/useTypingAnimation.ts
|
|
130
412
|
import { useState as useState2, useEffect as useEffect2, useRef as useRef2, useCallback as useCallback2, useMemo } from "react";
|
|
@@ -572,8 +854,8 @@ function TerminalBootLoader({
|
|
|
572
854
|
);
|
|
573
855
|
}
|
|
574
856
|
|
|
575
|
-
// src/components/
|
|
576
|
-
import {
|
|
857
|
+
// src/components/Icons.tsx
|
|
858
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
577
859
|
var TerminalIcon = ({ size = 14 }) => /* @__PURE__ */ jsxs2("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
578
860
|
/* @__PURE__ */ jsx2("polyline", { points: "4 17 10 11 4 5" }),
|
|
579
861
|
/* @__PURE__ */ jsx2("line", { x1: "12", y1: "19", x2: "20", y2: "19" })
|
|
@@ -605,26 +887,33 @@ var RefreshIcon = ({ size = 12, className }) => /* @__PURE__ */ jsxs2("svg", { w
|
|
|
605
887
|
] });
|
|
606
888
|
var KeyIcon = ({ size = 12, style: s }) => /* @__PURE__ */ jsx2("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: s, children: /* @__PURE__ */ jsx2("path", { d: "M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4" }) });
|
|
607
889
|
var MinimizeIcon = () => /* @__PURE__ */ jsx2("svg", { width: 14, height: 14, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx2("path", { d: "M4 14h6v6M3 21l7-7M20 10h-6V4M21 3l-7 7" }) });
|
|
890
|
+
|
|
891
|
+
// src/components/InlineSignIn.tsx
|
|
892
|
+
import { useState as useState4 } from "react";
|
|
893
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
608
894
|
var PROTOCOL_OPTIONS = [
|
|
609
895
|
{ value: "tn5250", label: "TN5250 (IBM i)" },
|
|
610
896
|
{ value: "tn3270", label: "TN3270 (Mainframe)" },
|
|
611
897
|
{ value: "vt", label: "VT220" },
|
|
612
898
|
{ value: "hp6530", label: "HP 6530 (NonStop)" }
|
|
613
899
|
];
|
|
614
|
-
function InlineSignIn({ defaultProtocol, loading, error, onConnect }) {
|
|
900
|
+
function InlineSignIn({ defaultProtocol, loading: externalLoading, error, onConnect }) {
|
|
615
901
|
const [host, setHost] = useState4("");
|
|
616
902
|
const [port, setPort] = useState4("");
|
|
617
903
|
const [selectedProtocol, setSelectedProtocol] = useState4(defaultProtocol);
|
|
618
904
|
const [username, setUsername] = useState4("");
|
|
619
905
|
const [password, setPassword] = useState4("");
|
|
906
|
+
const [submitted, setSubmitted] = useState4(false);
|
|
907
|
+
const loading = externalLoading || submitted;
|
|
620
908
|
const handleSubmit = (e) => {
|
|
621
909
|
e.preventDefault();
|
|
910
|
+
setSubmitted(true);
|
|
622
911
|
onConnect({
|
|
623
912
|
host,
|
|
624
|
-
port: port ? parseInt(port, 10) :
|
|
913
|
+
port: port ? parseInt(port, 10) : 23,
|
|
625
914
|
protocol: selectedProtocol,
|
|
626
|
-
username,
|
|
627
|
-
password
|
|
915
|
+
...username.trim() ? { username: username.trim() } : {},
|
|
916
|
+
...password ? { password } : {}
|
|
628
917
|
});
|
|
629
918
|
};
|
|
630
919
|
const inputStyle = {
|
|
@@ -647,42 +936,73 @@ function InlineSignIn({ defaultProtocol, loading, error, onConnect }) {
|
|
|
647
936
|
color: "var(--gs-muted, #94a3b8)",
|
|
648
937
|
fontFamily: "var(--gs-font)"
|
|
649
938
|
};
|
|
650
|
-
|
|
651
|
-
/* @__PURE__ */
|
|
652
|
-
/* @__PURE__ */
|
|
653
|
-
/* @__PURE__ */
|
|
939
|
+
if (loading) {
|
|
940
|
+
return /* @__PURE__ */ jsxs3("div", { className: "gs-signin", style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: "12px" }, children: [
|
|
941
|
+
/* @__PURE__ */ jsx3(RefreshIcon, { size: 28, className: "gs-spin" }),
|
|
942
|
+
/* @__PURE__ */ jsxs3("div", { style: { fontSize: "11px", letterSpacing: "0.15em", textTransform: "uppercase", color: "var(--gs-muted)", fontFamily: "var(--gs-font)" }, children: [
|
|
943
|
+
"Connecting to ",
|
|
944
|
+
host || "host",
|
|
945
|
+
"..."
|
|
946
|
+
] })
|
|
947
|
+
] });
|
|
948
|
+
}
|
|
949
|
+
return /* @__PURE__ */ jsxs3("form", { onSubmit: handleSubmit, className: "gs-signin", children: [
|
|
950
|
+
/* @__PURE__ */ jsxs3("div", { style: { textAlign: "center", marginBottom: "16px" }, children: [
|
|
951
|
+
/* @__PURE__ */ jsx3(TerminalIcon, { size: 28 }),
|
|
952
|
+
/* @__PURE__ */ jsx3("div", { style: { fontSize: "11px", letterSpacing: "0.15em", textTransform: "uppercase", color: "var(--gs-muted)", marginTop: "8px" }, children: "Connect to Host" })
|
|
654
953
|
] }),
|
|
655
|
-
/* @__PURE__ */
|
|
656
|
-
/* @__PURE__ */
|
|
657
|
-
/* @__PURE__ */
|
|
658
|
-
|
|
954
|
+
/* @__PURE__ */ jsxs3("div", { className: "gs-signin-row", children: [
|
|
955
|
+
/* @__PURE__ */ jsxs3("div", { style: { flex: 1 }, children: [
|
|
956
|
+
/* @__PURE__ */ jsxs3("label", { style: labelStyle, children: [
|
|
957
|
+
"Host ",
|
|
958
|
+
/* @__PURE__ */ jsx3("span", { style: { color: "#ef4444" }, children: "*" })
|
|
959
|
+
] }),
|
|
960
|
+
/* @__PURE__ */ jsx3("input", { style: inputStyle, value: host, onChange: (e) => setHost(e.target.value), placeholder: "192.168.1.100", required: true, autoFocus: true })
|
|
659
961
|
] }),
|
|
660
|
-
/* @__PURE__ */
|
|
661
|
-
/* @__PURE__ */
|
|
662
|
-
/* @__PURE__ */
|
|
962
|
+
/* @__PURE__ */ jsxs3("div", { style: { width: "72px" }, children: [
|
|
963
|
+
/* @__PURE__ */ jsx3("label", { style: labelStyle, children: "Port" }),
|
|
964
|
+
/* @__PURE__ */ jsx3("input", { style: inputStyle, value: port, onChange: (e) => setPort(e.target.value), placeholder: "23", type: "number", min: "1", max: "65535" })
|
|
663
965
|
] })
|
|
664
966
|
] }),
|
|
665
|
-
/* @__PURE__ */
|
|
666
|
-
/* @__PURE__ */
|
|
667
|
-
|
|
967
|
+
/* @__PURE__ */ jsxs3("div", { children: [
|
|
968
|
+
/* @__PURE__ */ jsxs3("label", { style: labelStyle, children: [
|
|
969
|
+
"Protocol ",
|
|
970
|
+
/* @__PURE__ */ jsx3("span", { style: { color: "#ef4444" }, children: "*" })
|
|
971
|
+
] }),
|
|
972
|
+
/* @__PURE__ */ jsx3("select", { style: { ...inputStyle, appearance: "none" }, value: selectedProtocol, onChange: (e) => setSelectedProtocol(e.target.value), children: PROTOCOL_OPTIONS.map((o) => /* @__PURE__ */ jsx3("option", { value: o.value, children: o.label }, o.value)) })
|
|
668
973
|
] }),
|
|
669
|
-
/* @__PURE__ */
|
|
670
|
-
/* @__PURE__ */
|
|
671
|
-
/* @__PURE__ */
|
|
974
|
+
/* @__PURE__ */ jsxs3("div", { children: [
|
|
975
|
+
/* @__PURE__ */ jsx3("label", { style: labelStyle, children: "Username" }),
|
|
976
|
+
/* @__PURE__ */ jsx3("input", { style: inputStyle, value: username, onChange: (e) => setUsername(e.target.value), autoComplete: "username" })
|
|
672
977
|
] }),
|
|
673
|
-
/* @__PURE__ */
|
|
674
|
-
/* @__PURE__ */
|
|
675
|
-
/* @__PURE__ */
|
|
978
|
+
/* @__PURE__ */ jsxs3("div", { children: [
|
|
979
|
+
/* @__PURE__ */ jsx3("label", { style: labelStyle, children: "Password" }),
|
|
980
|
+
/* @__PURE__ */ jsx3("input", { style: inputStyle, type: "password", value: password, onChange: (e) => setPassword(e.target.value), autoComplete: "current-password" })
|
|
676
981
|
] }),
|
|
677
|
-
error && /* @__PURE__ */
|
|
678
|
-
/* @__PURE__ */
|
|
679
|
-
/* @__PURE__ */
|
|
982
|
+
error && /* @__PURE__ */ jsxs3("div", { style: { color: "#FF6B00", fontSize: "11px", fontFamily: "var(--gs-font)", display: "flex", alignItems: "center", gap: "6px" }, children: [
|
|
983
|
+
/* @__PURE__ */ jsx3(AlertTriangleIcon, { size: 12 }),
|
|
984
|
+
/* @__PURE__ */ jsx3("span", { children: error })
|
|
680
985
|
] }),
|
|
681
|
-
/* @__PURE__ */
|
|
986
|
+
/* @__PURE__ */ jsx3("button", { type: "submit", disabled: !host, className: "gs-signin-btn", children: "Connect" })
|
|
682
987
|
] });
|
|
683
988
|
}
|
|
989
|
+
|
|
990
|
+
// src/components/GreenScreenTerminal.tsx
|
|
991
|
+
import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
992
|
+
var noopResult = { success: false, error: "No adapter configured" };
|
|
993
|
+
var noopAdapter = {
|
|
994
|
+
getScreen: async () => null,
|
|
995
|
+
getStatus: async () => ({ connected: false, status: "disconnected" }),
|
|
996
|
+
sendText: async () => noopResult,
|
|
997
|
+
sendKey: async () => noopResult,
|
|
998
|
+
connect: async () => noopResult,
|
|
999
|
+
disconnect: async () => noopResult,
|
|
1000
|
+
reconnect: async () => noopResult
|
|
1001
|
+
};
|
|
684
1002
|
function GreenScreenTerminal({
|
|
685
|
-
adapter,
|
|
1003
|
+
adapter: externalAdapter,
|
|
1004
|
+
baseUrl,
|
|
1005
|
+
workerUrl,
|
|
686
1006
|
protocol,
|
|
687
1007
|
protocolProfile: customProfile,
|
|
688
1008
|
screenData: externalScreenData,
|
|
@@ -695,9 +1015,11 @@ function GreenScreenTerminal({
|
|
|
695
1015
|
showHeader = true,
|
|
696
1016
|
typingAnimation = true,
|
|
697
1017
|
typingBudgetMs = 60,
|
|
698
|
-
inlineSignIn =
|
|
1018
|
+
inlineSignIn = true,
|
|
699
1019
|
defaultProtocol: signInDefaultProtocol,
|
|
700
1020
|
onSignIn,
|
|
1021
|
+
autoSignedIn,
|
|
1022
|
+
autoFocusDisabled = false,
|
|
701
1023
|
bootLoader,
|
|
702
1024
|
headerRight,
|
|
703
1025
|
overlay,
|
|
@@ -708,6 +1030,21 @@ function GreenScreenTerminal({
|
|
|
708
1030
|
style
|
|
709
1031
|
}) {
|
|
710
1032
|
const profile = customProfile ?? getProtocolProfile(protocol);
|
|
1033
|
+
const [internalAdapter, setInternalAdapter] = useState5(null);
|
|
1034
|
+
const baseUrlAdapter = useMemo2(
|
|
1035
|
+
() => baseUrl ? new RestAdapter({ baseUrl }) : null,
|
|
1036
|
+
[baseUrl]
|
|
1037
|
+
);
|
|
1038
|
+
const workerUrlAdapter = useMemo2(
|
|
1039
|
+
() => workerUrl ? new WebSocketAdapter({ workerUrl }) : null,
|
|
1040
|
+
[workerUrl]
|
|
1041
|
+
);
|
|
1042
|
+
const defaultWsAdapter = useMemo2(
|
|
1043
|
+
() => !externalAdapter && !baseUrl && !workerUrl ? new WebSocketAdapter() : null,
|
|
1044
|
+
[externalAdapter, baseUrl, workerUrl]
|
|
1045
|
+
);
|
|
1046
|
+
const adapter = externalAdapter ?? baseUrlAdapter ?? workerUrlAdapter ?? internalAdapter ?? defaultWsAdapter ?? noopAdapter;
|
|
1047
|
+
const isUsingDefaultAdapter = adapter === defaultWsAdapter;
|
|
711
1048
|
const shouldPoll = pollInterval > 0 && !externalScreenData;
|
|
712
1049
|
const { data: polledScreenData, error: screenError } = useTerminalScreen(adapter, pollInterval, shouldPoll);
|
|
713
1050
|
const { sendText: _sendText, sendKey: _sendKey } = useTerminalInput(adapter);
|
|
@@ -732,22 +1069,29 @@ function GreenScreenTerminal({
|
|
|
732
1069
|
}, [screenData, onScreenChange]);
|
|
733
1070
|
const sendText = useCallback3(async (text) => _sendText(text), [_sendText]);
|
|
734
1071
|
const sendKey = useCallback3(async (key) => _sendKey(key), [_sendKey]);
|
|
735
|
-
const [inputText, setInputText] =
|
|
736
|
-
const [isFocused, setIsFocused] =
|
|
1072
|
+
const [inputText, setInputText] = useState5("");
|
|
1073
|
+
const [isFocused, setIsFocused] = useState5(false);
|
|
1074
|
+
const [showSignInHint, setShowSignInHint] = useState5(false);
|
|
1075
|
+
const prevAutoSignedIn = useRef4(false);
|
|
1076
|
+
useEffect4(() => {
|
|
1077
|
+
if (autoSignedIn && !prevAutoSignedIn.current) setShowSignInHint(true);
|
|
1078
|
+
prevAutoSignedIn.current = !!autoSignedIn;
|
|
1079
|
+
}, [autoSignedIn]);
|
|
737
1080
|
const terminalRef = useRef4(null);
|
|
738
1081
|
const inputRef = useRef4(null);
|
|
739
|
-
const [syncedCursor, setSyncedCursor] =
|
|
1082
|
+
const [syncedCursor, setSyncedCursor] = useState5(null);
|
|
740
1083
|
const prevRawContentRef = useRef4("");
|
|
741
1084
|
useEffect4(() => {
|
|
742
1085
|
const newContent = rawScreenData?.content || "";
|
|
743
1086
|
if (prevRawContentRef.current && newContent && newContent !== prevRawContentRef.current) {
|
|
744
1087
|
setSyncedCursor(null);
|
|
745
1088
|
setInputText("");
|
|
1089
|
+
if (showSignInHint) setShowSignInHint(false);
|
|
746
1090
|
}
|
|
747
1091
|
prevRawContentRef.current = newContent;
|
|
748
1092
|
}, [rawScreenData?.content]);
|
|
749
|
-
const [autoReconnectAttempt, setAutoReconnectAttempt] =
|
|
750
|
-
const [isAutoReconnecting, setIsAutoReconnecting] =
|
|
1093
|
+
const [autoReconnectAttempt, setAutoReconnectAttempt] = useState5(0);
|
|
1094
|
+
const [isAutoReconnecting, setIsAutoReconnecting] = useState5(false);
|
|
751
1095
|
const reconnectTimeoutRef = useRef4(null);
|
|
752
1096
|
const wasConnectedRef = useRef4(false);
|
|
753
1097
|
const isConnectedRef = useRef4(false);
|
|
@@ -790,12 +1134,36 @@ function GreenScreenTerminal({
|
|
|
790
1134
|
if (reconnectTimeoutRef.current) clearTimeout(reconnectTimeoutRef.current);
|
|
791
1135
|
};
|
|
792
1136
|
}, [connStatus?.connected, autoReconnectAttempt, isAutoReconnecting, reconnecting, reconnect, autoReconnectEnabled, maxAttempts, onNotification]);
|
|
1137
|
+
const [connecting, setConnecting] = useState5(false);
|
|
1138
|
+
const [signInError, setSignInError] = useState5(null);
|
|
793
1139
|
const handleSignIn = useCallback3(async (config) => {
|
|
794
|
-
onSignIn
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
1140
|
+
if (onSignIn) {
|
|
1141
|
+
onSignIn(config);
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
setConnecting(true);
|
|
1145
|
+
setSignInError(null);
|
|
1146
|
+
try {
|
|
1147
|
+
if (!externalAdapter && !baseUrlAdapter) {
|
|
1148
|
+
const port = config.port ? `:${config.port}` : "";
|
|
1149
|
+
const newAdapter = new RestAdapter({ baseUrl: `http://${config.host}${port}` });
|
|
1150
|
+
setInternalAdapter(newAdapter);
|
|
1151
|
+
await newAdapter.connect(config);
|
|
1152
|
+
if (config.username && config.password) setShowSignInHint(true);
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
await connect(config);
|
|
1156
|
+
if (config.username && config.password) setShowSignInHint(true);
|
|
1157
|
+
} catch (err) {
|
|
1158
|
+
setSignInError(err instanceof Error ? err.message : String(err));
|
|
1159
|
+
setConnecting(false);
|
|
1160
|
+
}
|
|
1161
|
+
}, [connect, onSignIn, externalAdapter, baseUrlAdapter]);
|
|
1162
|
+
useEffect4(() => {
|
|
1163
|
+
if (connecting && screenData?.content) setConnecting(false);
|
|
1164
|
+
}, [connecting, screenData?.content]);
|
|
1165
|
+
const [showBootLoader, setShowBootLoader] = useState5(bootLoader !== false);
|
|
1166
|
+
const [bootFadingOut, setBootFadingOut] = useState5(false);
|
|
799
1167
|
useEffect4(() => {
|
|
800
1168
|
if (screenData?.content && showBootLoader) {
|
|
801
1169
|
setBootFadingOut(true);
|
|
@@ -804,6 +1172,35 @@ function GreenScreenTerminal({
|
|
|
804
1172
|
return () => clearTimeout(timer);
|
|
805
1173
|
}
|
|
806
1174
|
}, [screenData?.content, showBootLoader]);
|
|
1175
|
+
const FOCUS_STORAGE_KEY = "gs-terminal-focused";
|
|
1176
|
+
useEffect4(() => {
|
|
1177
|
+
if (!autoFocusDisabled && !readOnly) {
|
|
1178
|
+
try {
|
|
1179
|
+
if (localStorage.getItem(FOCUS_STORAGE_KEY) === "true") {
|
|
1180
|
+
setIsFocused(true);
|
|
1181
|
+
}
|
|
1182
|
+
} catch {
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
}, []);
|
|
1186
|
+
useEffect4(() => {
|
|
1187
|
+
if (autoFocusDisabled) return;
|
|
1188
|
+
try {
|
|
1189
|
+
localStorage.setItem(FOCUS_STORAGE_KEY, String(isFocused));
|
|
1190
|
+
} catch {
|
|
1191
|
+
}
|
|
1192
|
+
}, [isFocused, autoFocusDisabled]);
|
|
1193
|
+
useEffect4(() => {
|
|
1194
|
+
if (isFocused) inputRef.current?.focus();
|
|
1195
|
+
}, [isFocused]);
|
|
1196
|
+
const hadScreenData = useRef4(false);
|
|
1197
|
+
useEffect4(() => {
|
|
1198
|
+
if (screenData?.content && !hadScreenData.current && !autoFocusDisabled && !readOnly) {
|
|
1199
|
+
hadScreenData.current = true;
|
|
1200
|
+
setIsFocused(true);
|
|
1201
|
+
}
|
|
1202
|
+
if (!screenData?.content) hadScreenData.current = false;
|
|
1203
|
+
}, [screenData?.content, autoFocusDisabled, readOnly]);
|
|
807
1204
|
useEffect4(() => {
|
|
808
1205
|
const handleClickOutside = (event) => {
|
|
809
1206
|
if (terminalRef.current && !terminalRef.current.contains(event.target)) setIsFocused(false);
|
|
@@ -940,21 +1337,21 @@ function GreenScreenTerminal({
|
|
|
940
1337
|
let match;
|
|
941
1338
|
let segmentIndex = 0;
|
|
942
1339
|
while ((match = underscoreRegex.exec(text)) !== null) {
|
|
943
|
-
if (match.index > lastIndex) segments.push(/* @__PURE__ */
|
|
1340
|
+
if (match.index > lastIndex) segments.push(/* @__PURE__ */ jsx4("span", { children: text.substring(lastIndex, match.index) }, `${keyPrefix}-t-${segmentIndex}`));
|
|
944
1341
|
const count = match[0].length;
|
|
945
|
-
segments.push(/* @__PURE__ */
|
|
1342
|
+
segments.push(/* @__PURE__ */ jsx4("span", { style: { borderBottom: "1px solid var(--gs-green, #10b981)", display: "inline-block", width: `${count}ch` }, children: " ".repeat(count) }, `${keyPrefix}-u-${segmentIndex}`));
|
|
946
1343
|
lastIndex = match.index + match[0].length;
|
|
947
1344
|
segmentIndex++;
|
|
948
1345
|
}
|
|
949
|
-
if (lastIndex < text.length) segments.push(/* @__PURE__ */
|
|
950
|
-
return segments.length > 0 ? /* @__PURE__ */
|
|
1346
|
+
if (lastIndex < text.length) segments.push(/* @__PURE__ */ jsx4("span", { children: text.substring(lastIndex) }, `${keyPrefix}-e`));
|
|
1347
|
+
return segments.length > 0 ? /* @__PURE__ */ jsx4(Fragment, { children: segments }) : /* @__PURE__ */ jsx4(Fragment, { children: text });
|
|
951
1348
|
}, []);
|
|
952
1349
|
const renderRowWithFields = useCallback3((line, rowIndex, fields) => {
|
|
953
1350
|
const inputFields = fields.filter((f) => f.row === rowIndex && f.is_input);
|
|
954
1351
|
const highlightedFields = fields.filter((f) => f.row === rowIndex && f.is_protected && f.is_highlighted);
|
|
955
1352
|
const reverseFields = fields.filter((f) => f.row === rowIndex && f.is_protected && f.is_reverse);
|
|
956
1353
|
const allRowFields = [...inputFields, ...highlightedFields, ...reverseFields];
|
|
957
|
-
if (allRowFields.length === 0) return /* @__PURE__ */
|
|
1354
|
+
if (allRowFields.length === 0) return /* @__PURE__ */ jsx4("span", { children: line });
|
|
958
1355
|
const sorted = [...allRowFields].sort((a, b) => a.col - b.col);
|
|
959
1356
|
const segs = [];
|
|
960
1357
|
let lastEnd = 0;
|
|
@@ -962,35 +1359,42 @@ function GreenScreenTerminal({
|
|
|
962
1359
|
sorted.forEach((field, idx) => {
|
|
963
1360
|
const fs = field.col;
|
|
964
1361
|
const fe = Math.min(field.col + field.length, cols);
|
|
965
|
-
if (fs > lastEnd) segs.push(/* @__PURE__ */
|
|
1362
|
+
if (fs > lastEnd) segs.push(/* @__PURE__ */ jsx4("span", { children: line.substring(lastEnd, fs) }, `t${idx}`));
|
|
966
1363
|
const fc = line.substring(fs, fe);
|
|
967
1364
|
if (field.is_input) {
|
|
968
|
-
const
|
|
969
|
-
|
|
1365
|
+
const fieldWidth = Math.min(field.length, cols - fs);
|
|
1366
|
+
const fieldClass = showSignInHint ? "gs-confirmed-field" : field.is_underscored ? "gs-input-field" : void 0;
|
|
1367
|
+
segs.push(/* @__PURE__ */ jsx4("span", { className: fieldClass || void 0, style: { display: "inline-block", width: `${fieldWidth}ch`, overflow: "hidden" }, children: fc }, `f${idx}`));
|
|
970
1368
|
} else if (field.is_reverse) {
|
|
971
|
-
segs.push(/* @__PURE__ */
|
|
1369
|
+
segs.push(/* @__PURE__ */ jsx4("span", { style: { color: "#ef4444", fontWeight: "bold" }, children: fc }, `v${idx}`));
|
|
1370
|
+
} else if (field.is_highlighted) {
|
|
1371
|
+
segs.push(/* @__PURE__ */ jsx4("span", { style: { color: "var(--gs-white, #FFFFFF)" }, children: fc }, `h${idx}`));
|
|
972
1372
|
} else {
|
|
973
|
-
segs.push(/* @__PURE__ */
|
|
1373
|
+
segs.push(/* @__PURE__ */ jsx4("span", { children: fc }, `h${idx}`));
|
|
974
1374
|
}
|
|
975
1375
|
lastEnd = fe;
|
|
976
1376
|
});
|
|
977
|
-
if (lastEnd < line.length) segs.push(/* @__PURE__ */
|
|
978
|
-
return /* @__PURE__ */
|
|
979
|
-
}, [renderTextWithUnderlines, screenData?.cols, profile.defaultCols]);
|
|
1377
|
+
if (lastEnd < line.length) segs.push(/* @__PURE__ */ jsx4("span", { children: line.substring(lastEnd) }, "te"));
|
|
1378
|
+
return /* @__PURE__ */ jsx4(Fragment, { children: segs });
|
|
1379
|
+
}, [renderTextWithUnderlines, screenData?.cols, profile.defaultCols, showSignInHint]);
|
|
980
1380
|
const renderScreen = () => {
|
|
981
1381
|
if (showBootLoader && !screenData?.content) {
|
|
982
1382
|
if (bootLoader === false) return null;
|
|
983
|
-
if (bootLoader) return /* @__PURE__ */
|
|
984
|
-
return /* @__PURE__ */
|
|
1383
|
+
if (bootLoader) return /* @__PURE__ */ jsx4(Fragment, { children: bootLoader });
|
|
1384
|
+
return /* @__PURE__ */ jsx4(TerminalBootLoader, { brandText: profile.bootText });
|
|
985
1385
|
}
|
|
986
|
-
if (bootFadingOut && screenData?.content) return /* @__PURE__ */
|
|
1386
|
+
if (bootFadingOut && screenData?.content) return /* @__PURE__ */ jsx4("div", { className: "gs-fade-in", children: renderScreenContent() });
|
|
987
1387
|
if (!screenData?.content) {
|
|
988
1388
|
if (inlineSignIn && !connStatus?.connected) {
|
|
989
|
-
return /* @__PURE__ */
|
|
1389
|
+
return /* @__PURE__ */ jsx4("div", { style: { width: "100%", height: `${(screenData?.rows || profile.defaultRows) * 21}px`, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsx4(InlineSignIn, { defaultProtocol: signInDefaultProtocol || protocol || "tn5250", loading: connecting || reconnecting, error: signInError || connectError, onConnect: handleSignIn }) });
|
|
990
1390
|
}
|
|
991
|
-
return /* @__PURE__ */
|
|
992
|
-
/* @__PURE__ */
|
|
993
|
-
/* @__PURE__ */
|
|
1391
|
+
return /* @__PURE__ */ jsx4("div", { style: { width: `${screenData?.cols || profile.defaultCols}ch`, height: `${(screenData?.rows || profile.defaultRows) * 21}px`, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsxs4("div", { style: { textAlign: "center" }, children: [
|
|
1392
|
+
/* @__PURE__ */ jsx4("div", { style: { color: "#808080", marginBottom: "12px" }, children: /* @__PURE__ */ jsx4(TerminalIcon, { size: 40 }) }),
|
|
1393
|
+
/* @__PURE__ */ jsx4("p", { style: { fontFamily: "var(--gs-font)", fontSize: "12px", color: "#808080" }, children: connStatus?.connected ? "Waiting for screen data..." : "Not connected" }),
|
|
1394
|
+
!connStatus?.connected && isUsingDefaultAdapter && /* @__PURE__ */ jsxs4("p", { style: { fontFamily: "var(--gs-font)", fontSize: "11px", color: "#606060", marginTop: "8px" }, children: [
|
|
1395
|
+
"Start the proxy: ",
|
|
1396
|
+
/* @__PURE__ */ jsx4("code", { style: { color: "#10b981" }, children: "npx green-screen-proxy --mock" })
|
|
1397
|
+
] })
|
|
994
1398
|
] }) });
|
|
995
1399
|
}
|
|
996
1400
|
return renderScreenContent();
|
|
@@ -1005,9 +1409,8 @@ function GreenScreenTerminal({
|
|
|
1005
1409
|
const fields = screenData.fields || [];
|
|
1006
1410
|
const ROW_HEIGHT = 21;
|
|
1007
1411
|
const cursor = getCursorPos();
|
|
1008
|
-
const
|
|
1009
|
-
|
|
1010
|
-
return /* @__PURE__ */ jsxs2("div", { style: { fontFamily: "var(--gs-font)", fontSize: "13px", position: "relative", width: `${cols}ch` }, children: [
|
|
1412
|
+
const hasCursor = screenData.cursor_row !== void 0 && screenData.cursor_col !== void 0;
|
|
1413
|
+
return /* @__PURE__ */ jsxs4("div", { style: { fontFamily: "var(--gs-font)", fontSize: "13px", position: "relative", width: `${cols}ch` }, children: [
|
|
1011
1414
|
rows.map((line, index) => {
|
|
1012
1415
|
let displayLine = line;
|
|
1013
1416
|
if (hasCursor && index === cursor.row && inputText && !animatedCursorPos) {
|
|
@@ -1015,12 +1418,13 @@ function GreenScreenTerminal({
|
|
|
1015
1418
|
displayLine = (line.substring(0, baseCol) + inputText + line.substring(baseCol + inputText.length)).substring(0, cols).padEnd(cols, " ");
|
|
1016
1419
|
}
|
|
1017
1420
|
const headerSegments = index === 0 ? profile.colors.parseHeaderRow(displayLine) : null;
|
|
1018
|
-
return /* @__PURE__ */
|
|
1019
|
-
headerSegments ? headerSegments.map((seg, i) => /* @__PURE__ */
|
|
1020
|
-
hasCursor && index === cursor.row && /* @__PURE__ */
|
|
1421
|
+
return /* @__PURE__ */ jsxs4("div", { className: headerSegments ? "" : profile.colors.getRowColorClass(index, displayLine, termRows), style: { height: `${ROW_HEIGHT}px`, lineHeight: `${ROW_HEIGHT}px`, whiteSpace: "pre", position: "relative" }, children: [
|
|
1422
|
+
headerSegments ? headerSegments.map((seg, i) => /* @__PURE__ */ jsx4("span", { className: seg.colorClass, children: seg.text }, i)) : renderRowWithFields(displayLine, index, fields),
|
|
1423
|
+
hasCursor && !showSignInHint && index === cursor.row && /* @__PURE__ */ jsx4("span", { className: "gs-cursor", style: { position: "absolute", left: `${cursor.col}ch`, width: "1ch", height: `${ROW_HEIGHT}px`, top: 0, pointerEvents: "none" } })
|
|
1021
1424
|
] }, index);
|
|
1022
1425
|
}),
|
|
1023
|
-
|
|
1426
|
+
showSignInHint && /* @__PURE__ */ jsx4("div", { className: "gs-signin-hint", children: "Signed in \u2014 press Enter to continue" }),
|
|
1427
|
+
screenData.cursor_row !== void 0 && screenData.cursor_col !== void 0 && /* @__PURE__ */ jsxs4("span", { style: { position: "absolute", bottom: 0, right: 0, fontFamily: "var(--gs-font)", fontSize: "10px", color: "var(--gs-green, #10b981)", pointerEvents: "none", opacity: 0.6 }, children: [
|
|
1024
1428
|
String(screenData.cursor_row + 1).padStart(2, "0"),
|
|
1025
1429
|
"/",
|
|
1026
1430
|
String(screenData.cursor_col + 1).padStart(3, "0")
|
|
@@ -1050,49 +1454,49 @@ function GreenScreenTerminal({
|
|
|
1050
1454
|
return "#64748b";
|
|
1051
1455
|
}
|
|
1052
1456
|
};
|
|
1053
|
-
return /* @__PURE__ */
|
|
1054
|
-
showHeader && /* @__PURE__ */
|
|
1055
|
-
/* @__PURE__ */
|
|
1056
|
-
/* @__PURE__ */
|
|
1057
|
-
/* @__PURE__ */
|
|
1058
|
-
isFocused && /* @__PURE__ */
|
|
1059
|
-
screenData?.timestamp && /* @__PURE__ */
|
|
1060
|
-
/* @__PURE__ */
|
|
1457
|
+
return /* @__PURE__ */ jsxs4("div", { className: `gs-terminal ${isFocused ? "gs-terminal-focused" : ""} ${className || ""}`, style, children: [
|
|
1458
|
+
showHeader && /* @__PURE__ */ jsx4("div", { className: "gs-header", children: embedded ? /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
1459
|
+
/* @__PURE__ */ jsxs4("span", { className: "gs-header-left", children: [
|
|
1460
|
+
/* @__PURE__ */ jsx4(TerminalIcon, { size: 14 }),
|
|
1461
|
+
/* @__PURE__ */ jsx4("span", { children: "TERMINAL" }),
|
|
1462
|
+
isFocused && /* @__PURE__ */ jsx4("span", { className: "gs-badge-focused", children: "FOCUSED" }),
|
|
1463
|
+
screenData?.timestamp && /* @__PURE__ */ jsx4("span", { className: "gs-timestamp", children: new Date(screenData.timestamp).toLocaleTimeString() }),
|
|
1464
|
+
/* @__PURE__ */ jsx4("span", { className: "gs-hint", children: readOnly ? "Read-only" : isFocused ? "ESC to exit focus" : "Click to control" })
|
|
1061
1465
|
] }),
|
|
1062
|
-
/* @__PURE__ */
|
|
1063
|
-
connStatus?.status && /* @__PURE__ */
|
|
1064
|
-
connStatus && (connStatus.connected ? /* @__PURE__ */
|
|
1065
|
-
onMinimize && /* @__PURE__ */
|
|
1466
|
+
/* @__PURE__ */ jsxs4("div", { className: "gs-header-right", children: [
|
|
1467
|
+
connStatus?.status && /* @__PURE__ */ jsx4(KeyIcon, { size: 12, style: { color: getStatusColor(connStatus.status) } }),
|
|
1468
|
+
connStatus && (connStatus.connected ? /* @__PURE__ */ jsx4(WifiIcon, { size: 12, style: { color: "var(--gs-green, #10b981)" } }) : /* @__PURE__ */ jsx4(WifiOffIcon, { size: 12, style: { color: "#FF6B00" } })),
|
|
1469
|
+
onMinimize && /* @__PURE__ */ jsx4("button", { onClick: (e) => {
|
|
1066
1470
|
e.stopPropagation();
|
|
1067
1471
|
onMinimize();
|
|
1068
|
-
}, className: "gs-btn-icon", title: "Minimize terminal", children: /* @__PURE__ */
|
|
1472
|
+
}, className: "gs-btn-icon", title: "Minimize terminal", children: /* @__PURE__ */ jsx4(MinimizeIcon, {}) }),
|
|
1069
1473
|
headerRight
|
|
1070
1474
|
] })
|
|
1071
|
-
] }) : /* @__PURE__ */
|
|
1072
|
-
/* @__PURE__ */
|
|
1073
|
-
/* @__PURE__ */
|
|
1074
|
-
/* @__PURE__ */
|
|
1075
|
-
isFocused && /* @__PURE__ */
|
|
1076
|
-
screenData?.timestamp && /* @__PURE__ */
|
|
1077
|
-
/* @__PURE__ */
|
|
1475
|
+
] }) : /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
1476
|
+
/* @__PURE__ */ jsxs4("span", { className: "gs-header-left", children: [
|
|
1477
|
+
/* @__PURE__ */ jsx4(TerminalIcon, { size: 14 }),
|
|
1478
|
+
/* @__PURE__ */ jsx4("span", { children: profile.headerLabel }),
|
|
1479
|
+
isFocused && /* @__PURE__ */ jsx4("span", { className: "gs-badge-focused", children: "FOCUSED" }),
|
|
1480
|
+
screenData?.timestamp && /* @__PURE__ */ jsx4("span", { className: "gs-timestamp", children: new Date(screenData.timestamp).toLocaleTimeString() }),
|
|
1481
|
+
/* @__PURE__ */ jsx4("span", { className: "gs-hint", children: readOnly ? "Read-only mode" : isFocused ? "ESC to exit focus" : "Click terminal to control" })
|
|
1078
1482
|
] }),
|
|
1079
|
-
/* @__PURE__ */
|
|
1080
|
-
connStatus && /* @__PURE__ */
|
|
1081
|
-
/* @__PURE__ */
|
|
1082
|
-
/* @__PURE__ */
|
|
1083
|
-
] }) : /* @__PURE__ */
|
|
1084
|
-
/* @__PURE__ */
|
|
1085
|
-
/* @__PURE__ */
|
|
1086
|
-
/* @__PURE__ */
|
|
1483
|
+
/* @__PURE__ */ jsxs4("div", { className: "gs-header-right", children: [
|
|
1484
|
+
connStatus && /* @__PURE__ */ jsx4("div", { className: "gs-status-group", children: connStatus.connected ? /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
1485
|
+
/* @__PURE__ */ jsx4(WifiIcon, { size: 12, style: { color: "var(--gs-green, #10b981)" } }),
|
|
1486
|
+
/* @__PURE__ */ jsx4("span", { className: "gs-host", children: connStatus.host })
|
|
1487
|
+
] }) : /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
1488
|
+
/* @__PURE__ */ jsx4(WifiOffIcon, { size: 12, style: { color: "#FF6B00" } }),
|
|
1489
|
+
/* @__PURE__ */ jsx4("span", { className: "gs-disconnected-text", children: isAutoReconnecting || reconnecting ? `RECONNECTING${autoReconnectAttempt > 0 ? ` (${autoReconnectAttempt}/${maxAttempts})` : "..."}` : autoReconnectAttempt >= maxAttempts ? "DISCONNECTED (auto-retry exhausted)" : "DISCONNECTED" }),
|
|
1490
|
+
/* @__PURE__ */ jsx4("button", { onClick: handleReconnect, disabled: reconnecting || isAutoReconnecting, className: "gs-btn-icon", title: "Reconnect", children: /* @__PURE__ */ jsx4(RefreshIcon, { size: 12, className: reconnecting || isAutoReconnecting ? "gs-spin" : "" }) })
|
|
1087
1491
|
] }) }),
|
|
1088
|
-
connStatus?.status && /* @__PURE__ */
|
|
1089
|
-
/* @__PURE__ */
|
|
1090
|
-
connStatus.username && /* @__PURE__ */
|
|
1492
|
+
connStatus?.status && /* @__PURE__ */ jsxs4("div", { className: "gs-status-group", children: [
|
|
1493
|
+
/* @__PURE__ */ jsx4(KeyIcon, { size: 12, style: { color: getStatusColor(connStatus.status) } }),
|
|
1494
|
+
connStatus.username && /* @__PURE__ */ jsx4("span", { className: "gs-host", children: connStatus.username })
|
|
1091
1495
|
] }),
|
|
1092
1496
|
headerRight
|
|
1093
1497
|
] })
|
|
1094
1498
|
] }) }),
|
|
1095
|
-
/* @__PURE__ */
|
|
1499
|
+
/* @__PURE__ */ jsx4("div", { className: "gs-body", children: /* @__PURE__ */ jsxs4(
|
|
1096
1500
|
"div",
|
|
1097
1501
|
{
|
|
1098
1502
|
ref: terminalRef,
|
|
@@ -1100,17 +1504,17 @@ function GreenScreenTerminal({
|
|
|
1100
1504
|
className: `gs-screen ${embedded ? "gs-screen-embedded" : ""}`,
|
|
1101
1505
|
style: !embedded ? { width: `calc(${screenData?.cols || profile.defaultCols}ch + 24px)`, fontSize: (screenData?.cols ?? profile.defaultCols) > 80 ? "11px" : "13px", fontFamily: "var(--gs-font)" } : void 0,
|
|
1102
1506
|
children: [
|
|
1103
|
-
screenError != null && /* @__PURE__ */
|
|
1104
|
-
/* @__PURE__ */
|
|
1105
|
-
/* @__PURE__ */
|
|
1507
|
+
screenError != null && /* @__PURE__ */ jsxs4("div", { className: "gs-error-banner", children: [
|
|
1508
|
+
/* @__PURE__ */ jsx4(AlertTriangleIcon, { size: 14 }),
|
|
1509
|
+
/* @__PURE__ */ jsx4("span", { children: String(screenError) })
|
|
1106
1510
|
] }),
|
|
1107
|
-
/* @__PURE__ */
|
|
1511
|
+
/* @__PURE__ */ jsx4("div", { className: "gs-screen-content", children: renderScreen() }),
|
|
1108
1512
|
overlay,
|
|
1109
|
-
connStatus && !connStatus.connected && screenData && /* @__PURE__ */
|
|
1110
|
-
/* @__PURE__ */
|
|
1111
|
-
/* @__PURE__ */
|
|
1513
|
+
connStatus && !connStatus.connected && screenData && /* @__PURE__ */ jsxs4("div", { className: "gs-overlay", children: [
|
|
1514
|
+
/* @__PURE__ */ jsx4(WifiOffIcon, { size: 28 }),
|
|
1515
|
+
/* @__PURE__ */ jsx4("span", { children: isAutoReconnecting || reconnecting ? "Reconnecting..." : "Disconnected" })
|
|
1112
1516
|
] }),
|
|
1113
|
-
/* @__PURE__ */
|
|
1517
|
+
/* @__PURE__ */ jsx4(
|
|
1114
1518
|
"input",
|
|
1115
1519
|
{
|
|
1116
1520
|
ref: inputRef,
|
|
@@ -1130,66 +1534,6 @@ function GreenScreenTerminal({
|
|
|
1130
1534
|
) })
|
|
1131
1535
|
] });
|
|
1132
1536
|
}
|
|
1133
|
-
var TN5250Terminal = GreenScreenTerminal;
|
|
1134
|
-
|
|
1135
|
-
// src/adapters/RestAdapter.ts
|
|
1136
|
-
var RestAdapter = class {
|
|
1137
|
-
constructor(options) {
|
|
1138
|
-
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
1139
|
-
this.staticHeaders = options.headers || {};
|
|
1140
|
-
this.getHeaders = options.getHeaders;
|
|
1141
|
-
}
|
|
1142
|
-
async buildHeaders() {
|
|
1143
|
-
const dynamic = this.getHeaders ? await this.getHeaders() : {};
|
|
1144
|
-
return {
|
|
1145
|
-
"Content-Type": "application/json",
|
|
1146
|
-
...this.staticHeaders,
|
|
1147
|
-
...dynamic
|
|
1148
|
-
};
|
|
1149
|
-
}
|
|
1150
|
-
async request(method, path, body) {
|
|
1151
|
-
const headers = await this.buildHeaders();
|
|
1152
|
-
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
1153
|
-
method,
|
|
1154
|
-
headers,
|
|
1155
|
-
body: body ? JSON.stringify(body) : void 0
|
|
1156
|
-
});
|
|
1157
|
-
if (!response.ok) {
|
|
1158
|
-
const detail = await response.json().catch(() => ({}));
|
|
1159
|
-
throw new Error(detail?.detail || `HTTP ${response.status}`);
|
|
1160
|
-
}
|
|
1161
|
-
return response.json();
|
|
1162
|
-
}
|
|
1163
|
-
async getScreen() {
|
|
1164
|
-
try {
|
|
1165
|
-
return await this.request("GET", "/screen");
|
|
1166
|
-
} catch (e) {
|
|
1167
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
1168
|
-
if (message.includes("503") || message.includes("404")) {
|
|
1169
|
-
return null;
|
|
1170
|
-
}
|
|
1171
|
-
throw e;
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
|
-
async getStatus() {
|
|
1175
|
-
return this.request("GET", "/status");
|
|
1176
|
-
}
|
|
1177
|
-
async sendText(text) {
|
|
1178
|
-
return this.request("POST", "/send-text", { text });
|
|
1179
|
-
}
|
|
1180
|
-
async sendKey(key) {
|
|
1181
|
-
return this.request("POST", "/send-key", { key });
|
|
1182
|
-
}
|
|
1183
|
-
async connect(config) {
|
|
1184
|
-
return this.request("POST", "/connect", config);
|
|
1185
|
-
}
|
|
1186
|
-
async disconnect() {
|
|
1187
|
-
return this.request("POST", "/disconnect");
|
|
1188
|
-
}
|
|
1189
|
-
async reconnect() {
|
|
1190
|
-
return this.request("POST", "/reconnect");
|
|
1191
|
-
}
|
|
1192
|
-
};
|
|
1193
1537
|
|
|
1194
1538
|
// src/utils/rendering.ts
|
|
1195
1539
|
function getRowColorClass(rowIndex, rowContent) {
|
|
@@ -1199,10 +1543,18 @@ function parseHeaderRow(line) {
|
|
|
1199
1543
|
return getProtocolProfile("tn5250").colors.parseHeaderRow(line);
|
|
1200
1544
|
}
|
|
1201
1545
|
export {
|
|
1546
|
+
AlertTriangleIcon,
|
|
1202
1547
|
GreenScreenTerminal,
|
|
1548
|
+
InlineSignIn,
|
|
1549
|
+
KeyIcon,
|
|
1550
|
+
MinimizeIcon,
|
|
1551
|
+
RefreshIcon,
|
|
1203
1552
|
RestAdapter,
|
|
1204
|
-
TN5250Terminal,
|
|
1205
1553
|
TerminalBootLoader,
|
|
1554
|
+
TerminalIcon,
|
|
1555
|
+
WebSocketAdapter,
|
|
1556
|
+
WifiIcon,
|
|
1557
|
+
WifiOffIcon,
|
|
1206
1558
|
getProtocolProfile,
|
|
1207
1559
|
getRowColorClass,
|
|
1208
1560
|
hp6530Profile,
|
|
@@ -1211,9 +1563,6 @@ export {
|
|
|
1211
1563
|
positionToRowCol,
|
|
1212
1564
|
tn3270Profile,
|
|
1213
1565
|
tn5250Profile,
|
|
1214
|
-
useTN5250Connection,
|
|
1215
|
-
useTN5250Screen,
|
|
1216
|
-
useTN5250Terminal,
|
|
1217
1566
|
useTerminalConnection,
|
|
1218
1567
|
useTerminalInput,
|
|
1219
1568
|
useTerminalScreen,
|