green-screen-react 0.4.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 +33 -259
- package/dist/index.d.mts +40 -91
- package/dist/index.d.ts +40 -91
- package/dist/index.js +208 -64
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +207 -64
- 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.js
CHANGED
|
@@ -111,15 +111,40 @@ var RestAdapter = class {
|
|
|
111
111
|
};
|
|
112
112
|
|
|
113
113
|
// src/adapters/WebSocketAdapter.ts
|
|
114
|
-
var
|
|
114
|
+
var import_meta = {};
|
|
115
|
+
var WebSocketAdapter = class _WebSocketAdapter {
|
|
115
116
|
constructor(options = {}) {
|
|
116
117
|
this.ws = null;
|
|
117
118
|
this.screen = null;
|
|
118
119
|
this.status = { connected: false, status: "disconnected" };
|
|
119
|
-
this.
|
|
120
|
+
this.pendingScreenResolver = null;
|
|
121
|
+
this.pendingConnectResolver = null;
|
|
122
|
+
this.connectingPromise = null;
|
|
120
123
|
this.screenListeners = /* @__PURE__ */ new Set();
|
|
121
124
|
this.statusListeners = /* @__PURE__ */ new Set();
|
|
122
|
-
this.
|
|
125
|
+
this._sessionId = null;
|
|
126
|
+
this.workerUrl = (options.workerUrl || _WebSocketAdapter.detectEnvUrl() || "http://localhost:3001").replace(/\/+$/, "");
|
|
127
|
+
}
|
|
128
|
+
static detectEnvUrl() {
|
|
129
|
+
try {
|
|
130
|
+
if (typeof import_meta !== "undefined" && import_meta.env) {
|
|
131
|
+
const env = import_meta.env;
|
|
132
|
+
return env.VITE_GREEN_SCREEN_URL || env.VITE_WORKER_URL || void 0;
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
const p = typeof globalThis !== "undefined" && globalThis.process;
|
|
138
|
+
if (p && p.env) {
|
|
139
|
+
return p.env.NEXT_PUBLIC_GREEN_SCREEN_URL || p.env.REACT_APP_GREEN_SCREEN_URL || void 0;
|
|
140
|
+
}
|
|
141
|
+
} catch {
|
|
142
|
+
}
|
|
143
|
+
return void 0;
|
|
144
|
+
}
|
|
145
|
+
/** The proxy-side session ID (available after connect or reattach) */
|
|
146
|
+
get sessionId() {
|
|
147
|
+
return this._sessionId;
|
|
123
148
|
}
|
|
124
149
|
/** Subscribe to real-time screen updates */
|
|
125
150
|
onScreen(listener) {
|
|
@@ -150,24 +175,13 @@ var WebSocketAdapter = class {
|
|
|
150
175
|
}
|
|
151
176
|
return new Promise((resolve) => {
|
|
152
177
|
const timeout = setTimeout(() => {
|
|
178
|
+
this.pendingConnectResolver = null;
|
|
153
179
|
resolve({ success: false, error: "Connection timeout" });
|
|
154
180
|
}, 3e4);
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (msg.type === "connected") {
|
|
159
|
-
clearTimeout(timeout);
|
|
160
|
-
this.ws?.removeEventListener("message", onMessage);
|
|
161
|
-
resolve({ success: true });
|
|
162
|
-
} else if (msg.type === "error") {
|
|
163
|
-
clearTimeout(timeout);
|
|
164
|
-
this.ws?.removeEventListener("message", onMessage);
|
|
165
|
-
resolve({ success: false, error: msg.message });
|
|
166
|
-
}
|
|
167
|
-
} catch {
|
|
168
|
-
}
|
|
181
|
+
this.pendingConnectResolver = (result) => {
|
|
182
|
+
clearTimeout(timeout);
|
|
183
|
+
resolve(result);
|
|
169
184
|
};
|
|
170
|
-
this.ws?.addEventListener("message", onMessage);
|
|
171
185
|
this.wsSend({
|
|
172
186
|
type: "connect",
|
|
173
187
|
host: config.host,
|
|
@@ -178,9 +192,29 @@ var WebSocketAdapter = class {
|
|
|
178
192
|
});
|
|
179
193
|
});
|
|
180
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Reattach to an existing proxy session (e.g. after page reload).
|
|
197
|
+
* The proxy keeps the TCP connection alive; this just reconnects the
|
|
198
|
+
* WebSocket and receives the current screen.
|
|
199
|
+
*/
|
|
200
|
+
async reattach(sessionId) {
|
|
201
|
+
await this.ensureWebSocket();
|
|
202
|
+
return new Promise((resolve) => {
|
|
203
|
+
const timeout = setTimeout(() => {
|
|
204
|
+
this.pendingConnectResolver = null;
|
|
205
|
+
resolve({ success: false, error: "Reattach timeout" });
|
|
206
|
+
}, 1e4);
|
|
207
|
+
this.pendingConnectResolver = (result) => {
|
|
208
|
+
clearTimeout(timeout);
|
|
209
|
+
resolve(result);
|
|
210
|
+
};
|
|
211
|
+
this.wsSend({ type: "reattach", sessionId });
|
|
212
|
+
});
|
|
213
|
+
}
|
|
181
214
|
async disconnect() {
|
|
182
215
|
this.wsSend({ type: "disconnect" });
|
|
183
216
|
this.status = { connected: false, status: "disconnected" };
|
|
217
|
+
this._sessionId = null;
|
|
184
218
|
if (this.ws) {
|
|
185
219
|
this.ws.close();
|
|
186
220
|
this.ws = null;
|
|
@@ -190,7 +224,7 @@ var WebSocketAdapter = class {
|
|
|
190
224
|
async reconnect() {
|
|
191
225
|
return { success: false, error: "Use disconnect() then connect() instead" };
|
|
192
226
|
}
|
|
193
|
-
/** Close the WebSocket without sending disconnect */
|
|
227
|
+
/** Close the WebSocket without sending disconnect (session stays alive on proxy) */
|
|
194
228
|
dispose() {
|
|
195
229
|
if (this.ws) {
|
|
196
230
|
this.ws.close();
|
|
@@ -199,7 +233,8 @@ var WebSocketAdapter = class {
|
|
|
199
233
|
}
|
|
200
234
|
async ensureWebSocket() {
|
|
201
235
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) return;
|
|
202
|
-
|
|
236
|
+
if (this.connectingPromise) return this.connectingPromise;
|
|
237
|
+
this.connectingPromise = new Promise((resolve, reject) => {
|
|
203
238
|
const wsUrl = this.workerUrl.replace(/^http/, "ws") + "/ws";
|
|
204
239
|
this.ws = new WebSocket(wsUrl);
|
|
205
240
|
this.ws.onopen = () => resolve();
|
|
@@ -215,39 +250,61 @@ var WebSocketAdapter = class {
|
|
|
215
250
|
this.status = { connected: false, status: "disconnected" };
|
|
216
251
|
for (const listener of this.statusListeners) listener(this.status);
|
|
217
252
|
};
|
|
253
|
+
}).finally(() => {
|
|
254
|
+
this.connectingPromise = null;
|
|
218
255
|
});
|
|
256
|
+
return this.connectingPromise;
|
|
219
257
|
}
|
|
220
258
|
handleMessage(msg) {
|
|
221
259
|
switch (msg.type) {
|
|
222
|
-
case "screen":
|
|
260
|
+
case "screen": {
|
|
223
261
|
this.screen = msg.data;
|
|
224
262
|
for (const listener of this.screenListeners) listener(msg.data);
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
this.
|
|
228
|
-
|
|
263
|
+
if (this.pendingScreenResolver) {
|
|
264
|
+
const resolver = this.pendingScreenResolver;
|
|
265
|
+
this.pendingScreenResolver = null;
|
|
266
|
+
resolver(msg.data);
|
|
229
267
|
}
|
|
230
268
|
break;
|
|
269
|
+
}
|
|
231
270
|
case "status":
|
|
232
271
|
this.status = msg.data;
|
|
233
272
|
for (const listener of this.statusListeners) listener(msg.data);
|
|
234
273
|
break;
|
|
235
|
-
case "
|
|
236
|
-
|
|
237
|
-
if (
|
|
238
|
-
this.
|
|
239
|
-
|
|
274
|
+
case "connected":
|
|
275
|
+
this._sessionId = msg.sessionId ?? null;
|
|
276
|
+
if (this.pendingConnectResolver) {
|
|
277
|
+
const resolver = this.pendingConnectResolver;
|
|
278
|
+
this.pendingConnectResolver = null;
|
|
279
|
+
resolver({ success: true });
|
|
280
|
+
}
|
|
281
|
+
break;
|
|
282
|
+
case "error": {
|
|
283
|
+
if (this.pendingConnectResolver) {
|
|
284
|
+
const resolver = this.pendingConnectResolver;
|
|
285
|
+
this.pendingConnectResolver = null;
|
|
286
|
+
resolver({ success: false, error: msg.message });
|
|
287
|
+
} else if (this.pendingScreenResolver) {
|
|
288
|
+
const resolver = this.pendingScreenResolver;
|
|
289
|
+
this.pendingScreenResolver = null;
|
|
290
|
+
resolver(null);
|
|
240
291
|
}
|
|
241
292
|
break;
|
|
293
|
+
}
|
|
242
294
|
}
|
|
243
295
|
}
|
|
244
296
|
sendAndWaitForScreen(msg) {
|
|
245
297
|
return new Promise((resolve) => {
|
|
298
|
+
if (this.pendingScreenResolver) {
|
|
299
|
+
const old = this.pendingScreenResolver;
|
|
300
|
+
this.pendingScreenResolver = null;
|
|
301
|
+
old(this.screen);
|
|
302
|
+
}
|
|
246
303
|
const timeout = setTimeout(() => {
|
|
247
|
-
this.
|
|
304
|
+
this.pendingScreenResolver = null;
|
|
248
305
|
resolve({ success: true, ...this.screenToResult() });
|
|
249
306
|
}, 5e3);
|
|
250
|
-
this.
|
|
307
|
+
this.pendingScreenResolver = (screen) => {
|
|
251
308
|
clearTimeout(timeout);
|
|
252
309
|
if (screen) {
|
|
253
310
|
resolve({
|
|
@@ -260,7 +317,7 @@ var WebSocketAdapter = class {
|
|
|
260
317
|
} else {
|
|
261
318
|
resolve({ success: false, error: "No screen data received" });
|
|
262
319
|
}
|
|
263
|
-
}
|
|
320
|
+
};
|
|
264
321
|
this.wsSend(msg);
|
|
265
322
|
});
|
|
266
323
|
}
|
|
@@ -891,20 +948,23 @@ var PROTOCOL_OPTIONS = [
|
|
|
891
948
|
{ value: "vt", label: "VT220" },
|
|
892
949
|
{ value: "hp6530", label: "HP 6530 (NonStop)" }
|
|
893
950
|
];
|
|
894
|
-
function InlineSignIn({ defaultProtocol, loading, error, onConnect }) {
|
|
951
|
+
function InlineSignIn({ defaultProtocol, loading: externalLoading, error, onConnect }) {
|
|
895
952
|
const [host, setHost] = (0, import_react4.useState)("");
|
|
896
953
|
const [port, setPort] = (0, import_react4.useState)("");
|
|
897
954
|
const [selectedProtocol, setSelectedProtocol] = (0, import_react4.useState)(defaultProtocol);
|
|
898
955
|
const [username, setUsername] = (0, import_react4.useState)("");
|
|
899
956
|
const [password, setPassword] = (0, import_react4.useState)("");
|
|
957
|
+
const [submitted, setSubmitted] = (0, import_react4.useState)(false);
|
|
958
|
+
const loading = externalLoading || submitted;
|
|
900
959
|
const handleSubmit = (e) => {
|
|
901
960
|
e.preventDefault();
|
|
961
|
+
setSubmitted(true);
|
|
902
962
|
onConnect({
|
|
903
963
|
host,
|
|
904
|
-
port: port ? parseInt(port, 10) :
|
|
964
|
+
port: port ? parseInt(port, 10) : 23,
|
|
905
965
|
protocol: selectedProtocol,
|
|
906
|
-
username,
|
|
907
|
-
password
|
|
966
|
+
...username.trim() ? { username: username.trim() } : {},
|
|
967
|
+
...password ? { password } : {}
|
|
908
968
|
});
|
|
909
969
|
};
|
|
910
970
|
const inputStyle = {
|
|
@@ -927,6 +987,16 @@ function InlineSignIn({ defaultProtocol, loading, error, onConnect }) {
|
|
|
927
987
|
color: "var(--gs-muted, #94a3b8)",
|
|
928
988
|
fontFamily: "var(--gs-font)"
|
|
929
989
|
};
|
|
990
|
+
if (loading) {
|
|
991
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "gs-signin", style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: "12px" }, children: [
|
|
992
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(RefreshIcon, { size: 28, className: "gs-spin" }),
|
|
993
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { fontSize: "11px", letterSpacing: "0.15em", textTransform: "uppercase", color: "var(--gs-muted)", fontFamily: "var(--gs-font)" }, children: [
|
|
994
|
+
"Connecting to ",
|
|
995
|
+
host || "host",
|
|
996
|
+
"..."
|
|
997
|
+
] })
|
|
998
|
+
] });
|
|
999
|
+
}
|
|
930
1000
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("form", { onSubmit: handleSubmit, className: "gs-signin", children: [
|
|
931
1001
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { textAlign: "center", marginBottom: "16px" }, children: [
|
|
932
1002
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TerminalIcon, { size: 28 }),
|
|
@@ -934,7 +1004,10 @@ function InlineSignIn({ defaultProtocol, loading, error, onConnect }) {
|
|
|
934
1004
|
] }),
|
|
935
1005
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "gs-signin-row", children: [
|
|
936
1006
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { flex: 1 }, children: [
|
|
937
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.
|
|
1007
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { style: labelStyle, children: [
|
|
1008
|
+
"Host ",
|
|
1009
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { color: "#ef4444" }, children: "*" })
|
|
1010
|
+
] }),
|
|
938
1011
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("input", { style: inputStyle, value: host, onChange: (e) => setHost(e.target.value), placeholder: "192.168.1.100", required: true, autoFocus: true })
|
|
939
1012
|
] }),
|
|
940
1013
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { width: "72px" }, children: [
|
|
@@ -943,22 +1016,25 @@ function InlineSignIn({ defaultProtocol, loading, error, onConnect }) {
|
|
|
943
1016
|
] })
|
|
944
1017
|
] }),
|
|
945
1018
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
|
|
946
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.
|
|
1019
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { style: labelStyle, children: [
|
|
1020
|
+
"Protocol ",
|
|
1021
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { color: "#ef4444" }, children: "*" })
|
|
1022
|
+
] }),
|
|
947
1023
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("select", { style: { ...inputStyle, appearance: "none" }, value: selectedProtocol, onChange: (e) => setSelectedProtocol(e.target.value), children: PROTOCOL_OPTIONS.map((o) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: o.value, children: o.label }, o.value)) })
|
|
948
1024
|
] }),
|
|
949
1025
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
|
|
950
1026
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { style: labelStyle, children: "Username" }),
|
|
951
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("input", { style: inputStyle, value: username, onChange: (e) => setUsername(e.target.value),
|
|
1027
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("input", { style: inputStyle, value: username, onChange: (e) => setUsername(e.target.value), autoComplete: "username" })
|
|
952
1028
|
] }),
|
|
953
1029
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
|
|
954
1030
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { style: labelStyle, children: "Password" }),
|
|
955
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("input", { style: inputStyle, type: "password", value: password, onChange: (e) => setPassword(e.target.value),
|
|
1031
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("input", { style: inputStyle, type: "password", value: password, onChange: (e) => setPassword(e.target.value), autoComplete: "current-password" })
|
|
956
1032
|
] }),
|
|
957
1033
|
error && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { color: "#FF6B00", fontSize: "11px", fontFamily: "var(--gs-font)", display: "flex", alignItems: "center", gap: "6px" }, children: [
|
|
958
1034
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AlertTriangleIcon, { size: 12 }),
|
|
959
1035
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: error })
|
|
960
1036
|
] }),
|
|
961
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "submit", disabled:
|
|
1037
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "submit", disabled: !host, className: "gs-signin-btn", children: "Connect" })
|
|
962
1038
|
] });
|
|
963
1039
|
}
|
|
964
1040
|
|
|
@@ -977,6 +1053,7 @@ var noopAdapter = {
|
|
|
977
1053
|
function GreenScreenTerminal({
|
|
978
1054
|
adapter: externalAdapter,
|
|
979
1055
|
baseUrl,
|
|
1056
|
+
workerUrl,
|
|
980
1057
|
protocol,
|
|
981
1058
|
protocolProfile: customProfile,
|
|
982
1059
|
screenData: externalScreenData,
|
|
@@ -992,6 +1069,8 @@ function GreenScreenTerminal({
|
|
|
992
1069
|
inlineSignIn = true,
|
|
993
1070
|
defaultProtocol: signInDefaultProtocol,
|
|
994
1071
|
onSignIn,
|
|
1072
|
+
autoSignedIn,
|
|
1073
|
+
autoFocusDisabled = false,
|
|
995
1074
|
bootLoader,
|
|
996
1075
|
headerRight,
|
|
997
1076
|
overlay,
|
|
@@ -1007,11 +1086,16 @@ function GreenScreenTerminal({
|
|
|
1007
1086
|
() => baseUrl ? new RestAdapter({ baseUrl }) : null,
|
|
1008
1087
|
[baseUrl]
|
|
1009
1088
|
);
|
|
1089
|
+
const workerUrlAdapter = (0, import_react5.useMemo)(
|
|
1090
|
+
() => workerUrl ? new WebSocketAdapter({ workerUrl }) : null,
|
|
1091
|
+
[workerUrl]
|
|
1092
|
+
);
|
|
1010
1093
|
const defaultWsAdapter = (0, import_react5.useMemo)(
|
|
1011
|
-
() => !externalAdapter && !baseUrl ? new WebSocketAdapter() : null,
|
|
1012
|
-
[externalAdapter, baseUrl]
|
|
1094
|
+
() => !externalAdapter && !baseUrl && !workerUrl ? new WebSocketAdapter() : null,
|
|
1095
|
+
[externalAdapter, baseUrl, workerUrl]
|
|
1013
1096
|
);
|
|
1014
|
-
const adapter = externalAdapter ?? baseUrlAdapter ?? internalAdapter ?? defaultWsAdapter ?? noopAdapter;
|
|
1097
|
+
const adapter = externalAdapter ?? baseUrlAdapter ?? workerUrlAdapter ?? internalAdapter ?? defaultWsAdapter ?? noopAdapter;
|
|
1098
|
+
const isUsingDefaultAdapter = adapter === defaultWsAdapter;
|
|
1015
1099
|
const shouldPoll = pollInterval > 0 && !externalScreenData;
|
|
1016
1100
|
const { data: polledScreenData, error: screenError } = useTerminalScreen(adapter, pollInterval, shouldPoll);
|
|
1017
1101
|
const { sendText: _sendText, sendKey: _sendKey } = useTerminalInput(adapter);
|
|
@@ -1038,6 +1122,12 @@ function GreenScreenTerminal({
|
|
|
1038
1122
|
const sendKey = (0, import_react5.useCallback)(async (key) => _sendKey(key), [_sendKey]);
|
|
1039
1123
|
const [inputText, setInputText] = (0, import_react5.useState)("");
|
|
1040
1124
|
const [isFocused, setIsFocused] = (0, import_react5.useState)(false);
|
|
1125
|
+
const [showSignInHint, setShowSignInHint] = (0, import_react5.useState)(false);
|
|
1126
|
+
const prevAutoSignedIn = (0, import_react5.useRef)(false);
|
|
1127
|
+
(0, import_react5.useEffect)(() => {
|
|
1128
|
+
if (autoSignedIn && !prevAutoSignedIn.current) setShowSignInHint(true);
|
|
1129
|
+
prevAutoSignedIn.current = !!autoSignedIn;
|
|
1130
|
+
}, [autoSignedIn]);
|
|
1041
1131
|
const terminalRef = (0, import_react5.useRef)(null);
|
|
1042
1132
|
const inputRef = (0, import_react5.useRef)(null);
|
|
1043
1133
|
const [syncedCursor, setSyncedCursor] = (0, import_react5.useState)(null);
|
|
@@ -1047,6 +1137,7 @@ function GreenScreenTerminal({
|
|
|
1047
1137
|
if (prevRawContentRef.current && newContent && newContent !== prevRawContentRef.current) {
|
|
1048
1138
|
setSyncedCursor(null);
|
|
1049
1139
|
setInputText("");
|
|
1140
|
+
if (showSignInHint) setShowSignInHint(false);
|
|
1050
1141
|
}
|
|
1051
1142
|
prevRawContentRef.current = newContent;
|
|
1052
1143
|
}, [rawScreenData?.content]);
|
|
@@ -1094,17 +1185,34 @@ function GreenScreenTerminal({
|
|
|
1094
1185
|
if (reconnectTimeoutRef.current) clearTimeout(reconnectTimeoutRef.current);
|
|
1095
1186
|
};
|
|
1096
1187
|
}, [connStatus?.connected, autoReconnectAttempt, isAutoReconnecting, reconnecting, reconnect, autoReconnectEnabled, maxAttempts, onNotification]);
|
|
1188
|
+
const [connecting, setConnecting] = (0, import_react5.useState)(false);
|
|
1189
|
+
const [signInError, setSignInError] = (0, import_react5.useState)(null);
|
|
1097
1190
|
const handleSignIn = (0, import_react5.useCallback)(async (config) => {
|
|
1098
|
-
onSignIn
|
|
1099
|
-
|
|
1100
|
-
const port = config.port ? `:${config.port}` : "";
|
|
1101
|
-
const newAdapter = new RestAdapter({ baseUrl: `http://${config.host}${port}` });
|
|
1102
|
-
setInternalAdapter(newAdapter);
|
|
1103
|
-
await newAdapter.connect(config);
|
|
1191
|
+
if (onSignIn) {
|
|
1192
|
+
onSignIn(config);
|
|
1104
1193
|
return;
|
|
1105
1194
|
}
|
|
1106
|
-
|
|
1195
|
+
setConnecting(true);
|
|
1196
|
+
setSignInError(null);
|
|
1197
|
+
try {
|
|
1198
|
+
if (!externalAdapter && !baseUrlAdapter) {
|
|
1199
|
+
const port = config.port ? `:${config.port}` : "";
|
|
1200
|
+
const newAdapter = new RestAdapter({ baseUrl: `http://${config.host}${port}` });
|
|
1201
|
+
setInternalAdapter(newAdapter);
|
|
1202
|
+
await newAdapter.connect(config);
|
|
1203
|
+
if (config.username && config.password) setShowSignInHint(true);
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
await connect(config);
|
|
1207
|
+
if (config.username && config.password) setShowSignInHint(true);
|
|
1208
|
+
} catch (err) {
|
|
1209
|
+
setSignInError(err instanceof Error ? err.message : String(err));
|
|
1210
|
+
setConnecting(false);
|
|
1211
|
+
}
|
|
1107
1212
|
}, [connect, onSignIn, externalAdapter, baseUrlAdapter]);
|
|
1213
|
+
(0, import_react5.useEffect)(() => {
|
|
1214
|
+
if (connecting && screenData?.content) setConnecting(false);
|
|
1215
|
+
}, [connecting, screenData?.content]);
|
|
1108
1216
|
const [showBootLoader, setShowBootLoader] = (0, import_react5.useState)(bootLoader !== false);
|
|
1109
1217
|
const [bootFadingOut, setBootFadingOut] = (0, import_react5.useState)(false);
|
|
1110
1218
|
(0, import_react5.useEffect)(() => {
|
|
@@ -1115,6 +1223,35 @@ function GreenScreenTerminal({
|
|
|
1115
1223
|
return () => clearTimeout(timer);
|
|
1116
1224
|
}
|
|
1117
1225
|
}, [screenData?.content, showBootLoader]);
|
|
1226
|
+
const FOCUS_STORAGE_KEY = "gs-terminal-focused";
|
|
1227
|
+
(0, import_react5.useEffect)(() => {
|
|
1228
|
+
if (!autoFocusDisabled && !readOnly) {
|
|
1229
|
+
try {
|
|
1230
|
+
if (localStorage.getItem(FOCUS_STORAGE_KEY) === "true") {
|
|
1231
|
+
setIsFocused(true);
|
|
1232
|
+
}
|
|
1233
|
+
} catch {
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
}, []);
|
|
1237
|
+
(0, import_react5.useEffect)(() => {
|
|
1238
|
+
if (autoFocusDisabled) return;
|
|
1239
|
+
try {
|
|
1240
|
+
localStorage.setItem(FOCUS_STORAGE_KEY, String(isFocused));
|
|
1241
|
+
} catch {
|
|
1242
|
+
}
|
|
1243
|
+
}, [isFocused, autoFocusDisabled]);
|
|
1244
|
+
(0, import_react5.useEffect)(() => {
|
|
1245
|
+
if (isFocused) inputRef.current?.focus();
|
|
1246
|
+
}, [isFocused]);
|
|
1247
|
+
const hadScreenData = (0, import_react5.useRef)(false);
|
|
1248
|
+
(0, import_react5.useEffect)(() => {
|
|
1249
|
+
if (screenData?.content && !hadScreenData.current && !autoFocusDisabled && !readOnly) {
|
|
1250
|
+
hadScreenData.current = true;
|
|
1251
|
+
setIsFocused(true);
|
|
1252
|
+
}
|
|
1253
|
+
if (!screenData?.content) hadScreenData.current = false;
|
|
1254
|
+
}, [screenData?.content, autoFocusDisabled, readOnly]);
|
|
1118
1255
|
(0, import_react5.useEffect)(() => {
|
|
1119
1256
|
const handleClickOutside = (event) => {
|
|
1120
1257
|
if (terminalRef.current && !terminalRef.current.contains(event.target)) setIsFocused(false);
|
|
@@ -1265,7 +1402,7 @@ function GreenScreenTerminal({
|
|
|
1265
1402
|
const highlightedFields = fields.filter((f) => f.row === rowIndex && f.is_protected && f.is_highlighted);
|
|
1266
1403
|
const reverseFields = fields.filter((f) => f.row === rowIndex && f.is_protected && f.is_reverse);
|
|
1267
1404
|
const allRowFields = [...inputFields, ...highlightedFields, ...reverseFields];
|
|
1268
|
-
if (allRowFields.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children:
|
|
1405
|
+
if (allRowFields.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: line });
|
|
1269
1406
|
const sorted = [...allRowFields].sort((a, b) => a.col - b.col);
|
|
1270
1407
|
const segs = [];
|
|
1271
1408
|
let lastEnd = 0;
|
|
@@ -1273,21 +1410,24 @@ function GreenScreenTerminal({
|
|
|
1273
1410
|
sorted.forEach((field, idx) => {
|
|
1274
1411
|
const fs = field.col;
|
|
1275
1412
|
const fe = Math.min(field.col + field.length, cols);
|
|
1276
|
-
if (fs > lastEnd) segs.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children:
|
|
1413
|
+
if (fs > lastEnd) segs.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: line.substring(lastEnd, fs) }, `t${idx}`));
|
|
1277
1414
|
const fc = line.substring(fs, fe);
|
|
1278
1415
|
if (field.is_input) {
|
|
1279
|
-
const
|
|
1280
|
-
|
|
1416
|
+
const fieldWidth = Math.min(field.length, cols - fs);
|
|
1417
|
+
const fieldClass = showSignInHint ? "gs-confirmed-field" : field.is_underscored ? "gs-input-field" : void 0;
|
|
1418
|
+
segs.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: fieldClass || void 0, style: { display: "inline-block", width: `${fieldWidth}ch`, overflow: "hidden" }, children: fc }, `f${idx}`));
|
|
1281
1419
|
} else if (field.is_reverse) {
|
|
1282
1420
|
segs.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { color: "#ef4444", fontWeight: "bold" }, children: fc }, `v${idx}`));
|
|
1283
|
-
} else {
|
|
1421
|
+
} else if (field.is_highlighted) {
|
|
1284
1422
|
segs.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { color: "var(--gs-white, #FFFFFF)" }, children: fc }, `h${idx}`));
|
|
1423
|
+
} else {
|
|
1424
|
+
segs.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: fc }, `h${idx}`));
|
|
1285
1425
|
}
|
|
1286
1426
|
lastEnd = fe;
|
|
1287
1427
|
});
|
|
1288
|
-
if (lastEnd < line.length) segs.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children:
|
|
1428
|
+
if (lastEnd < line.length) segs.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: line.substring(lastEnd) }, "te"));
|
|
1289
1429
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: segs });
|
|
1290
|
-
}, [renderTextWithUnderlines, screenData?.cols, profile.defaultCols]);
|
|
1430
|
+
}, [renderTextWithUnderlines, screenData?.cols, profile.defaultCols, showSignInHint]);
|
|
1291
1431
|
const renderScreen = () => {
|
|
1292
1432
|
if (showBootLoader && !screenData?.content) {
|
|
1293
1433
|
if (bootLoader === false) return null;
|
|
@@ -1297,11 +1437,15 @@ function GreenScreenTerminal({
|
|
|
1297
1437
|
if (bootFadingOut && screenData?.content) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "gs-fade-in", children: renderScreenContent() });
|
|
1298
1438
|
if (!screenData?.content) {
|
|
1299
1439
|
if (inlineSignIn && !connStatus?.connected) {
|
|
1300
|
-
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { width:
|
|
1440
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { width: "100%", height: `${(screenData?.rows || profile.defaultRows) * 21}px`, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(InlineSignIn, { defaultProtocol: signInDefaultProtocol || protocol || "tn5250", loading: connecting || reconnecting, error: signInError || connectError, onConnect: handleSignIn }) });
|
|
1301
1441
|
}
|
|
1302
1442
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { width: `${screenData?.cols || profile.defaultCols}ch`, height: `${(screenData?.rows || profile.defaultRows) * 21}px`, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { textAlign: "center" }, children: [
|
|
1303
1443
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { color: "#808080", marginBottom: "12px" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TerminalIcon, { size: 40 }) }),
|
|
1304
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: { fontFamily: "var(--gs-font)", fontSize: "12px", color: "#808080" }, children: connStatus?.connected ? "Waiting for screen data..." : "Not connected" })
|
|
1444
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: { fontFamily: "var(--gs-font)", fontSize: "12px", color: "#808080" }, children: connStatus?.connected ? "Waiting for screen data..." : "Not connected" }),
|
|
1445
|
+
!connStatus?.connected && isUsingDefaultAdapter && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { style: { fontFamily: "var(--gs-font)", fontSize: "11px", color: "#606060", marginTop: "8px" }, children: [
|
|
1446
|
+
"Start the proxy: ",
|
|
1447
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("code", { style: { color: "#10b981" }, children: "npx green-screen-proxy --mock" })
|
|
1448
|
+
] })
|
|
1305
1449
|
] }) });
|
|
1306
1450
|
}
|
|
1307
1451
|
return renderScreenContent();
|
|
@@ -1316,8 +1460,7 @@ function GreenScreenTerminal({
|
|
|
1316
1460
|
const fields = screenData.fields || [];
|
|
1317
1461
|
const ROW_HEIGHT = 21;
|
|
1318
1462
|
const cursor = getCursorPos();
|
|
1319
|
-
const
|
|
1320
|
-
const hasCursor = screenData.cursor_row !== void 0 && screenData.cursor_col !== void 0 && (hasInputFields || screenData.cursor_row !== 0 || screenData.cursor_col !== 0);
|
|
1463
|
+
const hasCursor = screenData.cursor_row !== void 0 && screenData.cursor_col !== void 0;
|
|
1321
1464
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { fontFamily: "var(--gs-font)", fontSize: "13px", position: "relative", width: `${cols}ch` }, children: [
|
|
1322
1465
|
rows.map((line, index) => {
|
|
1323
1466
|
let displayLine = line;
|
|
@@ -1328,9 +1471,10 @@ function GreenScreenTerminal({
|
|
|
1328
1471
|
const headerSegments = index === 0 ? profile.colors.parseHeaderRow(displayLine) : null;
|
|
1329
1472
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: headerSegments ? "" : profile.colors.getRowColorClass(index, displayLine, termRows), style: { height: `${ROW_HEIGHT}px`, lineHeight: `${ROW_HEIGHT}px`, whiteSpace: "pre", position: "relative" }, children: [
|
|
1330
1473
|
headerSegments ? headerSegments.map((seg, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: seg.colorClass, children: seg.text }, i)) : renderRowWithFields(displayLine, index, fields),
|
|
1331
|
-
hasCursor && index === cursor.row && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "gs-cursor", style: { position: "absolute", left: `${cursor.col}ch`, width: "1ch", height: `${ROW_HEIGHT}px`, top: 0, pointerEvents: "none" } })
|
|
1474
|
+
hasCursor && !showSignInHint && index === cursor.row && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "gs-cursor", style: { position: "absolute", left: `${cursor.col}ch`, width: "1ch", height: `${ROW_HEIGHT}px`, top: 0, pointerEvents: "none" } })
|
|
1332
1475
|
] }, index);
|
|
1333
1476
|
}),
|
|
1477
|
+
showSignInHint && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "gs-signin-hint", children: "Signed in \u2014 press Enter to continue" }),
|
|
1334
1478
|
screenData.cursor_row !== void 0 && screenData.cursor_col !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("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: [
|
|
1335
1479
|
String(screenData.cursor_row + 1).padStart(2, "0"),
|
|
1336
1480
|
"/",
|