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/dist/index.mjs CHANGED
@@ -1,7 +1,292 @@
1
1
  // src/components/GreenScreenTerminal.tsx
2
- import { useState as useState4, useEffect as useEffect4, useRef as useRef4, useCallback as useCallback3, useMemo as useMemo2 } from "react";
2
+ import { useState as useState5, useEffect as useEffect4, useRef as useRef4, useCallback as useCallback3, useMemo as useMemo2 } from "react";
3
3
 
4
- // src/hooks/useTN5250.ts
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/GreenScreenTerminal.tsx
576
- import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
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) : void 0,
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
- return /* @__PURE__ */ jsxs2("form", { onSubmit: handleSubmit, className: "gs-signin", children: [
651
- /* @__PURE__ */ jsxs2("div", { style: { textAlign: "center", marginBottom: "16px" }, children: [
652
- /* @__PURE__ */ jsx2(TerminalIcon, { size: 28 }),
653
- /* @__PURE__ */ jsx2("div", { style: { fontSize: "11px", letterSpacing: "0.15em", textTransform: "uppercase", color: "var(--gs-muted)", marginTop: "8px" }, children: "Connect to Host" })
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__ */ jsxs2("div", { className: "gs-signin-row", children: [
656
- /* @__PURE__ */ jsxs2("div", { style: { flex: 1 }, children: [
657
- /* @__PURE__ */ jsx2("label", { style: labelStyle, children: "Host" }),
658
- /* @__PURE__ */ jsx2("input", { style: inputStyle, value: host, onChange: (e) => setHost(e.target.value), placeholder: "192.168.1.100", required: true, autoFocus: true })
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__ */ jsxs2("div", { style: { width: "72px" }, children: [
661
- /* @__PURE__ */ jsx2("label", { style: labelStyle, children: "Port" }),
662
- /* @__PURE__ */ jsx2("input", { style: inputStyle, value: port, onChange: (e) => setPort(e.target.value), placeholder: "23", type: "number", min: "1", max: "65535" })
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__ */ jsxs2("div", { children: [
666
- /* @__PURE__ */ jsx2("label", { style: labelStyle, children: "Protocol" }),
667
- /* @__PURE__ */ jsx2("select", { style: { ...inputStyle, appearance: "none" }, value: selectedProtocol, onChange: (e) => setSelectedProtocol(e.target.value), children: PROTOCOL_OPTIONS.map((o) => /* @__PURE__ */ jsx2("option", { value: o.value, children: o.label }, o.value)) })
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__ */ jsxs2("div", { children: [
670
- /* @__PURE__ */ jsx2("label", { style: labelStyle, children: "Username" }),
671
- /* @__PURE__ */ jsx2("input", { style: inputStyle, value: username, onChange: (e) => setUsername(e.target.value), required: true, autoComplete: "username" })
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__ */ jsxs2("div", { children: [
674
- /* @__PURE__ */ jsx2("label", { style: labelStyle, children: "Password" }),
675
- /* @__PURE__ */ jsx2("input", { style: inputStyle, type: "password", value: password, onChange: (e) => setPassword(e.target.value), required: true, autoComplete: "current-password" })
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__ */ jsxs2("div", { style: { color: "#FF6B00", fontSize: "11px", fontFamily: "var(--gs-font)", display: "flex", alignItems: "center", gap: "6px" }, children: [
678
- /* @__PURE__ */ jsx2(AlertTriangleIcon, { size: 12 }),
679
- /* @__PURE__ */ jsx2("span", { children: error })
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__ */ jsx2("button", { type: "submit", disabled: loading || !host || !username || !password, className: "gs-signin-btn", children: loading ? "Connecting..." : "Connect" })
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 = false,
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] = useState4("");
736
- const [isFocused, setIsFocused] = useState4(false);
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] = useState4(null);
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] = useState4(0);
750
- const [isAutoReconnecting, setIsAutoReconnecting] = useState4(false);
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?.(config);
795
- await connect(config);
796
- }, [connect, onSignIn]);
797
- const [showBootLoader, setShowBootLoader] = useState4(bootLoader !== false);
798
- const [bootFadingOut, setBootFadingOut] = useState4(false);
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__ */ jsx2("span", { children: text.substring(lastIndex, match.index) }, `${keyPrefix}-t-${segmentIndex}`));
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__ */ jsx2("span", { style: { borderBottom: "1px solid var(--gs-green, #10b981)", display: "inline-block", width: `${count}ch` }, children: " ".repeat(count) }, `${keyPrefix}-u-${segmentIndex}`));
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__ */ jsx2("span", { children: text.substring(lastIndex) }, `${keyPrefix}-e`));
950
- return segments.length > 0 ? /* @__PURE__ */ jsx2(Fragment, { children: segments }) : /* @__PURE__ */ jsx2(Fragment, { children: text });
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__ */ jsx2("span", { children: renderTextWithUnderlines(line, `r${rowIndex}`) });
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__ */ jsx2("span", { children: renderTextWithUnderlines(line.substring(lastEnd, fs), `r${rowIndex}p${idx}`) }, `t${idx}`));
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 w = field.length >= 30 ? Math.max(field.length, 40) : field.length;
969
- segs.push(/* @__PURE__ */ jsx2("span", { className: "gs-input-field", style: { borderBottom: "2px solid var(--gs-green, #10b981)", display: "inline-block", minWidth: `${w}ch` }, children: fc }, `f${idx}`));
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__ */ jsx2("span", { style: { color: "#ef4444", fontWeight: "bold" }, children: fc }, `v${idx}`));
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__ */ jsx2("span", { style: { color: "var(--gs-white, #FFFFFF)" }, children: fc }, `h${idx}`));
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__ */ jsx2("span", { children: renderTextWithUnderlines(line.substring(lastEnd), `r${rowIndex}e`) }, "te"));
978
- return /* @__PURE__ */ jsx2(Fragment, { children: segs });
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__ */ jsx2(Fragment, { children: bootLoader });
984
- return /* @__PURE__ */ jsx2(TerminalBootLoader, { brandText: profile.bootText });
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__ */ jsx2("div", { className: "gs-fade-in", children: renderScreenContent() });
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__ */ jsx2("div", { style: { width: `${screenData?.cols || profile.defaultCols}ch`, height: `${(screenData?.rows || profile.defaultRows) * 21}px`, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsx2(InlineSignIn, { defaultProtocol: signInDefaultProtocol || protocol || "tn5250", loading: reconnecting, error: connectError, onConnect: handleSignIn }) });
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__ */ jsx2("div", { style: { width: `${screenData?.cols || profile.defaultCols}ch`, height: `${(screenData?.rows || profile.defaultRows) * 21}px`, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsxs2("div", { style: { textAlign: "center" }, children: [
992
- /* @__PURE__ */ jsx2("div", { style: { color: "#808080", marginBottom: "12px" }, children: /* @__PURE__ */ jsx2(TerminalIcon, { size: 40 }) }),
993
- /* @__PURE__ */ jsx2("p", { style: { fontFamily: "var(--gs-font)", fontSize: "12px", color: "#808080" }, children: connStatus?.connected ? "Waiting for screen data..." : "Not connected" })
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 hasInputFields = fields.some((f) => f.is_input);
1009
- const hasCursor = screenData.cursor_row !== void 0 && screenData.cursor_col !== void 0 && (hasInputFields || screenData.cursor_row !== 0 || screenData.cursor_col !== 0);
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__ */ jsxs2("div", { className: headerSegments ? "" : profile.colors.getRowColorClass(index, displayLine, termRows), style: { height: `${ROW_HEIGHT}px`, lineHeight: `${ROW_HEIGHT}px`, whiteSpace: "pre", position: "relative" }, children: [
1019
- headerSegments ? headerSegments.map((seg, i) => /* @__PURE__ */ jsx2("span", { className: seg.colorClass, children: seg.text }, i)) : renderRowWithFields(displayLine, index, fields),
1020
- hasCursor && index === cursor.row && /* @__PURE__ */ jsx2("span", { className: "gs-cursor", style: { position: "absolute", left: `${cursor.col}ch`, width: "1ch", height: `${ROW_HEIGHT}px`, top: 0, pointerEvents: "none" } })
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
- screenData.cursor_row !== void 0 && screenData.cursor_col !== void 0 && /* @__PURE__ */ jsxs2("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: [
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__ */ jsxs2("div", { className: `gs-terminal ${isFocused ? "gs-terminal-focused" : ""} ${className || ""}`, style, children: [
1054
- showHeader && /* @__PURE__ */ jsx2("div", { className: "gs-header", children: embedded ? /* @__PURE__ */ jsxs2(Fragment, { children: [
1055
- /* @__PURE__ */ jsxs2("span", { className: "gs-header-left", children: [
1056
- /* @__PURE__ */ jsx2(TerminalIcon, { size: 14 }),
1057
- /* @__PURE__ */ jsx2("span", { children: "TERMINAL" }),
1058
- isFocused && /* @__PURE__ */ jsx2("span", { className: "gs-badge-focused", children: "FOCUSED" }),
1059
- screenData?.timestamp && /* @__PURE__ */ jsx2("span", { className: "gs-timestamp", children: new Date(screenData.timestamp).toLocaleTimeString() }),
1060
- /* @__PURE__ */ jsx2("span", { className: "gs-hint", children: readOnly ? "Read-only" : isFocused ? "ESC to exit focus" : "Click to control" })
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__ */ jsxs2("div", { className: "gs-header-right", children: [
1063
- connStatus?.status && /* @__PURE__ */ jsx2(KeyIcon, { size: 12, style: { color: getStatusColor(connStatus.status) } }),
1064
- connStatus && (connStatus.connected ? /* @__PURE__ */ jsx2(WifiIcon, { size: 12, style: { color: "var(--gs-green, #10b981)" } }) : /* @__PURE__ */ jsx2(WifiOffIcon, { size: 12, style: { color: "#FF6B00" } })),
1065
- onMinimize && /* @__PURE__ */ jsx2("button", { onClick: (e) => {
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__ */ jsx2(MinimizeIcon, {}) }),
1472
+ }, className: "gs-btn-icon", title: "Minimize terminal", children: /* @__PURE__ */ jsx4(MinimizeIcon, {}) }),
1069
1473
  headerRight
1070
1474
  ] })
1071
- ] }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
1072
- /* @__PURE__ */ jsxs2("span", { className: "gs-header-left", children: [
1073
- /* @__PURE__ */ jsx2(TerminalIcon, { size: 14 }),
1074
- /* @__PURE__ */ jsx2("span", { children: profile.headerLabel }),
1075
- isFocused && /* @__PURE__ */ jsx2("span", { className: "gs-badge-focused", children: "FOCUSED" }),
1076
- screenData?.timestamp && /* @__PURE__ */ jsx2("span", { className: "gs-timestamp", children: new Date(screenData.timestamp).toLocaleTimeString() }),
1077
- /* @__PURE__ */ jsx2("span", { className: "gs-hint", children: readOnly ? "Read-only mode" : isFocused ? "ESC to exit focus" : "Click terminal to control" })
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__ */ jsxs2("div", { className: "gs-header-right", children: [
1080
- connStatus && /* @__PURE__ */ jsx2("div", { className: "gs-status-group", children: connStatus.connected ? /* @__PURE__ */ jsxs2(Fragment, { children: [
1081
- /* @__PURE__ */ jsx2(WifiIcon, { size: 12, style: { color: "var(--gs-green, #10b981)" } }),
1082
- /* @__PURE__ */ jsx2("span", { className: "gs-host", children: connStatus.host })
1083
- ] }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
1084
- /* @__PURE__ */ jsx2(WifiOffIcon, { size: 12, style: { color: "#FF6B00" } }),
1085
- /* @__PURE__ */ jsx2("span", { className: "gs-disconnected-text", children: isAutoReconnecting || reconnecting ? `RECONNECTING${autoReconnectAttempt > 0 ? ` (${autoReconnectAttempt}/${maxAttempts})` : "..."}` : autoReconnectAttempt >= maxAttempts ? "DISCONNECTED (auto-retry exhausted)" : "DISCONNECTED" }),
1086
- /* @__PURE__ */ jsx2("button", { onClick: handleReconnect, disabled: reconnecting || isAutoReconnecting, className: "gs-btn-icon", title: "Reconnect", children: /* @__PURE__ */ jsx2(RefreshIcon, { size: 12, className: reconnecting || isAutoReconnecting ? "gs-spin" : "" }) })
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__ */ jsxs2("div", { className: "gs-status-group", children: [
1089
- /* @__PURE__ */ jsx2(KeyIcon, { size: 12, style: { color: getStatusColor(connStatus.status) } }),
1090
- connStatus.username && /* @__PURE__ */ jsx2("span", { className: "gs-host", children: connStatus.username })
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__ */ jsx2("div", { className: "gs-body", children: /* @__PURE__ */ jsxs2(
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__ */ jsxs2("div", { className: "gs-error-banner", children: [
1104
- /* @__PURE__ */ jsx2(AlertTriangleIcon, { size: 14 }),
1105
- /* @__PURE__ */ jsx2("span", { children: String(screenError) })
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__ */ jsx2("div", { className: "gs-screen-content", children: renderScreen() }),
1511
+ /* @__PURE__ */ jsx4("div", { className: "gs-screen-content", children: renderScreen() }),
1108
1512
  overlay,
1109
- connStatus && !connStatus.connected && screenData && /* @__PURE__ */ jsxs2("div", { className: "gs-overlay", children: [
1110
- /* @__PURE__ */ jsx2(WifiOffIcon, { size: 28 }),
1111
- /* @__PURE__ */ jsx2("span", { children: isAutoReconnecting || reconnecting ? "Reconnecting..." : "Disconnected" })
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__ */ jsx2(
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,