green-screen-react 1.1.2 → 1.2.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 +57 -0
- package/dist/index.d.mts +35 -5
- package/dist/index.d.ts +35 -5
- package/dist/index.js +577 -90
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +577 -90
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +80 -35
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -52,6 +52,14 @@ var RestAdapter = class {
|
|
|
52
52
|
async setCursor(row, col) {
|
|
53
53
|
return this.request("POST", "/set-cursor", { row, col });
|
|
54
54
|
}
|
|
55
|
+
async readMdt(modifiedOnly = true) {
|
|
56
|
+
const qs = modifiedOnly ? "" : "?includeUnmodified=1";
|
|
57
|
+
const resp = await this.request(
|
|
58
|
+
"GET",
|
|
59
|
+
`/read-mdt${qs}`
|
|
60
|
+
);
|
|
61
|
+
return resp?.fields ?? [];
|
|
62
|
+
}
|
|
55
63
|
async connect(config) {
|
|
56
64
|
return this.request("POST", "/connect", config);
|
|
57
65
|
}
|
|
@@ -71,9 +79,12 @@ var WebSocketAdapter = class _WebSocketAdapter {
|
|
|
71
79
|
this.status = { connected: false, status: "disconnected" };
|
|
72
80
|
this.pendingScreenResolver = null;
|
|
73
81
|
this.pendingConnectResolver = null;
|
|
82
|
+
this.pendingMdtResolver = null;
|
|
74
83
|
this.connectingPromise = null;
|
|
75
84
|
this.screenListeners = /* @__PURE__ */ new Set();
|
|
76
85
|
this.statusListeners = /* @__PURE__ */ new Set();
|
|
86
|
+
this.sessionLostListeners = /* @__PURE__ */ new Set();
|
|
87
|
+
this.sessionResumedListeners = /* @__PURE__ */ new Set();
|
|
77
88
|
this._sessionId = null;
|
|
78
89
|
this.workerUrl = (options.workerUrl || _WebSocketAdapter.detectEnvUrl() || "http://localhost:3001").replace(/\/+$/, "");
|
|
79
90
|
}
|
|
@@ -108,6 +119,22 @@ var WebSocketAdapter = class _WebSocketAdapter {
|
|
|
108
119
|
this.statusListeners.add(listener);
|
|
109
120
|
return () => this.statusListeners.delete(listener);
|
|
110
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* Subscribe to session-lost notifications. Fires when the proxy detects
|
|
124
|
+
* that the server-side session has terminated (host TCP drop, idle
|
|
125
|
+
* timeout, explicit destroy). Integrators use this to prompt a reconnect
|
|
126
|
+
* or swap to a "session expired" UI without relying on error-string
|
|
127
|
+
* matching.
|
|
128
|
+
*/
|
|
129
|
+
onSessionLost(listener) {
|
|
130
|
+
this.sessionLostListeners.add(listener);
|
|
131
|
+
return () => this.sessionLostListeners.delete(listener);
|
|
132
|
+
}
|
|
133
|
+
/** Subscribe to session-resumed notifications (fires after a successful `reattach`). */
|
|
134
|
+
onSessionResumed(listener) {
|
|
135
|
+
this.sessionResumedListeners.add(listener);
|
|
136
|
+
return () => this.sessionResumedListeners.delete(listener);
|
|
137
|
+
}
|
|
111
138
|
async getScreen() {
|
|
112
139
|
return this.screen;
|
|
113
140
|
}
|
|
@@ -123,6 +150,25 @@ var WebSocketAdapter = class _WebSocketAdapter {
|
|
|
123
150
|
async setCursor(row, col) {
|
|
124
151
|
return this.sendAndWaitForScreen({ type: "setCursor", row, col });
|
|
125
152
|
}
|
|
153
|
+
async readMdt(modifiedOnly = true) {
|
|
154
|
+
await this.ensureWebSocket();
|
|
155
|
+
return new Promise((resolve) => {
|
|
156
|
+
if (this.pendingMdtResolver) {
|
|
157
|
+
const old = this.pendingMdtResolver;
|
|
158
|
+
this.pendingMdtResolver = null;
|
|
159
|
+
old([]);
|
|
160
|
+
}
|
|
161
|
+
const timeout = setTimeout(() => {
|
|
162
|
+
this.pendingMdtResolver = null;
|
|
163
|
+
resolve([]);
|
|
164
|
+
}, 5e3);
|
|
165
|
+
this.pendingMdtResolver = (fields) => {
|
|
166
|
+
clearTimeout(timeout);
|
|
167
|
+
resolve(fields);
|
|
168
|
+
};
|
|
169
|
+
this.wsSend({ type: "readMdt", modifiedOnly });
|
|
170
|
+
});
|
|
171
|
+
}
|
|
126
172
|
async connect(config) {
|
|
127
173
|
await this.ensureWebSocket();
|
|
128
174
|
if (!config) {
|
|
@@ -236,6 +282,22 @@ var WebSocketAdapter = class _WebSocketAdapter {
|
|
|
236
282
|
}
|
|
237
283
|
break;
|
|
238
284
|
}
|
|
285
|
+
case "mdt": {
|
|
286
|
+
if (this.pendingMdtResolver) {
|
|
287
|
+
const resolver = this.pendingMdtResolver;
|
|
288
|
+
this.pendingMdtResolver = null;
|
|
289
|
+
resolver(msg.data?.fields ?? []);
|
|
290
|
+
}
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
case "session.lost": {
|
|
294
|
+
for (const listener of this.sessionLostListeners) listener(msg.sessionId, msg.status);
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
case "session.resumed": {
|
|
298
|
+
for (const listener of this.sessionResumedListeners) listener(msg.sessionId);
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
239
301
|
case "status":
|
|
240
302
|
this.status = msg.data;
|
|
241
303
|
for (const listener of this.statusListeners) listener(msg.data);
|
|
@@ -906,10 +968,15 @@ var RefreshIcon = ({ size = 12, className }) => /* @__PURE__ */ jsxs2("svg", { w
|
|
|
906
968
|
/* @__PURE__ */ jsx2("path", { d: "M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15" })
|
|
907
969
|
] });
|
|
908
970
|
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" }) });
|
|
909
|
-
var
|
|
910
|
-
/* @__PURE__ */ jsx2("
|
|
911
|
-
/* @__PURE__ */ jsx2("
|
|
912
|
-
/* @__PURE__ */ jsx2("line", { x1: "
|
|
971
|
+
var KeyboardIcon = ({ 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: [
|
|
972
|
+
/* @__PURE__ */ jsx2("rect", { x: "2", y: "6", width: "20", height: "12", rx: "2", ry: "2" }),
|
|
973
|
+
/* @__PURE__ */ jsx2("line", { x1: "6", y1: "10", x2: "6", y2: "10" }),
|
|
974
|
+
/* @__PURE__ */ jsx2("line", { x1: "10", y1: "10", x2: "10", y2: "10" }),
|
|
975
|
+
/* @__PURE__ */ jsx2("line", { x1: "14", y1: "10", x2: "14", y2: "10" }),
|
|
976
|
+
/* @__PURE__ */ jsx2("line", { x1: "18", y1: "10", x2: "18", y2: "10" }),
|
|
977
|
+
/* @__PURE__ */ jsx2("line", { x1: "6", y1: "14", x2: "6", y2: "14" }),
|
|
978
|
+
/* @__PURE__ */ jsx2("line", { x1: "18", y1: "14", x2: "18", y2: "14" }),
|
|
979
|
+
/* @__PURE__ */ jsx2("line", { x1: "10", y1: "14", x2: "14", y2: "14" })
|
|
913
980
|
] });
|
|
914
981
|
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" }) });
|
|
915
982
|
|
|
@@ -1034,8 +1101,201 @@ function InlineSignIn({ defaultProtocol, loading: externalLoading, error, onConn
|
|
|
1034
1101
|
] });
|
|
1035
1102
|
}
|
|
1036
1103
|
|
|
1104
|
+
// src/utils/attribute.ts
|
|
1105
|
+
function decodeAttrByte(byte) {
|
|
1106
|
+
const type = byte & 7;
|
|
1107
|
+
const colorGroup = byte & 24;
|
|
1108
|
+
const highIntensity = (byte & 2) !== 0;
|
|
1109
|
+
let color = "green";
|
|
1110
|
+
switch (colorGroup) {
|
|
1111
|
+
case 0:
|
|
1112
|
+
color = highIntensity ? "white" : "green";
|
|
1113
|
+
break;
|
|
1114
|
+
case 8:
|
|
1115
|
+
color = "red";
|
|
1116
|
+
break;
|
|
1117
|
+
// red stays red at HI
|
|
1118
|
+
case 16:
|
|
1119
|
+
color = highIntensity ? "yellow" : "turquoise";
|
|
1120
|
+
break;
|
|
1121
|
+
case 24:
|
|
1122
|
+
color = highIntensity ? "blue" : "pink";
|
|
1123
|
+
break;
|
|
1124
|
+
}
|
|
1125
|
+
const nonDisplay = type === 7;
|
|
1126
|
+
const underscore = type === 4 || type === 5 || type === 6;
|
|
1127
|
+
const reverse = type === 5;
|
|
1128
|
+
const columnSeparator = type === 1 || type === 3;
|
|
1129
|
+
const hiTypeBit = type === 2 || type === 3 || type === 6;
|
|
1130
|
+
return {
|
|
1131
|
+
color,
|
|
1132
|
+
highIntensity: highIntensity || hiTypeBit,
|
|
1133
|
+
underscore,
|
|
1134
|
+
reverse,
|
|
1135
|
+
blink: false,
|
|
1136
|
+
// 5250 has no base-attr blink; blink comes from WEA highlight
|
|
1137
|
+
nonDisplay,
|
|
1138
|
+
columnSeparator
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
function decodeExtColor(byte) {
|
|
1142
|
+
switch (byte & 15) {
|
|
1143
|
+
case 0:
|
|
1144
|
+
return "green";
|
|
1145
|
+
case 1:
|
|
1146
|
+
return "blue";
|
|
1147
|
+
case 2:
|
|
1148
|
+
return "red";
|
|
1149
|
+
case 3:
|
|
1150
|
+
return "pink";
|
|
1151
|
+
case 4:
|
|
1152
|
+
return "turquoise";
|
|
1153
|
+
case 5:
|
|
1154
|
+
return "yellow";
|
|
1155
|
+
case 6:
|
|
1156
|
+
return "white";
|
|
1157
|
+
case 7:
|
|
1158
|
+
return void 0;
|
|
1159
|
+
// default — inherit
|
|
1160
|
+
// Reverse-image pairs: distinct tokens so themes can override
|
|
1161
|
+
case 8:
|
|
1162
|
+
return "green";
|
|
1163
|
+
// reverse green → typically same color, background flip
|
|
1164
|
+
case 9:
|
|
1165
|
+
return "blue";
|
|
1166
|
+
case 10:
|
|
1167
|
+
return "red";
|
|
1168
|
+
case 11:
|
|
1169
|
+
return "pink";
|
|
1170
|
+
case 12:
|
|
1171
|
+
return "turquoise";
|
|
1172
|
+
case 13:
|
|
1173
|
+
return "yellow";
|
|
1174
|
+
case 14:
|
|
1175
|
+
return "white";
|
|
1176
|
+
case 15:
|
|
1177
|
+
return void 0;
|
|
1178
|
+
default:
|
|
1179
|
+
return void 0;
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
function extColorIsReverse(byte) {
|
|
1183
|
+
const v = byte & 15;
|
|
1184
|
+
return v >= 8 && v <= 14;
|
|
1185
|
+
}
|
|
1186
|
+
function decodeExtHighlight(byte) {
|
|
1187
|
+
return {
|
|
1188
|
+
underscore: (byte & 1) !== 0,
|
|
1189
|
+
reverse: (byte & 2) !== 0,
|
|
1190
|
+
blink: (byte & 4) !== 0,
|
|
1191
|
+
columnSeparator: (byte & 8) !== 0
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
function cssVarForColor(color) {
|
|
1195
|
+
if (!color) return "var(--gs-green, #10b981)";
|
|
1196
|
+
return `var(--gs-${color}, var(--gs-green, #10b981))`;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
// src/utils/validation.ts
|
|
1200
|
+
function filterFieldChar(field, ch, isLastPosition = false) {
|
|
1201
|
+
let out = ch;
|
|
1202
|
+
if (field.monocase) {
|
|
1203
|
+
out = out.toUpperCase();
|
|
1204
|
+
}
|
|
1205
|
+
const cp = out.charCodeAt(0);
|
|
1206
|
+
const isAsciiLetter = cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122;
|
|
1207
|
+
const isAsciiDigit = cp >= 48 && cp <= 57;
|
|
1208
|
+
switch (field.shift_type) {
|
|
1209
|
+
case "alpha_only": {
|
|
1210
|
+
if (isAsciiLetter || out === "," || out === "-" || out === "." || out === " ") return out;
|
|
1211
|
+
return null;
|
|
1212
|
+
}
|
|
1213
|
+
case "numeric_only": {
|
|
1214
|
+
if (isAsciiDigit || out === "-" || out === "+" || out === "," || out === "." || out === " ") return out;
|
|
1215
|
+
return null;
|
|
1216
|
+
}
|
|
1217
|
+
case "digits_only": {
|
|
1218
|
+
if (isAsciiDigit) return out;
|
|
1219
|
+
return null;
|
|
1220
|
+
}
|
|
1221
|
+
case "signed_num": {
|
|
1222
|
+
if (isAsciiDigit) return out;
|
|
1223
|
+
if (isLastPosition && (out === "-" || out === "+")) return out;
|
|
1224
|
+
return null;
|
|
1225
|
+
}
|
|
1226
|
+
case "katakana": {
|
|
1227
|
+
if (cp >= 65377 && cp <= 65439 || isAsciiDigit || out === " ") return out;
|
|
1228
|
+
return null;
|
|
1229
|
+
}
|
|
1230
|
+
case "alpha":
|
|
1231
|
+
case "numeric_shift":
|
|
1232
|
+
case "io":
|
|
1233
|
+
case void 0:
|
|
1234
|
+
default:
|
|
1235
|
+
return out;
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
function filterFieldInput(field, input, startOffset) {
|
|
1239
|
+
let out = "";
|
|
1240
|
+
let rejected = false;
|
|
1241
|
+
for (let i = 0; i < input.length; i++) {
|
|
1242
|
+
const pos = startOffset + i;
|
|
1243
|
+
const isLast = pos === field.length - 1;
|
|
1244
|
+
const transformed = filterFieldChar(field, input[i], isLast);
|
|
1245
|
+
if (transformed === null) {
|
|
1246
|
+
rejected = true;
|
|
1247
|
+
} else {
|
|
1248
|
+
out += transformed;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
return { out, rejected };
|
|
1252
|
+
}
|
|
1253
|
+
function validateMod10(value) {
|
|
1254
|
+
const digits = value.replace(/\D/g, "");
|
|
1255
|
+
if (digits.length < 2) return true;
|
|
1256
|
+
let sum = 0;
|
|
1257
|
+
let alt = false;
|
|
1258
|
+
for (let i = digits.length - 1; i >= 0; i--) {
|
|
1259
|
+
let d = digits.charCodeAt(i) - 48;
|
|
1260
|
+
if (alt) {
|
|
1261
|
+
d *= 2;
|
|
1262
|
+
if (d > 9) d -= 9;
|
|
1263
|
+
}
|
|
1264
|
+
sum += d;
|
|
1265
|
+
alt = !alt;
|
|
1266
|
+
}
|
|
1267
|
+
return sum % 10 === 0;
|
|
1268
|
+
}
|
|
1269
|
+
function validateMod11(value) {
|
|
1270
|
+
const digits = value.replace(/\D/g, "");
|
|
1271
|
+
if (digits.length < 2) return true;
|
|
1272
|
+
const body = digits.slice(0, -1);
|
|
1273
|
+
const check = digits.charCodeAt(digits.length - 1) - 48;
|
|
1274
|
+
let sum = 0;
|
|
1275
|
+
let weight = 2;
|
|
1276
|
+
for (let i = body.length - 1; i >= 0; i--) {
|
|
1277
|
+
sum += (body.charCodeAt(i) - 48) * weight;
|
|
1278
|
+
weight++;
|
|
1279
|
+
if (weight > 7) weight = 2;
|
|
1280
|
+
}
|
|
1281
|
+
const remainder = sum % 11;
|
|
1282
|
+
const expected = remainder === 0 ? 0 : 11 - remainder;
|
|
1283
|
+
return expected === check;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1037
1286
|
// src/components/GreenScreenTerminal.tsx
|
|
1038
1287
|
import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1288
|
+
function aidByteToKeyName(aid) {
|
|
1289
|
+
if (aid === 241) return "ENTER";
|
|
1290
|
+
if (aid === 243) return "HELP";
|
|
1291
|
+
if (aid === 244) return "PAGEUP";
|
|
1292
|
+
if (aid === 245) return "PAGEDOWN";
|
|
1293
|
+
if (aid === 246) return "PRINT";
|
|
1294
|
+
if (aid === 189) return "CLEAR";
|
|
1295
|
+
if (aid >= 49 && aid <= 60) return `F${aid - 48}`;
|
|
1296
|
+
if (aid >= 177 && aid <= 188) return `F${aid - 176 + 12}`;
|
|
1297
|
+
return null;
|
|
1298
|
+
}
|
|
1039
1299
|
var noopResult = { success: false, error: "No adapter configured" };
|
|
1040
1300
|
var noopAdapter = {
|
|
1041
1301
|
getScreen: async () => null,
|
|
@@ -1066,14 +1326,16 @@ function GreenScreenTerminal({
|
|
|
1066
1326
|
inlineSignIn = true,
|
|
1067
1327
|
defaultProtocol: signInDefaultProtocol,
|
|
1068
1328
|
onSignIn,
|
|
1069
|
-
autoSignedIn,
|
|
1070
1329
|
autoFocusDisabled = false,
|
|
1071
1330
|
bootLoader,
|
|
1331
|
+
bootLoaderReady,
|
|
1072
1332
|
headerRight,
|
|
1333
|
+
statusActions,
|
|
1073
1334
|
overlay,
|
|
1074
1335
|
onNotification,
|
|
1075
1336
|
onScreenChange,
|
|
1076
1337
|
onMinimize,
|
|
1338
|
+
showShortcutsButton = true,
|
|
1077
1339
|
className,
|
|
1078
1340
|
style
|
|
1079
1341
|
}) {
|
|
@@ -1118,22 +1380,17 @@ function GreenScreenTerminal({
|
|
|
1118
1380
|
const sendText = useCallback3(async (text) => _sendText(text), [_sendText]);
|
|
1119
1381
|
const sendKey = useCallback3(async (key) => _sendKey(key), [_sendKey]);
|
|
1120
1382
|
const [optimisticEdits, setOptimisticEdits] = useState5([]);
|
|
1121
|
-
const
|
|
1383
|
+
const prevScreenContentForEdits = useRef4(void 0);
|
|
1122
1384
|
useEffect4(() => {
|
|
1123
|
-
|
|
1124
|
-
|
|
1385
|
+
const content = rawScreenData?.content;
|
|
1386
|
+
if (content && content !== prevScreenContentForEdits.current) {
|
|
1387
|
+
prevScreenContentForEdits.current = content;
|
|
1125
1388
|
setOptimisticEdits([]);
|
|
1126
1389
|
}
|
|
1127
|
-
}, [rawScreenData?.
|
|
1390
|
+
}, [rawScreenData?.content]);
|
|
1128
1391
|
const [inputText, setInputText] = useState5("");
|
|
1129
1392
|
const [isFocused, setIsFocused] = useState5(false);
|
|
1130
|
-
const [showSignInHint, setShowSignInHint] = useState5(false);
|
|
1131
1393
|
const [showShortcuts, setShowShortcuts] = useState5(false);
|
|
1132
|
-
const prevAutoSignedIn = useRef4(false);
|
|
1133
|
-
useEffect4(() => {
|
|
1134
|
-
if (autoSignedIn && !prevAutoSignedIn.current) setShowSignInHint(true);
|
|
1135
|
-
prevAutoSignedIn.current = !!autoSignedIn;
|
|
1136
|
-
}, [autoSignedIn]);
|
|
1137
1394
|
const terminalRef = useRef4(null);
|
|
1138
1395
|
const inputRef = useRef4(null);
|
|
1139
1396
|
const [syncedCursor, setSyncedCursor] = useState5(null);
|
|
@@ -1143,7 +1400,6 @@ function GreenScreenTerminal({
|
|
|
1143
1400
|
if (prevRawContentRef.current && newContent && newContent !== prevRawContentRef.current) {
|
|
1144
1401
|
setSyncedCursor(null);
|
|
1145
1402
|
setInputText("");
|
|
1146
|
-
if (showSignInHint) setShowSignInHint(false);
|
|
1147
1403
|
}
|
|
1148
1404
|
prevRawContentRef.current = newContent;
|
|
1149
1405
|
}, [rawScreenData?.content]);
|
|
@@ -1206,11 +1462,9 @@ function GreenScreenTerminal({
|
|
|
1206
1462
|
const newAdapter = new RestAdapter({ baseUrl: `http://${config.host}${port}` });
|
|
1207
1463
|
setInternalAdapter(newAdapter);
|
|
1208
1464
|
await newAdapter.connect(config);
|
|
1209
|
-
if (config.username && config.password) setShowSignInHint(true);
|
|
1210
1465
|
return;
|
|
1211
1466
|
}
|
|
1212
1467
|
await connect(config);
|
|
1213
|
-
if (config.username && config.password) setShowSignInHint(true);
|
|
1214
1468
|
} catch (err) {
|
|
1215
1469
|
setSignInError(err instanceof Error ? err.message : String(err));
|
|
1216
1470
|
setConnecting(false);
|
|
@@ -1222,13 +1476,15 @@ function GreenScreenTerminal({
|
|
|
1222
1476
|
const [showBootLoader, setShowBootLoader] = useState5(bootLoader !== false);
|
|
1223
1477
|
const [bootFadingOut, setBootFadingOut] = useState5(false);
|
|
1224
1478
|
useEffect4(() => {
|
|
1225
|
-
if (
|
|
1479
|
+
if (!showBootLoader) return;
|
|
1480
|
+
const shouldDismiss = bootLoaderReady !== void 0 ? bootLoaderReady : !!screenData?.content;
|
|
1481
|
+
if (shouldDismiss) {
|
|
1226
1482
|
setBootFadingOut(true);
|
|
1227
1483
|
setShowBootLoader(false);
|
|
1228
1484
|
const timer = setTimeout(() => setBootFadingOut(false), 400);
|
|
1229
1485
|
return () => clearTimeout(timer);
|
|
1230
1486
|
}
|
|
1231
|
-
}, [screenData?.content, showBootLoader]);
|
|
1487
|
+
}, [screenData?.content, showBootLoader, bootLoaderReady]);
|
|
1232
1488
|
const FOCUS_STORAGE_KEY = "gs-terminal-focused";
|
|
1233
1489
|
useEffect4(() => {
|
|
1234
1490
|
if (!autoFocusDisabled && !readOnly) {
|
|
@@ -1296,13 +1552,25 @@ function GreenScreenTerminal({
|
|
|
1296
1552
|
const clickedRow = Math.floor(y / ROW_HEIGHT);
|
|
1297
1553
|
const clickedCol = Math.floor(x / charWidth);
|
|
1298
1554
|
if (clickedRow < 0 || clickedRow >= (screenData.rows || 24) || clickedCol < 0 || clickedCol >= (screenData.cols || 80)) return;
|
|
1555
|
+
const clickedField = screenData.fields.find(
|
|
1556
|
+
(f) => f.row === clickedRow && clickedCol >= f.col && clickedCol < f.col + f.length
|
|
1557
|
+
);
|
|
1558
|
+
const ptrAid = clickedField && clickedField.pointer_aid;
|
|
1559
|
+
if (ptrAid) {
|
|
1560
|
+
const keyName = aidByteToKeyName(ptrAid);
|
|
1561
|
+
if (keyName) {
|
|
1562
|
+
setSyncedCursor({ row: clickedRow, col: clickedCol });
|
|
1563
|
+
adapter.setCursor?.(clickedRow, clickedCol).then(() => sendKey(keyName));
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1299
1567
|
setSyncedCursor({ row: clickedRow, col: clickedCol });
|
|
1300
1568
|
adapter.setCursor?.(clickedRow, clickedCol).then((r) => {
|
|
1301
1569
|
if (r?.cursor_row !== void 0) {
|
|
1302
1570
|
setSyncedCursor({ row: r.cursor_row, col: r.cursor_col });
|
|
1303
1571
|
}
|
|
1304
1572
|
});
|
|
1305
|
-
}, [readOnly, screenData, adapter]);
|
|
1573
|
+
}, [readOnly, screenData, adapter, sendKey]);
|
|
1306
1574
|
const getCurrentField = useCallback3(() => {
|
|
1307
1575
|
const fields = screenData?.fields || [];
|
|
1308
1576
|
const cursorRow = syncedCursor?.row ?? screenData?.cursor_row ?? 0;
|
|
@@ -1312,6 +1580,31 @@ function GreenScreenTerminal({
|
|
|
1312
1580
|
}
|
|
1313
1581
|
return null;
|
|
1314
1582
|
}, [screenData, syncedCursor]);
|
|
1583
|
+
const readFieldValue = useCallback3((field) => {
|
|
1584
|
+
if (!screenData?.content) return "";
|
|
1585
|
+
const lines = screenData.content.split("\n");
|
|
1586
|
+
const line = lines[field.row] || "";
|
|
1587
|
+
return line.substring(field.col, field.col + field.length).replace(/\s+$/, "");
|
|
1588
|
+
}, [screenData?.content]);
|
|
1589
|
+
const [validationError, setValidationError] = useState5(null);
|
|
1590
|
+
const runSelfCheck = useCallback3(() => {
|
|
1591
|
+
const fields = screenData?.fields || [];
|
|
1592
|
+
for (const f of fields) {
|
|
1593
|
+
if (!f.is_input) continue;
|
|
1594
|
+
const val = readFieldValue(f);
|
|
1595
|
+
if (!val) continue;
|
|
1596
|
+
if (f.self_check_mod10 && !validateMod10(val)) {
|
|
1597
|
+
setValidationError(`Invalid check digit (MOD10) in field at row ${f.row + 1}, col ${f.col + 1}`);
|
|
1598
|
+
return false;
|
|
1599
|
+
}
|
|
1600
|
+
if (f.self_check_mod11 && !validateMod11(val)) {
|
|
1601
|
+
setValidationError(`Invalid check digit (MOD11) in field at row ${f.row + 1}, col ${f.col + 1}`);
|
|
1602
|
+
return false;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
setValidationError(null);
|
|
1606
|
+
return true;
|
|
1607
|
+
}, [screenData?.fields, readFieldValue]);
|
|
1315
1608
|
const handleKeyDown = async (e) => {
|
|
1316
1609
|
if (readOnly) {
|
|
1317
1610
|
e.preventDefault();
|
|
@@ -1350,18 +1643,19 @@ function GreenScreenTerminal({
|
|
|
1350
1643
|
End: "END",
|
|
1351
1644
|
Insert: "INSERT"
|
|
1352
1645
|
};
|
|
1353
|
-
if (
|
|
1646
|
+
if (/^F([1-9]|1[0-9]|2[0-4])$/.test(e.key)) {
|
|
1354
1647
|
e.preventDefault();
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
return;
|
|
1360
|
-
}
|
|
1648
|
+
if (!runSelfCheck()) return;
|
|
1649
|
+
const kr = await sendKey(e.key);
|
|
1650
|
+
if (kr.cursor_row !== void 0) setSyncedCursor({ row: kr.cursor_row, col: kr.cursor_col });
|
|
1651
|
+
return;
|
|
1361
1652
|
}
|
|
1362
1653
|
if (keyMap[e.key]) {
|
|
1363
1654
|
e.preventDefault();
|
|
1364
|
-
const
|
|
1655
|
+
const k = keyMap[e.key];
|
|
1656
|
+
const isSubmit = k === "ENTER" || k === "PAGEUP" || k === "PAGEDOWN";
|
|
1657
|
+
if (isSubmit && !runSelfCheck()) return;
|
|
1658
|
+
const kr = await sendKey(k);
|
|
1365
1659
|
if (kr.cursor_row !== void 0) setSyncedCursor({ row: kr.cursor_row, col: kr.cursor_col });
|
|
1366
1660
|
}
|
|
1367
1661
|
};
|
|
@@ -1372,18 +1666,32 @@ function GreenScreenTerminal({
|
|
|
1372
1666
|
}
|
|
1373
1667
|
const newText = e.target.value;
|
|
1374
1668
|
if (newText.length > inputText.length) {
|
|
1375
|
-
|
|
1376
|
-
const
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1669
|
+
let newChars = newText.substring(inputText.length);
|
|
1670
|
+
const curField = getCurrentField();
|
|
1671
|
+
if (curField && (curField.shift_type || curField.monocase)) {
|
|
1672
|
+
const curColAbs = syncedCursor?.col ?? screenData?.cursor_col ?? 0;
|
|
1673
|
+
const startOffset = Math.max(0, curColAbs - curField.col);
|
|
1674
|
+
const { out, rejected } = filterFieldInput(curField, newChars, startOffset);
|
|
1675
|
+
if (rejected) {
|
|
1676
|
+
const what = curField.shift_type === "digits_only" || curField.shift_type === "numeric_only" || curField.shift_type === "signed_num" ? "digits only" : curField.shift_type === "alpha_only" ? "letters only" : curField.shift_type === "katakana" ? "katakana only" : "character not allowed";
|
|
1677
|
+
setValidationError(`Field accepts ${what}`);
|
|
1678
|
+
setTimeout(() => setValidationError(null), 1500);
|
|
1679
|
+
}
|
|
1680
|
+
newChars = out;
|
|
1681
|
+
}
|
|
1682
|
+
if (newChars.length > 0) {
|
|
1683
|
+
const curRow = syncedCursor?.row ?? screenData?.cursor_row ?? 0;
|
|
1684
|
+
const curCol = syncedCursor?.col ?? screenData?.cursor_col ?? 0;
|
|
1685
|
+
const edits = [];
|
|
1686
|
+
for (let i = 0; i < newChars.length; i++) {
|
|
1687
|
+
edits.push({ row: curRow, col: curCol + i, ch: newChars[i] });
|
|
1688
|
+
}
|
|
1689
|
+
setOptimisticEdits((prev) => [...prev, ...edits]);
|
|
1690
|
+
setSyncedCursor({ row: curRow, col: curCol + newChars.length });
|
|
1691
|
+
sendText(newChars).then((r) => {
|
|
1692
|
+
if (r.cursor_row !== void 0) setSyncedCursor({ row: r.cursor_row, col: r.cursor_col });
|
|
1693
|
+
});
|
|
1381
1694
|
}
|
|
1382
|
-
setOptimisticEdits((prev) => [...prev, ...edits]);
|
|
1383
|
-
setSyncedCursor({ row: curRow, col: curCol + newChars.length });
|
|
1384
|
-
sendText(newChars).then((r) => {
|
|
1385
|
-
if (r.cursor_row !== void 0) setSyncedCursor({ row: r.cursor_row, col: r.cursor_col });
|
|
1386
|
-
});
|
|
1387
1695
|
}
|
|
1388
1696
|
setInputText("");
|
|
1389
1697
|
e.target.value = "";
|
|
@@ -1415,41 +1723,156 @@ function GreenScreenTerminal({
|
|
|
1415
1723
|
if (lastIndex < text.length) segments.push(/* @__PURE__ */ jsx4("span", { children: text.substring(lastIndex) }, `${keyPrefix}-e`));
|
|
1416
1724
|
return segments.length > 0 ? /* @__PURE__ */ jsx4(Fragment, { children: segments }) : /* @__PURE__ */ jsx4(Fragment, { children: text });
|
|
1417
1725
|
}, []);
|
|
1418
|
-
const renderRowWithFields = useCallback3((line, rowIndex, fields) => {
|
|
1726
|
+
const renderRowWithFields = useCallback3((line, rowIndex, fields, cursorRow, cursorCol) => {
|
|
1419
1727
|
const inputFields = fields.filter((f) => f.row === rowIndex && f.is_input);
|
|
1420
1728
|
const highlightedFields = fields.filter((f) => f.row === rowIndex && f.is_protected && f.is_highlighted);
|
|
1421
1729
|
const reverseFields = fields.filter((f) => f.row === rowIndex && f.is_protected && f.is_reverse);
|
|
1422
1730
|
const colorFields = fields.filter((f) => f.row === rowIndex && f.is_protected && f.color && !f.is_highlighted && !f.is_reverse);
|
|
1423
1731
|
const allRowFields = [...inputFields, ...highlightedFields, ...reverseFields, ...colorFields];
|
|
1424
|
-
|
|
1732
|
+
const cols = screenData?.cols || profile.defaultCols;
|
|
1733
|
+
const extAttrs = screenData?.ext_attrs;
|
|
1734
|
+
const dbcsCont = screenData?.dbcs_cont;
|
|
1735
|
+
const dbcsContSet = dbcsCont && dbcsCont.length > 0 ? new Set(dbcsCont) : null;
|
|
1736
|
+
const hasExtOnRow = !!extAttrs && (() => {
|
|
1737
|
+
for (let c = 0; c < cols; c++) {
|
|
1738
|
+
if (extAttrs[rowIndex * cols + c]) return true;
|
|
1739
|
+
}
|
|
1740
|
+
return false;
|
|
1741
|
+
})();
|
|
1742
|
+
const hasDbcsOnRow = !!dbcsContSet && (() => {
|
|
1743
|
+
for (let c = 0; c < cols; c++) {
|
|
1744
|
+
if (dbcsContSet.has(rowIndex * cols + c)) return true;
|
|
1745
|
+
}
|
|
1746
|
+
return false;
|
|
1747
|
+
})();
|
|
1748
|
+
if (allRowFields.length === 0 && !hasExtOnRow && !hasDbcsOnRow) return /* @__PURE__ */ jsx4("span", { children: line });
|
|
1425
1749
|
const sorted = [...allRowFields].sort((a, b) => a.col - b.col);
|
|
1426
1750
|
const segs = [];
|
|
1427
1751
|
let lastEnd = 0;
|
|
1428
|
-
const
|
|
1752
|
+
const renderPlainRun = (runStart, runEnd, keyPrefix) => {
|
|
1753
|
+
if (runStart >= runEnd) return;
|
|
1754
|
+
if (!hasExtOnRow && !hasDbcsOnRow) {
|
|
1755
|
+
segs.push(/* @__PURE__ */ jsx4("span", { children: line.substring(runStart, runEnd) }, keyPrefix));
|
|
1756
|
+
return;
|
|
1757
|
+
}
|
|
1758
|
+
let pos = runStart;
|
|
1759
|
+
while (pos < runEnd) {
|
|
1760
|
+
const addr = rowIndex * cols + pos;
|
|
1761
|
+
const ext = extAttrs?.[addr];
|
|
1762
|
+
const isContCell = dbcsContSet?.has(addr);
|
|
1763
|
+
if (isContCell) {
|
|
1764
|
+
segs.push(
|
|
1765
|
+
/* @__PURE__ */ jsx4(
|
|
1766
|
+
"span",
|
|
1767
|
+
{
|
|
1768
|
+
style: { display: "inline-block", width: "1ch" },
|
|
1769
|
+
"aria-hidden": "true"
|
|
1770
|
+
},
|
|
1771
|
+
`${keyPrefix}-dc${pos}`
|
|
1772
|
+
)
|
|
1773
|
+
);
|
|
1774
|
+
pos++;
|
|
1775
|
+
continue;
|
|
1776
|
+
}
|
|
1777
|
+
if (ext) {
|
|
1778
|
+
const color = ext.color !== void 0 ? decodeExtColor(ext.color) : void 0;
|
|
1779
|
+
const colorReverse = ext.color !== void 0 && extColorIsReverse(ext.color);
|
|
1780
|
+
const hl = ext.highlight !== void 0 ? decodeExtHighlight(ext.highlight) : void 0;
|
|
1781
|
+
const style2 = {};
|
|
1782
|
+
if (color) style2.color = cssVarForColor(color);
|
|
1783
|
+
if (colorReverse) {
|
|
1784
|
+
style2.background = cssVarForColor(color);
|
|
1785
|
+
style2.color = "#000";
|
|
1786
|
+
}
|
|
1787
|
+
if (hl?.reverse) {
|
|
1788
|
+
style2.background = "currentColor";
|
|
1789
|
+
style2.color = "#000";
|
|
1790
|
+
}
|
|
1791
|
+
if (hl?.underscore) style2.textDecoration = "underline";
|
|
1792
|
+
if (hl?.blink) style2.animation = "gs-blink 1s steps(2, start) infinite";
|
|
1793
|
+
segs.push(
|
|
1794
|
+
/* @__PURE__ */ jsx4("span", { style: style2, children: line[pos] }, `${keyPrefix}-x${pos}`)
|
|
1795
|
+
);
|
|
1796
|
+
pos++;
|
|
1797
|
+
continue;
|
|
1798
|
+
}
|
|
1799
|
+
let runEndPlain = pos + 1;
|
|
1800
|
+
while (runEndPlain < runEnd) {
|
|
1801
|
+
const a = rowIndex * cols + runEndPlain;
|
|
1802
|
+
if (extAttrs?.[a] || dbcsContSet?.has(a)) break;
|
|
1803
|
+
runEndPlain++;
|
|
1804
|
+
}
|
|
1805
|
+
segs.push(
|
|
1806
|
+
/* @__PURE__ */ jsx4("span", { children: line.substring(pos, runEndPlain) }, `${keyPrefix}-p${pos}`)
|
|
1807
|
+
);
|
|
1808
|
+
pos = runEndPlain;
|
|
1809
|
+
}
|
|
1810
|
+
};
|
|
1429
1811
|
sorted.forEach((field, idx) => {
|
|
1430
1812
|
const fs = field.col;
|
|
1431
1813
|
const fe = Math.min(field.col + field.length, cols);
|
|
1432
|
-
if (fs > lastEnd)
|
|
1814
|
+
if (fs > lastEnd) renderPlainRun(lastEnd, fs, `t${idx}`);
|
|
1433
1815
|
const fc = line.substring(fs, fe);
|
|
1434
|
-
const
|
|
1816
|
+
const cursorInField = cursorRow === field.row && cursorCol >= fs && cursorCol < field.col + field.length;
|
|
1817
|
+
const entryAttr = cursorInField && field.highlight_entry_attr !== void 0 ? decodeAttrByte(field.highlight_entry_attr) : null;
|
|
1818
|
+
const baseColor = entryAttr?.color ?? field.color;
|
|
1819
|
+
const colorVar = baseColor ? cssVarForColor(baseColor) : void 0;
|
|
1820
|
+
const fieldStyle = {};
|
|
1821
|
+
if (colorVar) fieldStyle.color = colorVar;
|
|
1822
|
+
if (entryAttr?.reverse) {
|
|
1823
|
+
fieldStyle.background = "currentColor";
|
|
1824
|
+
fieldStyle.color = "#000";
|
|
1825
|
+
}
|
|
1826
|
+
if (entryAttr?.underscore) fieldStyle.textDecoration = "underline";
|
|
1827
|
+
if (entryAttr?.highIntensity) fieldStyle.fontWeight = "bold";
|
|
1828
|
+
const isPassword = field.is_non_display;
|
|
1829
|
+
const displayText = isPassword ? " ".repeat(fc.length) : fc;
|
|
1435
1830
|
if (field.is_input) {
|
|
1436
1831
|
const fieldWidth = Math.min(field.length, cols - fs);
|
|
1437
|
-
const fieldClass =
|
|
1438
|
-
|
|
1832
|
+
const fieldClass = field.is_underscored ? "gs-input-field" : void 0;
|
|
1833
|
+
const extra = [];
|
|
1834
|
+
if (field.is_dbcs) extra.push("gs-dbcs-field");
|
|
1835
|
+
if (cursorInField) extra.push("gs-field-active");
|
|
1836
|
+
const composedClass = [fieldClass, ...extra].filter(Boolean).join(" ") || void 0;
|
|
1837
|
+
segs.push(
|
|
1838
|
+
/* @__PURE__ */ jsx4(
|
|
1839
|
+
"span",
|
|
1840
|
+
{
|
|
1841
|
+
className: composedClass,
|
|
1842
|
+
style: {
|
|
1843
|
+
display: "inline-block",
|
|
1844
|
+
width: `${fieldWidth}ch`,
|
|
1845
|
+
overflow: "hidden",
|
|
1846
|
+
...fieldStyle
|
|
1847
|
+
},
|
|
1848
|
+
children: displayText
|
|
1849
|
+
},
|
|
1850
|
+
`f${idx}`
|
|
1851
|
+
)
|
|
1852
|
+
);
|
|
1439
1853
|
} else if (field.is_reverse) {
|
|
1440
|
-
segs.push(
|
|
1441
|
-
|
|
1442
|
-
|
|
1854
|
+
segs.push(
|
|
1855
|
+
/* @__PURE__ */ jsx4(
|
|
1856
|
+
"span",
|
|
1857
|
+
{
|
|
1858
|
+
style: { color: colorVar || "var(--gs-red, #FF5555)", fontWeight: "bold", ...fieldStyle },
|
|
1859
|
+
children: displayText
|
|
1860
|
+
},
|
|
1861
|
+
`v${idx}`
|
|
1862
|
+
)
|
|
1863
|
+
);
|
|
1864
|
+
} else if (colorVar || entryAttr) {
|
|
1865
|
+
segs.push(/* @__PURE__ */ jsx4("span", { style: fieldStyle, children: displayText }, `h${idx}`));
|
|
1443
1866
|
} else if (field.is_highlighted) {
|
|
1444
|
-
segs.push(/* @__PURE__ */ jsx4("span", { style: { color: "var(--gs-white, #FFFFFF)" }, children:
|
|
1867
|
+
segs.push(/* @__PURE__ */ jsx4("span", { style: { color: "var(--gs-white, #FFFFFF)" }, children: displayText }, `h${idx}`));
|
|
1445
1868
|
} else {
|
|
1446
|
-
segs.push(/* @__PURE__ */ jsx4("span", { children:
|
|
1869
|
+
segs.push(/* @__PURE__ */ jsx4("span", { children: displayText }, `h${idx}`));
|
|
1447
1870
|
}
|
|
1448
1871
|
lastEnd = fe;
|
|
1449
1872
|
});
|
|
1450
|
-
if (lastEnd < line.length)
|
|
1873
|
+
if (lastEnd < line.length) renderPlainRun(lastEnd, line.length, "te");
|
|
1451
1874
|
return /* @__PURE__ */ jsx4(Fragment, { children: segs });
|
|
1452
|
-
}, [renderTextWithUnderlines, screenData
|
|
1875
|
+
}, [renderTextWithUnderlines, screenData, profile.defaultCols]);
|
|
1453
1876
|
const renderScreen = () => {
|
|
1454
1877
|
if (showBootLoader && !screenData?.content) {
|
|
1455
1878
|
if (bootLoader === false) return null;
|
|
@@ -1463,10 +1886,10 @@ function GreenScreenTerminal({
|
|
|
1463
1886
|
}
|
|
1464
1887
|
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: [
|
|
1465
1888
|
/* @__PURE__ */ jsx4("div", { style: { color: "#808080", marginBottom: "12px" }, children: /* @__PURE__ */ jsx4(TerminalIcon, { size: 40 }) }),
|
|
1466
|
-
/* @__PURE__ */ jsx4("p", { style: { fontFamily: "var(--gs-font)", fontSize: "12px", color: "#808080" }, children: connStatus?.connected ? "Waiting for screen data..." : "Not connected" }),
|
|
1889
|
+
/* @__PURE__ */ jsx4("p", { style: { fontFamily: "var(--gs-font)", fontSize: "12px", color: connStatus?.status === "connecting" ? "#f59e0b" : connStatus?.status === "loading" ? "#94a3b8" : "#808080" }, children: connStatus?.connected ? "Waiting for screen data..." : connStatus?.status === "connecting" ? "Connecting..." : connStatus?.status === "loading" ? "Loading..." : "Not connected" }),
|
|
1467
1890
|
!connStatus?.connected && isUsingDefaultAdapter && /* @__PURE__ */ jsxs4("p", { style: { fontFamily: "var(--gs-font)", fontSize: "11px", color: "#606060", marginTop: "8px" }, children: [
|
|
1468
1891
|
"Start the proxy: ",
|
|
1469
|
-
/* @__PURE__ */ jsx4("code", { style: { color: "#10b981" }, children: "npx green-screen-proxy
|
|
1892
|
+
/* @__PURE__ */ jsx4("code", { style: { color: "#10b981" }, children: "npx green-screen-proxy" })
|
|
1470
1893
|
] })
|
|
1471
1894
|
] }) });
|
|
1472
1895
|
}
|
|
@@ -1484,7 +1907,7 @@ function GreenScreenTerminal({
|
|
|
1484
1907
|
const cursor = getCursorPos();
|
|
1485
1908
|
const hasCursor = screenData.cursor_row !== void 0 && screenData.cursor_col !== void 0;
|
|
1486
1909
|
const cursorInInputField = hasCursor && fields.some(
|
|
1487
|
-
(f) => f.is_input && f.row === cursor.row && cursor.col >= f.col && cursor.col < f.col + f.length
|
|
1910
|
+
(f) => f.is_input && !f.is_non_display && f.row === cursor.row && cursor.col >= f.col && cursor.col < f.col + f.length
|
|
1488
1911
|
);
|
|
1489
1912
|
return /* @__PURE__ */ jsxs4("div", { style: { fontFamily: "var(--gs-font)", fontSize: "13px", position: "relative", width: `${cols}ch` }, children: [
|
|
1490
1913
|
rows.map((line, index) => {
|
|
@@ -1499,11 +1922,33 @@ function GreenScreenTerminal({
|
|
|
1499
1922
|
}
|
|
1500
1923
|
const headerSegments = index === 0 ? profile.colors.parseHeaderRow(displayLine) : null;
|
|
1501
1924
|
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: [
|
|
1502
|
-
headerSegments ? headerSegments.map((seg, i) => /* @__PURE__ */ jsx4("span", { className: seg.colorClass, children: seg.text }, i)) : renderRowWithFields(displayLine, index, fields),
|
|
1503
|
-
cursorInInputField &&
|
|
1925
|
+
headerSegments ? headerSegments.map((seg, i) => /* @__PURE__ */ jsx4("span", { className: seg.colorClass, children: seg.text }, i)) : renderRowWithFields(displayLine, index, fields, cursor.row, cursor.col),
|
|
1926
|
+
cursorInInputField && 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" } })
|
|
1504
1927
|
] }, index);
|
|
1505
1928
|
}),
|
|
1506
|
-
|
|
1929
|
+
validationError && /* @__PURE__ */ jsxs4(
|
|
1930
|
+
"div",
|
|
1931
|
+
{
|
|
1932
|
+
role: "alert",
|
|
1933
|
+
style: {
|
|
1934
|
+
position: "absolute",
|
|
1935
|
+
left: 0,
|
|
1936
|
+
right: 0,
|
|
1937
|
+
bottom: 0,
|
|
1938
|
+
padding: "2px 6px",
|
|
1939
|
+
background: "var(--gs-red, #FF5555)",
|
|
1940
|
+
color: "#000",
|
|
1941
|
+
fontFamily: "var(--gs-font)",
|
|
1942
|
+
fontSize: "12px",
|
|
1943
|
+
zIndex: 10
|
|
1944
|
+
},
|
|
1945
|
+
onClick: () => setValidationError(null),
|
|
1946
|
+
children: [
|
|
1947
|
+
validationError,
|
|
1948
|
+
" \u2014 click to dismiss"
|
|
1949
|
+
]
|
|
1950
|
+
}
|
|
1951
|
+
),
|
|
1507
1952
|
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: [
|
|
1508
1953
|
String(screenData.cursor_row + 1).padStart(2, "0"),
|
|
1509
1954
|
"/",
|
|
@@ -1534,6 +1979,15 @@ function GreenScreenTerminal({
|
|
|
1534
1979
|
return "#64748b";
|
|
1535
1980
|
}
|
|
1536
1981
|
};
|
|
1982
|
+
const handleShortcutSend = useCallback3(async (key) => {
|
|
1983
|
+
if (readOnly) return;
|
|
1984
|
+
const isSubmit = key === "ENTER" || key === "PAGEUP" || key === "PAGEDOWN" || /^F([1-9]|1[0-9]|2[0-4])$/.test(key);
|
|
1985
|
+
if (isSubmit && !runSelfCheck()) return;
|
|
1986
|
+
setIsFocused(true);
|
|
1987
|
+
inputRef.current?.focus();
|
|
1988
|
+
const kr = await sendKey(key);
|
|
1989
|
+
if (kr.cursor_row !== void 0) setSyncedCursor({ row: kr.cursor_row, col: kr.cursor_col });
|
|
1990
|
+
}, [readOnly, runSelfCheck, sendKey]);
|
|
1537
1991
|
return /* @__PURE__ */ jsxs4("div", { className: `gs-terminal ${isFocused ? "gs-terminal-focused" : ""} ${className || ""}`, style, children: [
|
|
1538
1992
|
showHeader && /* @__PURE__ */ jsx4("div", { className: "gs-header", children: embedded ? /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
1539
1993
|
/* @__PURE__ */ jsxs4("span", { className: "gs-header-left", children: [
|
|
@@ -1546,16 +2000,17 @@ function GreenScreenTerminal({
|
|
|
1546
2000
|
screenData?.insert_mode && /* @__PURE__ */ jsx4("span", { className: "gs-badge-ins", children: "INS" })
|
|
1547
2001
|
] }),
|
|
1548
2002
|
/* @__PURE__ */ jsxs4("div", { className: "gs-header-right", children: [
|
|
1549
|
-
connStatus?.status && /* @__PURE__ */ jsx4(KeyIcon, { size: 12, style: { color: getStatusColor(connStatus.status) } }),
|
|
1550
|
-
connStatus && (connStatus.connected ? /* @__PURE__ */ jsx4(WifiIcon, { size: 12, style: { color: "var(--gs-green, #10b981)" } }) : /* @__PURE__ */ jsx4(WifiOffIcon, { size: 12, style: { color: "#FF6B00" } })),
|
|
2003
|
+
connStatus?.status && connStatus.status !== "loading" && /* @__PURE__ */ jsx4(KeyIcon, { size: 12, style: { color: getStatusColor(connStatus.status) } }),
|
|
2004
|
+
connStatus && connStatus.status !== "loading" && (connStatus.connected ? /* @__PURE__ */ jsx4(WifiIcon, { size: 12, style: { color: "var(--gs-green, #10b981)" } }) : /* @__PURE__ */ jsx4(WifiOffIcon, { size: 12, style: { color: "#FF6B00" } })),
|
|
2005
|
+
statusActions,
|
|
1551
2006
|
onMinimize && /* @__PURE__ */ jsx4("button", { onClick: (e) => {
|
|
1552
2007
|
e.stopPropagation();
|
|
1553
2008
|
onMinimize();
|
|
1554
2009
|
}, className: "gs-btn-icon", title: "Minimize terminal", children: /* @__PURE__ */ jsx4(MinimizeIcon, {}) }),
|
|
1555
|
-
/* @__PURE__ */ jsx4("button", { onClick: (e) => {
|
|
2010
|
+
showShortcutsButton && /* @__PURE__ */ jsx4("button", { onClick: (e) => {
|
|
1556
2011
|
e.stopPropagation();
|
|
1557
2012
|
setShowShortcuts((s) => !s);
|
|
1558
|
-
}, className: "gs-btn-icon", title: "Keyboard shortcuts", children: /* @__PURE__ */ jsx4(
|
|
2013
|
+
}, className: "gs-btn-icon", title: "Keyboard shortcuts", children: /* @__PURE__ */ jsx4(KeyboardIcon, { size: 12 }) }),
|
|
1559
2014
|
headerRight
|
|
1560
2015
|
] })
|
|
1561
2016
|
] }) : /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
@@ -1569,7 +2024,7 @@ function GreenScreenTerminal({
|
|
|
1569
2024
|
screenData?.insert_mode && /* @__PURE__ */ jsx4("span", { className: "gs-badge-ins", children: "INS" })
|
|
1570
2025
|
] }),
|
|
1571
2026
|
/* @__PURE__ */ jsxs4("div", { className: "gs-header-right", children: [
|
|
1572
|
-
connStatus && /* @__PURE__ */ jsx4("div", { className: "gs-status-group", children: connStatus.connected ? /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
2027
|
+
connStatus && connStatus.status !== "loading" && /* @__PURE__ */ jsx4("div", { className: "gs-status-group", children: connStatus.connected ? /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
1573
2028
|
/* @__PURE__ */ jsx4(WifiIcon, { size: 12, style: { color: "var(--gs-green, #10b981)" } }),
|
|
1574
2029
|
/* @__PURE__ */ jsx4("span", { className: "gs-host", children: connStatus.host })
|
|
1575
2030
|
] }) : /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
@@ -1581,10 +2036,11 @@ function GreenScreenTerminal({
|
|
|
1581
2036
|
/* @__PURE__ */ jsx4(KeyIcon, { size: 12, style: { color: getStatusColor(connStatus.status) } }),
|
|
1582
2037
|
connStatus.username && /* @__PURE__ */ jsx4("span", { className: "gs-host", children: connStatus.username })
|
|
1583
2038
|
] }),
|
|
1584
|
-
|
|
2039
|
+
statusActions,
|
|
2040
|
+
showShortcutsButton && /* @__PURE__ */ jsx4("button", { onClick: (e) => {
|
|
1585
2041
|
e.stopPropagation();
|
|
1586
2042
|
setShowShortcuts((s) => !s);
|
|
1587
|
-
}, className: "gs-btn-icon", title: "Keyboard shortcuts", children: /* @__PURE__ */ jsx4(
|
|
2043
|
+
}, className: "gs-btn-icon", title: "Keyboard shortcuts", children: /* @__PURE__ */ jsx4(KeyboardIcon, { size: 12 }) }),
|
|
1588
2044
|
headerRight
|
|
1589
2045
|
] })
|
|
1590
2046
|
] }) }),
|
|
@@ -1605,29 +2061,47 @@ function GreenScreenTerminal({
|
|
|
1605
2061
|
showShortcuts && /* @__PURE__ */ jsxs4("div", { className: "gs-shortcuts-panel", children: [
|
|
1606
2062
|
/* @__PURE__ */ jsxs4("div", { className: "gs-shortcuts-header", children: [
|
|
1607
2063
|
/* @__PURE__ */ jsx4("span", { children: "Keyboard Shortcuts" }),
|
|
1608
|
-
/* @__PURE__ */ jsx4("button", { className: "gs-btn-icon", onClick: () => setShowShortcuts(false),
|
|
2064
|
+
/* @__PURE__ */ jsx4("button", { className: "gs-btn-icon", onClick: () => setShowShortcuts(false), children: "\xD7" })
|
|
1609
2065
|
] }),
|
|
2066
|
+
/* @__PURE__ */ jsx4("div", { className: "gs-shortcuts-section-title", children: "Actions" }),
|
|
2067
|
+
/* @__PURE__ */ jsx4("table", { className: "gs-shortcuts-table", children: /* @__PURE__ */ jsx4("tbody", { children: [
|
|
2068
|
+
["Enter", "Submit", "ENTER"],
|
|
2069
|
+
["Tab", "Next field", "TAB"],
|
|
2070
|
+
["Backspace", "Backspace", "BACKSPACE"],
|
|
2071
|
+
["Delete", "Delete", "DELETE"],
|
|
2072
|
+
["Insert", "Insert / Overwrite", "INSERT"],
|
|
2073
|
+
["Home", "Home", "HOME"],
|
|
2074
|
+
["End", "End", "END"],
|
|
2075
|
+
["Page Up", "Roll Down", "PAGEUP"],
|
|
2076
|
+
["Page Down", "Roll Up", "PAGEDOWN"],
|
|
2077
|
+
["Ctrl+Enter", "Field Exit", "FIELD_EXIT"],
|
|
2078
|
+
["Ctrl+R", "Reset", "RESET"],
|
|
2079
|
+
["\u2014", "Help", "HELP"],
|
|
2080
|
+
["\u2014", "Clear", "CLEAR"],
|
|
2081
|
+
["\u2014", "Print", "PRINT"]
|
|
2082
|
+
].map(([label, desc, key]) => /* @__PURE__ */ jsxs4("tr", { className: "gs-shortcut-row", onClick: (e) => {
|
|
2083
|
+
e.stopPropagation();
|
|
2084
|
+
handleShortcutSend(key);
|
|
2085
|
+
}, children: [
|
|
2086
|
+
/* @__PURE__ */ jsx4("td", { className: "gs-shortcut-key", children: label }),
|
|
2087
|
+
/* @__PURE__ */ jsx4("td", { children: desc })
|
|
2088
|
+
] }, key)) }) }),
|
|
2089
|
+
/* @__PURE__ */ jsx4("div", { className: "gs-shortcuts-section-title", children: "Function keys" }),
|
|
2090
|
+
/* @__PURE__ */ jsx4("div", { className: "gs-shortcuts-fkeys", children: Array.from({ length: 24 }, (_, i) => `F${i + 1}`).map((fk) => /* @__PURE__ */ jsx4(
|
|
2091
|
+
"button",
|
|
2092
|
+
{
|
|
2093
|
+
className: "gs-shortcut-fkey",
|
|
2094
|
+
onClick: (e) => {
|
|
2095
|
+
e.stopPropagation();
|
|
2096
|
+
handleShortcutSend(fk);
|
|
2097
|
+
},
|
|
2098
|
+
title: `Send ${fk}`,
|
|
2099
|
+
children: fk
|
|
2100
|
+
},
|
|
2101
|
+
fk
|
|
2102
|
+
)) }),
|
|
2103
|
+
/* @__PURE__ */ jsx4("div", { className: "gs-shortcuts-section-title", children: "Info" }),
|
|
1610
2104
|
/* @__PURE__ */ jsx4("table", { className: "gs-shortcuts-table", children: /* @__PURE__ */ jsxs4("tbody", { children: [
|
|
1611
|
-
/* @__PURE__ */ jsxs4("tr", { children: [
|
|
1612
|
-
/* @__PURE__ */ jsx4("td", { className: "gs-shortcut-key", children: "Ctrl+Enter" }),
|
|
1613
|
-
/* @__PURE__ */ jsx4("td", { children: "Field Exit" })
|
|
1614
|
-
] }),
|
|
1615
|
-
/* @__PURE__ */ jsxs4("tr", { children: [
|
|
1616
|
-
/* @__PURE__ */ jsx4("td", { className: "gs-shortcut-key", children: "Ctrl+R" }),
|
|
1617
|
-
/* @__PURE__ */ jsx4("td", { children: "Reset" })
|
|
1618
|
-
] }),
|
|
1619
|
-
/* @__PURE__ */ jsxs4("tr", { children: [
|
|
1620
|
-
/* @__PURE__ */ jsx4("td", { className: "gs-shortcut-key", children: "Insert" }),
|
|
1621
|
-
/* @__PURE__ */ jsx4("td", { children: "Insert / Overwrite" })
|
|
1622
|
-
] }),
|
|
1623
|
-
/* @__PURE__ */ jsxs4("tr", { children: [
|
|
1624
|
-
/* @__PURE__ */ jsx4("td", { className: "gs-shortcut-key", children: "Page Up" }),
|
|
1625
|
-
/* @__PURE__ */ jsx4("td", { children: "Roll Down" })
|
|
1626
|
-
] }),
|
|
1627
|
-
/* @__PURE__ */ jsxs4("tr", { children: [
|
|
1628
|
-
/* @__PURE__ */ jsx4("td", { className: "gs-shortcut-key", children: "Page Down" }),
|
|
1629
|
-
/* @__PURE__ */ jsx4("td", { children: "Roll Up" })
|
|
1630
|
-
] }),
|
|
1631
2105
|
/* @__PURE__ */ jsxs4("tr", { children: [
|
|
1632
2106
|
/* @__PURE__ */ jsx4("td", { className: "gs-shortcut-key", children: "Click" }),
|
|
1633
2107
|
/* @__PURE__ */ jsx4("td", { children: "Focus / Position cursor" })
|
|
@@ -1640,7 +2114,8 @@ function GreenScreenTerminal({
|
|
|
1640
2114
|
] }),
|
|
1641
2115
|
connStatus && !connStatus.connected && screenData && /* @__PURE__ */ jsxs4("div", { className: "gs-overlay", children: [
|
|
1642
2116
|
/* @__PURE__ */ jsx4(WifiOffIcon, { size: 28 }),
|
|
1643
|
-
/* @__PURE__ */ jsx4("span", { children: isAutoReconnecting || reconnecting ? "Reconnecting..." : "Disconnected" })
|
|
2117
|
+
/* @__PURE__ */ jsx4("span", { children: isAutoReconnecting || reconnecting ? "Reconnecting..." : connStatus?.status === "connecting" ? "Connecting..." : "Disconnected" }),
|
|
2118
|
+
connStatus.error && !isAutoReconnecting && !reconnecting && /* @__PURE__ */ jsx4("span", { style: { fontSize: "0.75em", opacity: 0.7, maxWidth: "80%", textAlign: "center", wordBreak: "break-word" }, children: connStatus.error })
|
|
1644
2119
|
] }),
|
|
1645
2120
|
/* @__PURE__ */ jsx4(
|
|
1646
2121
|
"input",
|
|
@@ -1654,7 +2129,19 @@ function GreenScreenTerminal({
|
|
|
1654
2129
|
autoComplete: "off",
|
|
1655
2130
|
autoCorrect: "off",
|
|
1656
2131
|
autoCapitalize: "off",
|
|
1657
|
-
spellCheck: false
|
|
2132
|
+
spellCheck: false,
|
|
2133
|
+
lang: (() => {
|
|
2134
|
+
const f = getCurrentField();
|
|
2135
|
+
if (!f) return void 0;
|
|
2136
|
+
if (f.is_dbcs || f.is_dbcs_either) return "ja";
|
|
2137
|
+
return void 0;
|
|
2138
|
+
})(),
|
|
2139
|
+
inputMode: (() => {
|
|
2140
|
+
const f = getCurrentField();
|
|
2141
|
+
if (!f) return void 0;
|
|
2142
|
+
if (f.is_dbcs) return "text";
|
|
2143
|
+
return "text";
|
|
2144
|
+
})()
|
|
1658
2145
|
}
|
|
1659
2146
|
)
|
|
1660
2147
|
]
|