green-screen-react 1.1.3 → 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 +29 -5
- package/dist/index.d.ts +29 -5
- package/dist/index.js +557 -78
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +557 -78
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +72 -27
- 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,7 +1326,6 @@ function GreenScreenTerminal({
|
|
|
1066
1326
|
inlineSignIn = true,
|
|
1067
1327
|
defaultProtocol: signInDefaultProtocol,
|
|
1068
1328
|
onSignIn,
|
|
1069
|
-
autoSignedIn,
|
|
1070
1329
|
autoFocusDisabled = false,
|
|
1071
1330
|
bootLoader,
|
|
1072
1331
|
bootLoaderReady,
|
|
@@ -1076,6 +1335,7 @@ function GreenScreenTerminal({
|
|
|
1076
1335
|
onNotification,
|
|
1077
1336
|
onScreenChange,
|
|
1078
1337
|
onMinimize,
|
|
1338
|
+
showShortcutsButton = true,
|
|
1079
1339
|
className,
|
|
1080
1340
|
style
|
|
1081
1341
|
}) {
|
|
@@ -1130,13 +1390,7 @@ function GreenScreenTerminal({
|
|
|
1130
1390
|
}, [rawScreenData?.content]);
|
|
1131
1391
|
const [inputText, setInputText] = useState5("");
|
|
1132
1392
|
const [isFocused, setIsFocused] = useState5(false);
|
|
1133
|
-
const [showSignInHint, setShowSignInHint] = useState5(false);
|
|
1134
1393
|
const [showShortcuts, setShowShortcuts] = useState5(false);
|
|
1135
|
-
const prevAutoSignedIn = useRef4(false);
|
|
1136
|
-
useEffect4(() => {
|
|
1137
|
-
if (autoSignedIn && !prevAutoSignedIn.current) setShowSignInHint(true);
|
|
1138
|
-
prevAutoSignedIn.current = !!autoSignedIn;
|
|
1139
|
-
}, [autoSignedIn]);
|
|
1140
1394
|
const terminalRef = useRef4(null);
|
|
1141
1395
|
const inputRef = useRef4(null);
|
|
1142
1396
|
const [syncedCursor, setSyncedCursor] = useState5(null);
|
|
@@ -1146,7 +1400,6 @@ function GreenScreenTerminal({
|
|
|
1146
1400
|
if (prevRawContentRef.current && newContent && newContent !== prevRawContentRef.current) {
|
|
1147
1401
|
setSyncedCursor(null);
|
|
1148
1402
|
setInputText("");
|
|
1149
|
-
if (showSignInHint) setShowSignInHint(false);
|
|
1150
1403
|
}
|
|
1151
1404
|
prevRawContentRef.current = newContent;
|
|
1152
1405
|
}, [rawScreenData?.content]);
|
|
@@ -1209,11 +1462,9 @@ function GreenScreenTerminal({
|
|
|
1209
1462
|
const newAdapter = new RestAdapter({ baseUrl: `http://${config.host}${port}` });
|
|
1210
1463
|
setInternalAdapter(newAdapter);
|
|
1211
1464
|
await newAdapter.connect(config);
|
|
1212
|
-
if (config.username && config.password) setShowSignInHint(true);
|
|
1213
1465
|
return;
|
|
1214
1466
|
}
|
|
1215
1467
|
await connect(config);
|
|
1216
|
-
if (config.username && config.password) setShowSignInHint(true);
|
|
1217
1468
|
} catch (err) {
|
|
1218
1469
|
setSignInError(err instanceof Error ? err.message : String(err));
|
|
1219
1470
|
setConnecting(false);
|
|
@@ -1301,13 +1552,25 @@ function GreenScreenTerminal({
|
|
|
1301
1552
|
const clickedRow = Math.floor(y / ROW_HEIGHT);
|
|
1302
1553
|
const clickedCol = Math.floor(x / charWidth);
|
|
1303
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
|
+
}
|
|
1304
1567
|
setSyncedCursor({ row: clickedRow, col: clickedCol });
|
|
1305
1568
|
adapter.setCursor?.(clickedRow, clickedCol).then((r) => {
|
|
1306
1569
|
if (r?.cursor_row !== void 0) {
|
|
1307
1570
|
setSyncedCursor({ row: r.cursor_row, col: r.cursor_col });
|
|
1308
1571
|
}
|
|
1309
1572
|
});
|
|
1310
|
-
}, [readOnly, screenData, adapter]);
|
|
1573
|
+
}, [readOnly, screenData, adapter, sendKey]);
|
|
1311
1574
|
const getCurrentField = useCallback3(() => {
|
|
1312
1575
|
const fields = screenData?.fields || [];
|
|
1313
1576
|
const cursorRow = syncedCursor?.row ?? screenData?.cursor_row ?? 0;
|
|
@@ -1317,6 +1580,31 @@ function GreenScreenTerminal({
|
|
|
1317
1580
|
}
|
|
1318
1581
|
return null;
|
|
1319
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]);
|
|
1320
1608
|
const handleKeyDown = async (e) => {
|
|
1321
1609
|
if (readOnly) {
|
|
1322
1610
|
e.preventDefault();
|
|
@@ -1355,18 +1643,19 @@ function GreenScreenTerminal({
|
|
|
1355
1643
|
End: "END",
|
|
1356
1644
|
Insert: "INSERT"
|
|
1357
1645
|
};
|
|
1358
|
-
if (
|
|
1646
|
+
if (/^F([1-9]|1[0-9]|2[0-4])$/.test(e.key)) {
|
|
1359
1647
|
e.preventDefault();
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
return;
|
|
1365
|
-
}
|
|
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;
|
|
1366
1652
|
}
|
|
1367
1653
|
if (keyMap[e.key]) {
|
|
1368
1654
|
e.preventDefault();
|
|
1369
|
-
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);
|
|
1370
1659
|
if (kr.cursor_row !== void 0) setSyncedCursor({ row: kr.cursor_row, col: kr.cursor_col });
|
|
1371
1660
|
}
|
|
1372
1661
|
};
|
|
@@ -1377,18 +1666,32 @@ function GreenScreenTerminal({
|
|
|
1377
1666
|
}
|
|
1378
1667
|
const newText = e.target.value;
|
|
1379
1668
|
if (newText.length > inputText.length) {
|
|
1380
|
-
|
|
1381
|
-
const
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
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
|
+
});
|
|
1386
1694
|
}
|
|
1387
|
-
setOptimisticEdits((prev) => [...prev, ...edits]);
|
|
1388
|
-
setSyncedCursor({ row: curRow, col: curCol + newChars.length });
|
|
1389
|
-
sendText(newChars).then((r) => {
|
|
1390
|
-
if (r.cursor_row !== void 0) setSyncedCursor({ row: r.cursor_row, col: r.cursor_col });
|
|
1391
|
-
});
|
|
1392
1695
|
}
|
|
1393
1696
|
setInputText("");
|
|
1394
1697
|
e.target.value = "";
|
|
@@ -1420,41 +1723,156 @@ function GreenScreenTerminal({
|
|
|
1420
1723
|
if (lastIndex < text.length) segments.push(/* @__PURE__ */ jsx4("span", { children: text.substring(lastIndex) }, `${keyPrefix}-e`));
|
|
1421
1724
|
return segments.length > 0 ? /* @__PURE__ */ jsx4(Fragment, { children: segments }) : /* @__PURE__ */ jsx4(Fragment, { children: text });
|
|
1422
1725
|
}, []);
|
|
1423
|
-
const renderRowWithFields = useCallback3((line, rowIndex, fields) => {
|
|
1726
|
+
const renderRowWithFields = useCallback3((line, rowIndex, fields, cursorRow, cursorCol) => {
|
|
1424
1727
|
const inputFields = fields.filter((f) => f.row === rowIndex && f.is_input);
|
|
1425
1728
|
const highlightedFields = fields.filter((f) => f.row === rowIndex && f.is_protected && f.is_highlighted);
|
|
1426
1729
|
const reverseFields = fields.filter((f) => f.row === rowIndex && f.is_protected && f.is_reverse);
|
|
1427
1730
|
const colorFields = fields.filter((f) => f.row === rowIndex && f.is_protected && f.color && !f.is_highlighted && !f.is_reverse);
|
|
1428
1731
|
const allRowFields = [...inputFields, ...highlightedFields, ...reverseFields, ...colorFields];
|
|
1429
|
-
|
|
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 });
|
|
1430
1749
|
const sorted = [...allRowFields].sort((a, b) => a.col - b.col);
|
|
1431
1750
|
const segs = [];
|
|
1432
1751
|
let lastEnd = 0;
|
|
1433
|
-
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
|
+
};
|
|
1434
1811
|
sorted.forEach((field, idx) => {
|
|
1435
1812
|
const fs = field.col;
|
|
1436
1813
|
const fe = Math.min(field.col + field.length, cols);
|
|
1437
|
-
if (fs > lastEnd)
|
|
1814
|
+
if (fs > lastEnd) renderPlainRun(lastEnd, fs, `t${idx}`);
|
|
1438
1815
|
const fc = line.substring(fs, fe);
|
|
1439
|
-
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;
|
|
1440
1830
|
if (field.is_input) {
|
|
1441
1831
|
const fieldWidth = Math.min(field.length, cols - fs);
|
|
1442
|
-
const fieldClass =
|
|
1443
|
-
|
|
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
|
+
);
|
|
1444
1853
|
} else if (field.is_reverse) {
|
|
1445
|
-
segs.push(
|
|
1446
|
-
|
|
1447
|
-
|
|
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}`));
|
|
1448
1866
|
} else if (field.is_highlighted) {
|
|
1449
|
-
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}`));
|
|
1450
1868
|
} else {
|
|
1451
|
-
segs.push(/* @__PURE__ */ jsx4("span", { children:
|
|
1869
|
+
segs.push(/* @__PURE__ */ jsx4("span", { children: displayText }, `h${idx}`));
|
|
1452
1870
|
}
|
|
1453
1871
|
lastEnd = fe;
|
|
1454
1872
|
});
|
|
1455
|
-
if (lastEnd < line.length)
|
|
1873
|
+
if (lastEnd < line.length) renderPlainRun(lastEnd, line.length, "te");
|
|
1456
1874
|
return /* @__PURE__ */ jsx4(Fragment, { children: segs });
|
|
1457
|
-
}, [renderTextWithUnderlines, screenData
|
|
1875
|
+
}, [renderTextWithUnderlines, screenData, profile.defaultCols]);
|
|
1458
1876
|
const renderScreen = () => {
|
|
1459
1877
|
if (showBootLoader && !screenData?.content) {
|
|
1460
1878
|
if (bootLoader === false) return null;
|
|
@@ -1471,7 +1889,7 @@ function GreenScreenTerminal({
|
|
|
1471
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" }),
|
|
1472
1890
|
!connStatus?.connected && isUsingDefaultAdapter && /* @__PURE__ */ jsxs4("p", { style: { fontFamily: "var(--gs-font)", fontSize: "11px", color: "#606060", marginTop: "8px" }, children: [
|
|
1473
1891
|
"Start the proxy: ",
|
|
1474
|
-
/* @__PURE__ */ jsx4("code", { style: { color: "#10b981" }, children: "npx green-screen-proxy
|
|
1892
|
+
/* @__PURE__ */ jsx4("code", { style: { color: "#10b981" }, children: "npx green-screen-proxy" })
|
|
1475
1893
|
] })
|
|
1476
1894
|
] }) });
|
|
1477
1895
|
}
|
|
@@ -1504,11 +1922,33 @@ function GreenScreenTerminal({
|
|
|
1504
1922
|
}
|
|
1505
1923
|
const headerSegments = index === 0 ? profile.colors.parseHeaderRow(displayLine) : null;
|
|
1506
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: [
|
|
1507
|
-
headerSegments ? headerSegments.map((seg, i) => /* @__PURE__ */ jsx4("span", { className: seg.colorClass, children: seg.text }, i)) : renderRowWithFields(displayLine, index, fields),
|
|
1508
|
-
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" } })
|
|
1509
1927
|
] }, index);
|
|
1510
1928
|
}),
|
|
1511
|
-
|
|
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
|
+
),
|
|
1512
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: [
|
|
1513
1953
|
String(screenData.cursor_row + 1).padStart(2, "0"),
|
|
1514
1954
|
"/",
|
|
@@ -1539,6 +1979,15 @@ function GreenScreenTerminal({
|
|
|
1539
1979
|
return "#64748b";
|
|
1540
1980
|
}
|
|
1541
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]);
|
|
1542
1991
|
return /* @__PURE__ */ jsxs4("div", { className: `gs-terminal ${isFocused ? "gs-terminal-focused" : ""} ${className || ""}`, style, children: [
|
|
1543
1992
|
showHeader && /* @__PURE__ */ jsx4("div", { className: "gs-header", children: embedded ? /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
1544
1993
|
/* @__PURE__ */ jsxs4("span", { className: "gs-header-left", children: [
|
|
@@ -1558,10 +2007,10 @@ function GreenScreenTerminal({
|
|
|
1558
2007
|
e.stopPropagation();
|
|
1559
2008
|
onMinimize();
|
|
1560
2009
|
}, className: "gs-btn-icon", title: "Minimize terminal", children: /* @__PURE__ */ jsx4(MinimizeIcon, {}) }),
|
|
1561
|
-
/* @__PURE__ */ jsx4("button", { onClick: (e) => {
|
|
2010
|
+
showShortcutsButton && /* @__PURE__ */ jsx4("button", { onClick: (e) => {
|
|
1562
2011
|
e.stopPropagation();
|
|
1563
2012
|
setShowShortcuts((s) => !s);
|
|
1564
|
-
}, className: "gs-btn-icon", title: "Keyboard shortcuts", children: /* @__PURE__ */ jsx4(
|
|
2013
|
+
}, className: "gs-btn-icon", title: "Keyboard shortcuts", children: /* @__PURE__ */ jsx4(KeyboardIcon, { size: 12 }) }),
|
|
1565
2014
|
headerRight
|
|
1566
2015
|
] })
|
|
1567
2016
|
] }) : /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
@@ -1588,10 +2037,10 @@ function GreenScreenTerminal({
|
|
|
1588
2037
|
connStatus.username && /* @__PURE__ */ jsx4("span", { className: "gs-host", children: connStatus.username })
|
|
1589
2038
|
] }),
|
|
1590
2039
|
statusActions,
|
|
1591
|
-
/* @__PURE__ */ jsx4("button", { onClick: (e) => {
|
|
2040
|
+
showShortcutsButton && /* @__PURE__ */ jsx4("button", { onClick: (e) => {
|
|
1592
2041
|
e.stopPropagation();
|
|
1593
2042
|
setShowShortcuts((s) => !s);
|
|
1594
|
-
}, className: "gs-btn-icon", title: "Keyboard shortcuts", children: /* @__PURE__ */ jsx4(
|
|
2043
|
+
}, className: "gs-btn-icon", title: "Keyboard shortcuts", children: /* @__PURE__ */ jsx4(KeyboardIcon, { size: 12 }) }),
|
|
1595
2044
|
headerRight
|
|
1596
2045
|
] })
|
|
1597
2046
|
] }) }),
|
|
@@ -1612,29 +2061,47 @@ function GreenScreenTerminal({
|
|
|
1612
2061
|
showShortcuts && /* @__PURE__ */ jsxs4("div", { className: "gs-shortcuts-panel", children: [
|
|
1613
2062
|
/* @__PURE__ */ jsxs4("div", { className: "gs-shortcuts-header", children: [
|
|
1614
2063
|
/* @__PURE__ */ jsx4("span", { children: "Keyboard Shortcuts" }),
|
|
1615
|
-
/* @__PURE__ */ jsx4("button", { className: "gs-btn-icon", onClick: () => setShowShortcuts(false),
|
|
2064
|
+
/* @__PURE__ */ jsx4("button", { className: "gs-btn-icon", onClick: () => setShowShortcuts(false), children: "\xD7" })
|
|
1616
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" }),
|
|
1617
2104
|
/* @__PURE__ */ jsx4("table", { className: "gs-shortcuts-table", children: /* @__PURE__ */ jsxs4("tbody", { children: [
|
|
1618
|
-
/* @__PURE__ */ jsxs4("tr", { children: [
|
|
1619
|
-
/* @__PURE__ */ jsx4("td", { className: "gs-shortcut-key", children: "Ctrl+Enter" }),
|
|
1620
|
-
/* @__PURE__ */ jsx4("td", { children: "Field Exit" })
|
|
1621
|
-
] }),
|
|
1622
|
-
/* @__PURE__ */ jsxs4("tr", { children: [
|
|
1623
|
-
/* @__PURE__ */ jsx4("td", { className: "gs-shortcut-key", children: "Ctrl+R" }),
|
|
1624
|
-
/* @__PURE__ */ jsx4("td", { children: "Reset" })
|
|
1625
|
-
] }),
|
|
1626
|
-
/* @__PURE__ */ jsxs4("tr", { children: [
|
|
1627
|
-
/* @__PURE__ */ jsx4("td", { className: "gs-shortcut-key", children: "Insert" }),
|
|
1628
|
-
/* @__PURE__ */ jsx4("td", { children: "Insert / Overwrite" })
|
|
1629
|
-
] }),
|
|
1630
|
-
/* @__PURE__ */ jsxs4("tr", { children: [
|
|
1631
|
-
/* @__PURE__ */ jsx4("td", { className: "gs-shortcut-key", children: "Page Up" }),
|
|
1632
|
-
/* @__PURE__ */ jsx4("td", { children: "Roll Down" })
|
|
1633
|
-
] }),
|
|
1634
|
-
/* @__PURE__ */ jsxs4("tr", { children: [
|
|
1635
|
-
/* @__PURE__ */ jsx4("td", { className: "gs-shortcut-key", children: "Page Down" }),
|
|
1636
|
-
/* @__PURE__ */ jsx4("td", { children: "Roll Up" })
|
|
1637
|
-
] }),
|
|
1638
2105
|
/* @__PURE__ */ jsxs4("tr", { children: [
|
|
1639
2106
|
/* @__PURE__ */ jsx4("td", { className: "gs-shortcut-key", children: "Click" }),
|
|
1640
2107
|
/* @__PURE__ */ jsx4("td", { children: "Focus / Position cursor" })
|
|
@@ -1662,7 +2129,19 @@ function GreenScreenTerminal({
|
|
|
1662
2129
|
autoComplete: "off",
|
|
1663
2130
|
autoCorrect: "off",
|
|
1664
2131
|
autoCapitalize: "off",
|
|
1665
|
-
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
|
+
})()
|
|
1666
2145
|
}
|
|
1667
2146
|
)
|
|
1668
2147
|
]
|