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.js
CHANGED
|
@@ -102,6 +102,14 @@ var RestAdapter = class {
|
|
|
102
102
|
async setCursor(row, col) {
|
|
103
103
|
return this.request("POST", "/set-cursor", { row, col });
|
|
104
104
|
}
|
|
105
|
+
async readMdt(modifiedOnly = true) {
|
|
106
|
+
const qs = modifiedOnly ? "" : "?includeUnmodified=1";
|
|
107
|
+
const resp = await this.request(
|
|
108
|
+
"GET",
|
|
109
|
+
`/read-mdt${qs}`
|
|
110
|
+
);
|
|
111
|
+
return resp?.fields ?? [];
|
|
112
|
+
}
|
|
105
113
|
async connect(config) {
|
|
106
114
|
return this.request("POST", "/connect", config);
|
|
107
115
|
}
|
|
@@ -122,9 +130,12 @@ var WebSocketAdapter = class _WebSocketAdapter {
|
|
|
122
130
|
this.status = { connected: false, status: "disconnected" };
|
|
123
131
|
this.pendingScreenResolver = null;
|
|
124
132
|
this.pendingConnectResolver = null;
|
|
133
|
+
this.pendingMdtResolver = null;
|
|
125
134
|
this.connectingPromise = null;
|
|
126
135
|
this.screenListeners = /* @__PURE__ */ new Set();
|
|
127
136
|
this.statusListeners = /* @__PURE__ */ new Set();
|
|
137
|
+
this.sessionLostListeners = /* @__PURE__ */ new Set();
|
|
138
|
+
this.sessionResumedListeners = /* @__PURE__ */ new Set();
|
|
128
139
|
this._sessionId = null;
|
|
129
140
|
this.workerUrl = (options.workerUrl || _WebSocketAdapter.detectEnvUrl() || "http://localhost:3001").replace(/\/+$/, "");
|
|
130
141
|
}
|
|
@@ -159,6 +170,22 @@ var WebSocketAdapter = class _WebSocketAdapter {
|
|
|
159
170
|
this.statusListeners.add(listener);
|
|
160
171
|
return () => this.statusListeners.delete(listener);
|
|
161
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Subscribe to session-lost notifications. Fires when the proxy detects
|
|
175
|
+
* that the server-side session has terminated (host TCP drop, idle
|
|
176
|
+
* timeout, explicit destroy). Integrators use this to prompt a reconnect
|
|
177
|
+
* or swap to a "session expired" UI without relying on error-string
|
|
178
|
+
* matching.
|
|
179
|
+
*/
|
|
180
|
+
onSessionLost(listener) {
|
|
181
|
+
this.sessionLostListeners.add(listener);
|
|
182
|
+
return () => this.sessionLostListeners.delete(listener);
|
|
183
|
+
}
|
|
184
|
+
/** Subscribe to session-resumed notifications (fires after a successful `reattach`). */
|
|
185
|
+
onSessionResumed(listener) {
|
|
186
|
+
this.sessionResumedListeners.add(listener);
|
|
187
|
+
return () => this.sessionResumedListeners.delete(listener);
|
|
188
|
+
}
|
|
162
189
|
async getScreen() {
|
|
163
190
|
return this.screen;
|
|
164
191
|
}
|
|
@@ -174,6 +201,25 @@ var WebSocketAdapter = class _WebSocketAdapter {
|
|
|
174
201
|
async setCursor(row, col) {
|
|
175
202
|
return this.sendAndWaitForScreen({ type: "setCursor", row, col });
|
|
176
203
|
}
|
|
204
|
+
async readMdt(modifiedOnly = true) {
|
|
205
|
+
await this.ensureWebSocket();
|
|
206
|
+
return new Promise((resolve) => {
|
|
207
|
+
if (this.pendingMdtResolver) {
|
|
208
|
+
const old = this.pendingMdtResolver;
|
|
209
|
+
this.pendingMdtResolver = null;
|
|
210
|
+
old([]);
|
|
211
|
+
}
|
|
212
|
+
const timeout = setTimeout(() => {
|
|
213
|
+
this.pendingMdtResolver = null;
|
|
214
|
+
resolve([]);
|
|
215
|
+
}, 5e3);
|
|
216
|
+
this.pendingMdtResolver = (fields) => {
|
|
217
|
+
clearTimeout(timeout);
|
|
218
|
+
resolve(fields);
|
|
219
|
+
};
|
|
220
|
+
this.wsSend({ type: "readMdt", modifiedOnly });
|
|
221
|
+
});
|
|
222
|
+
}
|
|
177
223
|
async connect(config) {
|
|
178
224
|
await this.ensureWebSocket();
|
|
179
225
|
if (!config) {
|
|
@@ -287,6 +333,22 @@ var WebSocketAdapter = class _WebSocketAdapter {
|
|
|
287
333
|
}
|
|
288
334
|
break;
|
|
289
335
|
}
|
|
336
|
+
case "mdt": {
|
|
337
|
+
if (this.pendingMdtResolver) {
|
|
338
|
+
const resolver = this.pendingMdtResolver;
|
|
339
|
+
this.pendingMdtResolver = null;
|
|
340
|
+
resolver(msg.data?.fields ?? []);
|
|
341
|
+
}
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
case "session.lost": {
|
|
345
|
+
for (const listener of this.sessionLostListeners) listener(msg.sessionId, msg.status);
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
case "session.resumed": {
|
|
349
|
+
for (const listener of this.sessionResumedListeners) listener(msg.sessionId);
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
290
352
|
case "status":
|
|
291
353
|
this.status = msg.data;
|
|
292
354
|
for (const listener of this.statusListeners) listener(msg.data);
|
|
@@ -957,10 +1019,15 @@ var RefreshIcon = ({ size = 12, className }) => /* @__PURE__ */ (0, import_jsx_r
|
|
|
957
1019
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" })
|
|
958
1020
|
] });
|
|
959
1021
|
var KeyIcon = ({ size = 12, style: s }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: s, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" }) });
|
|
960
|
-
var
|
|
961
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("
|
|
962
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("
|
|
963
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "
|
|
1022
|
+
var KeyboardIcon = ({ size = 14 }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
1023
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: "2", y: "6", width: "20", height: "12", rx: "2", ry: "2" }),
|
|
1024
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "6", y1: "10", x2: "6", y2: "10" }),
|
|
1025
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "10", y1: "10", x2: "10", y2: "10" }),
|
|
1026
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "14", y1: "10", x2: "14", y2: "10" }),
|
|
1027
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "18", y1: "10", x2: "18", y2: "10" }),
|
|
1028
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "6", y1: "14", x2: "6", y2: "14" }),
|
|
1029
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "18", y1: "14", x2: "18", y2: "14" }),
|
|
1030
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "10", y1: "14", x2: "14", y2: "14" })
|
|
964
1031
|
] });
|
|
965
1032
|
var MinimizeIcon = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: 14, height: 14, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M4 14h6v6M3 21l7-7M20 10h-6V4M21 3l-7 7" }) });
|
|
966
1033
|
|
|
@@ -1085,8 +1152,201 @@ function InlineSignIn({ defaultProtocol, loading: externalLoading, error, onConn
|
|
|
1085
1152
|
] });
|
|
1086
1153
|
}
|
|
1087
1154
|
|
|
1155
|
+
// src/utils/attribute.ts
|
|
1156
|
+
function decodeAttrByte(byte) {
|
|
1157
|
+
const type = byte & 7;
|
|
1158
|
+
const colorGroup = byte & 24;
|
|
1159
|
+
const highIntensity = (byte & 2) !== 0;
|
|
1160
|
+
let color = "green";
|
|
1161
|
+
switch (colorGroup) {
|
|
1162
|
+
case 0:
|
|
1163
|
+
color = highIntensity ? "white" : "green";
|
|
1164
|
+
break;
|
|
1165
|
+
case 8:
|
|
1166
|
+
color = "red";
|
|
1167
|
+
break;
|
|
1168
|
+
// red stays red at HI
|
|
1169
|
+
case 16:
|
|
1170
|
+
color = highIntensity ? "yellow" : "turquoise";
|
|
1171
|
+
break;
|
|
1172
|
+
case 24:
|
|
1173
|
+
color = highIntensity ? "blue" : "pink";
|
|
1174
|
+
break;
|
|
1175
|
+
}
|
|
1176
|
+
const nonDisplay = type === 7;
|
|
1177
|
+
const underscore = type === 4 || type === 5 || type === 6;
|
|
1178
|
+
const reverse = type === 5;
|
|
1179
|
+
const columnSeparator = type === 1 || type === 3;
|
|
1180
|
+
const hiTypeBit = type === 2 || type === 3 || type === 6;
|
|
1181
|
+
return {
|
|
1182
|
+
color,
|
|
1183
|
+
highIntensity: highIntensity || hiTypeBit,
|
|
1184
|
+
underscore,
|
|
1185
|
+
reverse,
|
|
1186
|
+
blink: false,
|
|
1187
|
+
// 5250 has no base-attr blink; blink comes from WEA highlight
|
|
1188
|
+
nonDisplay,
|
|
1189
|
+
columnSeparator
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
function decodeExtColor(byte) {
|
|
1193
|
+
switch (byte & 15) {
|
|
1194
|
+
case 0:
|
|
1195
|
+
return "green";
|
|
1196
|
+
case 1:
|
|
1197
|
+
return "blue";
|
|
1198
|
+
case 2:
|
|
1199
|
+
return "red";
|
|
1200
|
+
case 3:
|
|
1201
|
+
return "pink";
|
|
1202
|
+
case 4:
|
|
1203
|
+
return "turquoise";
|
|
1204
|
+
case 5:
|
|
1205
|
+
return "yellow";
|
|
1206
|
+
case 6:
|
|
1207
|
+
return "white";
|
|
1208
|
+
case 7:
|
|
1209
|
+
return void 0;
|
|
1210
|
+
// default — inherit
|
|
1211
|
+
// Reverse-image pairs: distinct tokens so themes can override
|
|
1212
|
+
case 8:
|
|
1213
|
+
return "green";
|
|
1214
|
+
// reverse green → typically same color, background flip
|
|
1215
|
+
case 9:
|
|
1216
|
+
return "blue";
|
|
1217
|
+
case 10:
|
|
1218
|
+
return "red";
|
|
1219
|
+
case 11:
|
|
1220
|
+
return "pink";
|
|
1221
|
+
case 12:
|
|
1222
|
+
return "turquoise";
|
|
1223
|
+
case 13:
|
|
1224
|
+
return "yellow";
|
|
1225
|
+
case 14:
|
|
1226
|
+
return "white";
|
|
1227
|
+
case 15:
|
|
1228
|
+
return void 0;
|
|
1229
|
+
default:
|
|
1230
|
+
return void 0;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
function extColorIsReverse(byte) {
|
|
1234
|
+
const v = byte & 15;
|
|
1235
|
+
return v >= 8 && v <= 14;
|
|
1236
|
+
}
|
|
1237
|
+
function decodeExtHighlight(byte) {
|
|
1238
|
+
return {
|
|
1239
|
+
underscore: (byte & 1) !== 0,
|
|
1240
|
+
reverse: (byte & 2) !== 0,
|
|
1241
|
+
blink: (byte & 4) !== 0,
|
|
1242
|
+
columnSeparator: (byte & 8) !== 0
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
function cssVarForColor(color) {
|
|
1246
|
+
if (!color) return "var(--gs-green, #10b981)";
|
|
1247
|
+
return `var(--gs-${color}, var(--gs-green, #10b981))`;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// src/utils/validation.ts
|
|
1251
|
+
function filterFieldChar(field, ch, isLastPosition = false) {
|
|
1252
|
+
let out = ch;
|
|
1253
|
+
if (field.monocase) {
|
|
1254
|
+
out = out.toUpperCase();
|
|
1255
|
+
}
|
|
1256
|
+
const cp = out.charCodeAt(0);
|
|
1257
|
+
const isAsciiLetter = cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122;
|
|
1258
|
+
const isAsciiDigit = cp >= 48 && cp <= 57;
|
|
1259
|
+
switch (field.shift_type) {
|
|
1260
|
+
case "alpha_only": {
|
|
1261
|
+
if (isAsciiLetter || out === "," || out === "-" || out === "." || out === " ") return out;
|
|
1262
|
+
return null;
|
|
1263
|
+
}
|
|
1264
|
+
case "numeric_only": {
|
|
1265
|
+
if (isAsciiDigit || out === "-" || out === "+" || out === "," || out === "." || out === " ") return out;
|
|
1266
|
+
return null;
|
|
1267
|
+
}
|
|
1268
|
+
case "digits_only": {
|
|
1269
|
+
if (isAsciiDigit) return out;
|
|
1270
|
+
return null;
|
|
1271
|
+
}
|
|
1272
|
+
case "signed_num": {
|
|
1273
|
+
if (isAsciiDigit) return out;
|
|
1274
|
+
if (isLastPosition && (out === "-" || out === "+")) return out;
|
|
1275
|
+
return null;
|
|
1276
|
+
}
|
|
1277
|
+
case "katakana": {
|
|
1278
|
+
if (cp >= 65377 && cp <= 65439 || isAsciiDigit || out === " ") return out;
|
|
1279
|
+
return null;
|
|
1280
|
+
}
|
|
1281
|
+
case "alpha":
|
|
1282
|
+
case "numeric_shift":
|
|
1283
|
+
case "io":
|
|
1284
|
+
case void 0:
|
|
1285
|
+
default:
|
|
1286
|
+
return out;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
function filterFieldInput(field, input, startOffset) {
|
|
1290
|
+
let out = "";
|
|
1291
|
+
let rejected = false;
|
|
1292
|
+
for (let i = 0; i < input.length; i++) {
|
|
1293
|
+
const pos = startOffset + i;
|
|
1294
|
+
const isLast = pos === field.length - 1;
|
|
1295
|
+
const transformed = filterFieldChar(field, input[i], isLast);
|
|
1296
|
+
if (transformed === null) {
|
|
1297
|
+
rejected = true;
|
|
1298
|
+
} else {
|
|
1299
|
+
out += transformed;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
return { out, rejected };
|
|
1303
|
+
}
|
|
1304
|
+
function validateMod10(value) {
|
|
1305
|
+
const digits = value.replace(/\D/g, "");
|
|
1306
|
+
if (digits.length < 2) return true;
|
|
1307
|
+
let sum = 0;
|
|
1308
|
+
let alt = false;
|
|
1309
|
+
for (let i = digits.length - 1; i >= 0; i--) {
|
|
1310
|
+
let d = digits.charCodeAt(i) - 48;
|
|
1311
|
+
if (alt) {
|
|
1312
|
+
d *= 2;
|
|
1313
|
+
if (d > 9) d -= 9;
|
|
1314
|
+
}
|
|
1315
|
+
sum += d;
|
|
1316
|
+
alt = !alt;
|
|
1317
|
+
}
|
|
1318
|
+
return sum % 10 === 0;
|
|
1319
|
+
}
|
|
1320
|
+
function validateMod11(value) {
|
|
1321
|
+
const digits = value.replace(/\D/g, "");
|
|
1322
|
+
if (digits.length < 2) return true;
|
|
1323
|
+
const body = digits.slice(0, -1);
|
|
1324
|
+
const check = digits.charCodeAt(digits.length - 1) - 48;
|
|
1325
|
+
let sum = 0;
|
|
1326
|
+
let weight = 2;
|
|
1327
|
+
for (let i = body.length - 1; i >= 0; i--) {
|
|
1328
|
+
sum += (body.charCodeAt(i) - 48) * weight;
|
|
1329
|
+
weight++;
|
|
1330
|
+
if (weight > 7) weight = 2;
|
|
1331
|
+
}
|
|
1332
|
+
const remainder = sum % 11;
|
|
1333
|
+
const expected = remainder === 0 ? 0 : 11 - remainder;
|
|
1334
|
+
return expected === check;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1088
1337
|
// src/components/GreenScreenTerminal.tsx
|
|
1089
1338
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1339
|
+
function aidByteToKeyName(aid) {
|
|
1340
|
+
if (aid === 241) return "ENTER";
|
|
1341
|
+
if (aid === 243) return "HELP";
|
|
1342
|
+
if (aid === 244) return "PAGEUP";
|
|
1343
|
+
if (aid === 245) return "PAGEDOWN";
|
|
1344
|
+
if (aid === 246) return "PRINT";
|
|
1345
|
+
if (aid === 189) return "CLEAR";
|
|
1346
|
+
if (aid >= 49 && aid <= 60) return `F${aid - 48}`;
|
|
1347
|
+
if (aid >= 177 && aid <= 188) return `F${aid - 176 + 12}`;
|
|
1348
|
+
return null;
|
|
1349
|
+
}
|
|
1090
1350
|
var noopResult = { success: false, error: "No adapter configured" };
|
|
1091
1351
|
var noopAdapter = {
|
|
1092
1352
|
getScreen: async () => null,
|
|
@@ -1117,7 +1377,6 @@ function GreenScreenTerminal({
|
|
|
1117
1377
|
inlineSignIn = true,
|
|
1118
1378
|
defaultProtocol: signInDefaultProtocol,
|
|
1119
1379
|
onSignIn,
|
|
1120
|
-
autoSignedIn,
|
|
1121
1380
|
autoFocusDisabled = false,
|
|
1122
1381
|
bootLoader,
|
|
1123
1382
|
bootLoaderReady,
|
|
@@ -1127,6 +1386,7 @@ function GreenScreenTerminal({
|
|
|
1127
1386
|
onNotification,
|
|
1128
1387
|
onScreenChange,
|
|
1129
1388
|
onMinimize,
|
|
1389
|
+
showShortcutsButton = true,
|
|
1130
1390
|
className,
|
|
1131
1391
|
style
|
|
1132
1392
|
}) {
|
|
@@ -1181,13 +1441,7 @@ function GreenScreenTerminal({
|
|
|
1181
1441
|
}, [rawScreenData?.content]);
|
|
1182
1442
|
const [inputText, setInputText] = (0, import_react5.useState)("");
|
|
1183
1443
|
const [isFocused, setIsFocused] = (0, import_react5.useState)(false);
|
|
1184
|
-
const [showSignInHint, setShowSignInHint] = (0, import_react5.useState)(false);
|
|
1185
1444
|
const [showShortcuts, setShowShortcuts] = (0, import_react5.useState)(false);
|
|
1186
|
-
const prevAutoSignedIn = (0, import_react5.useRef)(false);
|
|
1187
|
-
(0, import_react5.useEffect)(() => {
|
|
1188
|
-
if (autoSignedIn && !prevAutoSignedIn.current) setShowSignInHint(true);
|
|
1189
|
-
prevAutoSignedIn.current = !!autoSignedIn;
|
|
1190
|
-
}, [autoSignedIn]);
|
|
1191
1445
|
const terminalRef = (0, import_react5.useRef)(null);
|
|
1192
1446
|
const inputRef = (0, import_react5.useRef)(null);
|
|
1193
1447
|
const [syncedCursor, setSyncedCursor] = (0, import_react5.useState)(null);
|
|
@@ -1197,7 +1451,6 @@ function GreenScreenTerminal({
|
|
|
1197
1451
|
if (prevRawContentRef.current && newContent && newContent !== prevRawContentRef.current) {
|
|
1198
1452
|
setSyncedCursor(null);
|
|
1199
1453
|
setInputText("");
|
|
1200
|
-
if (showSignInHint) setShowSignInHint(false);
|
|
1201
1454
|
}
|
|
1202
1455
|
prevRawContentRef.current = newContent;
|
|
1203
1456
|
}, [rawScreenData?.content]);
|
|
@@ -1260,11 +1513,9 @@ function GreenScreenTerminal({
|
|
|
1260
1513
|
const newAdapter = new RestAdapter({ baseUrl: `http://${config.host}${port}` });
|
|
1261
1514
|
setInternalAdapter(newAdapter);
|
|
1262
1515
|
await newAdapter.connect(config);
|
|
1263
|
-
if (config.username && config.password) setShowSignInHint(true);
|
|
1264
1516
|
return;
|
|
1265
1517
|
}
|
|
1266
1518
|
await connect(config);
|
|
1267
|
-
if (config.username && config.password) setShowSignInHint(true);
|
|
1268
1519
|
} catch (err) {
|
|
1269
1520
|
setSignInError(err instanceof Error ? err.message : String(err));
|
|
1270
1521
|
setConnecting(false);
|
|
@@ -1352,13 +1603,25 @@ function GreenScreenTerminal({
|
|
|
1352
1603
|
const clickedRow = Math.floor(y / ROW_HEIGHT);
|
|
1353
1604
|
const clickedCol = Math.floor(x / charWidth);
|
|
1354
1605
|
if (clickedRow < 0 || clickedRow >= (screenData.rows || 24) || clickedCol < 0 || clickedCol >= (screenData.cols || 80)) return;
|
|
1606
|
+
const clickedField = screenData.fields.find(
|
|
1607
|
+
(f) => f.row === clickedRow && clickedCol >= f.col && clickedCol < f.col + f.length
|
|
1608
|
+
);
|
|
1609
|
+
const ptrAid = clickedField && clickedField.pointer_aid;
|
|
1610
|
+
if (ptrAid) {
|
|
1611
|
+
const keyName = aidByteToKeyName(ptrAid);
|
|
1612
|
+
if (keyName) {
|
|
1613
|
+
setSyncedCursor({ row: clickedRow, col: clickedCol });
|
|
1614
|
+
adapter.setCursor?.(clickedRow, clickedCol).then(() => sendKey(keyName));
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1355
1618
|
setSyncedCursor({ row: clickedRow, col: clickedCol });
|
|
1356
1619
|
adapter.setCursor?.(clickedRow, clickedCol).then((r) => {
|
|
1357
1620
|
if (r?.cursor_row !== void 0) {
|
|
1358
1621
|
setSyncedCursor({ row: r.cursor_row, col: r.cursor_col });
|
|
1359
1622
|
}
|
|
1360
1623
|
});
|
|
1361
|
-
}, [readOnly, screenData, adapter]);
|
|
1624
|
+
}, [readOnly, screenData, adapter, sendKey]);
|
|
1362
1625
|
const getCurrentField = (0, import_react5.useCallback)(() => {
|
|
1363
1626
|
const fields = screenData?.fields || [];
|
|
1364
1627
|
const cursorRow = syncedCursor?.row ?? screenData?.cursor_row ?? 0;
|
|
@@ -1368,6 +1631,31 @@ function GreenScreenTerminal({
|
|
|
1368
1631
|
}
|
|
1369
1632
|
return null;
|
|
1370
1633
|
}, [screenData, syncedCursor]);
|
|
1634
|
+
const readFieldValue = (0, import_react5.useCallback)((field) => {
|
|
1635
|
+
if (!screenData?.content) return "";
|
|
1636
|
+
const lines = screenData.content.split("\n");
|
|
1637
|
+
const line = lines[field.row] || "";
|
|
1638
|
+
return line.substring(field.col, field.col + field.length).replace(/\s+$/, "");
|
|
1639
|
+
}, [screenData?.content]);
|
|
1640
|
+
const [validationError, setValidationError] = (0, import_react5.useState)(null);
|
|
1641
|
+
const runSelfCheck = (0, import_react5.useCallback)(() => {
|
|
1642
|
+
const fields = screenData?.fields || [];
|
|
1643
|
+
for (const f of fields) {
|
|
1644
|
+
if (!f.is_input) continue;
|
|
1645
|
+
const val = readFieldValue(f);
|
|
1646
|
+
if (!val) continue;
|
|
1647
|
+
if (f.self_check_mod10 && !validateMod10(val)) {
|
|
1648
|
+
setValidationError(`Invalid check digit (MOD10) in field at row ${f.row + 1}, col ${f.col + 1}`);
|
|
1649
|
+
return false;
|
|
1650
|
+
}
|
|
1651
|
+
if (f.self_check_mod11 && !validateMod11(val)) {
|
|
1652
|
+
setValidationError(`Invalid check digit (MOD11) in field at row ${f.row + 1}, col ${f.col + 1}`);
|
|
1653
|
+
return false;
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
setValidationError(null);
|
|
1657
|
+
return true;
|
|
1658
|
+
}, [screenData?.fields, readFieldValue]);
|
|
1371
1659
|
const handleKeyDown = async (e) => {
|
|
1372
1660
|
if (readOnly) {
|
|
1373
1661
|
e.preventDefault();
|
|
@@ -1406,18 +1694,19 @@ function GreenScreenTerminal({
|
|
|
1406
1694
|
End: "END",
|
|
1407
1695
|
Insert: "INSERT"
|
|
1408
1696
|
};
|
|
1409
|
-
if (
|
|
1697
|
+
if (/^F([1-9]|1[0-9]|2[0-4])$/.test(e.key)) {
|
|
1410
1698
|
e.preventDefault();
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
return;
|
|
1416
|
-
}
|
|
1699
|
+
if (!runSelfCheck()) return;
|
|
1700
|
+
const kr = await sendKey(e.key);
|
|
1701
|
+
if (kr.cursor_row !== void 0) setSyncedCursor({ row: kr.cursor_row, col: kr.cursor_col });
|
|
1702
|
+
return;
|
|
1417
1703
|
}
|
|
1418
1704
|
if (keyMap[e.key]) {
|
|
1419
1705
|
e.preventDefault();
|
|
1420
|
-
const
|
|
1706
|
+
const k = keyMap[e.key];
|
|
1707
|
+
const isSubmit = k === "ENTER" || k === "PAGEUP" || k === "PAGEDOWN";
|
|
1708
|
+
if (isSubmit && !runSelfCheck()) return;
|
|
1709
|
+
const kr = await sendKey(k);
|
|
1421
1710
|
if (kr.cursor_row !== void 0) setSyncedCursor({ row: kr.cursor_row, col: kr.cursor_col });
|
|
1422
1711
|
}
|
|
1423
1712
|
};
|
|
@@ -1428,18 +1717,32 @@ function GreenScreenTerminal({
|
|
|
1428
1717
|
}
|
|
1429
1718
|
const newText = e.target.value;
|
|
1430
1719
|
if (newText.length > inputText.length) {
|
|
1431
|
-
|
|
1432
|
-
const
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1720
|
+
let newChars = newText.substring(inputText.length);
|
|
1721
|
+
const curField = getCurrentField();
|
|
1722
|
+
if (curField && (curField.shift_type || curField.monocase)) {
|
|
1723
|
+
const curColAbs = syncedCursor?.col ?? screenData?.cursor_col ?? 0;
|
|
1724
|
+
const startOffset = Math.max(0, curColAbs - curField.col);
|
|
1725
|
+
const { out, rejected } = filterFieldInput(curField, newChars, startOffset);
|
|
1726
|
+
if (rejected) {
|
|
1727
|
+
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";
|
|
1728
|
+
setValidationError(`Field accepts ${what}`);
|
|
1729
|
+
setTimeout(() => setValidationError(null), 1500);
|
|
1730
|
+
}
|
|
1731
|
+
newChars = out;
|
|
1732
|
+
}
|
|
1733
|
+
if (newChars.length > 0) {
|
|
1734
|
+
const curRow = syncedCursor?.row ?? screenData?.cursor_row ?? 0;
|
|
1735
|
+
const curCol = syncedCursor?.col ?? screenData?.cursor_col ?? 0;
|
|
1736
|
+
const edits = [];
|
|
1737
|
+
for (let i = 0; i < newChars.length; i++) {
|
|
1738
|
+
edits.push({ row: curRow, col: curCol + i, ch: newChars[i] });
|
|
1739
|
+
}
|
|
1740
|
+
setOptimisticEdits((prev) => [...prev, ...edits]);
|
|
1741
|
+
setSyncedCursor({ row: curRow, col: curCol + newChars.length });
|
|
1742
|
+
sendText(newChars).then((r) => {
|
|
1743
|
+
if (r.cursor_row !== void 0) setSyncedCursor({ row: r.cursor_row, col: r.cursor_col });
|
|
1744
|
+
});
|
|
1437
1745
|
}
|
|
1438
|
-
setOptimisticEdits((prev) => [...prev, ...edits]);
|
|
1439
|
-
setSyncedCursor({ row: curRow, col: curCol + newChars.length });
|
|
1440
|
-
sendText(newChars).then((r) => {
|
|
1441
|
-
if (r.cursor_row !== void 0) setSyncedCursor({ row: r.cursor_row, col: r.cursor_col });
|
|
1442
|
-
});
|
|
1443
1746
|
}
|
|
1444
1747
|
setInputText("");
|
|
1445
1748
|
e.target.value = "";
|
|
@@ -1471,41 +1774,156 @@ function GreenScreenTerminal({
|
|
|
1471
1774
|
if (lastIndex < text.length) segments.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: text.substring(lastIndex) }, `${keyPrefix}-e`));
|
|
1472
1775
|
return segments.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: segments }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: text });
|
|
1473
1776
|
}, []);
|
|
1474
|
-
const renderRowWithFields = (0, import_react5.useCallback)((line, rowIndex, fields) => {
|
|
1777
|
+
const renderRowWithFields = (0, import_react5.useCallback)((line, rowIndex, fields, cursorRow, cursorCol) => {
|
|
1475
1778
|
const inputFields = fields.filter((f) => f.row === rowIndex && f.is_input);
|
|
1476
1779
|
const highlightedFields = fields.filter((f) => f.row === rowIndex && f.is_protected && f.is_highlighted);
|
|
1477
1780
|
const reverseFields = fields.filter((f) => f.row === rowIndex && f.is_protected && f.is_reverse);
|
|
1478
1781
|
const colorFields = fields.filter((f) => f.row === rowIndex && f.is_protected && f.color && !f.is_highlighted && !f.is_reverse);
|
|
1479
1782
|
const allRowFields = [...inputFields, ...highlightedFields, ...reverseFields, ...colorFields];
|
|
1480
|
-
|
|
1783
|
+
const cols = screenData?.cols || profile.defaultCols;
|
|
1784
|
+
const extAttrs = screenData?.ext_attrs;
|
|
1785
|
+
const dbcsCont = screenData?.dbcs_cont;
|
|
1786
|
+
const dbcsContSet = dbcsCont && dbcsCont.length > 0 ? new Set(dbcsCont) : null;
|
|
1787
|
+
const hasExtOnRow = !!extAttrs && (() => {
|
|
1788
|
+
for (let c = 0; c < cols; c++) {
|
|
1789
|
+
if (extAttrs[rowIndex * cols + c]) return true;
|
|
1790
|
+
}
|
|
1791
|
+
return false;
|
|
1792
|
+
})();
|
|
1793
|
+
const hasDbcsOnRow = !!dbcsContSet && (() => {
|
|
1794
|
+
for (let c = 0; c < cols; c++) {
|
|
1795
|
+
if (dbcsContSet.has(rowIndex * cols + c)) return true;
|
|
1796
|
+
}
|
|
1797
|
+
return false;
|
|
1798
|
+
})();
|
|
1799
|
+
if (allRowFields.length === 0 && !hasExtOnRow && !hasDbcsOnRow) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: line });
|
|
1481
1800
|
const sorted = [...allRowFields].sort((a, b) => a.col - b.col);
|
|
1482
1801
|
const segs = [];
|
|
1483
1802
|
let lastEnd = 0;
|
|
1484
|
-
const
|
|
1803
|
+
const renderPlainRun = (runStart, runEnd, keyPrefix) => {
|
|
1804
|
+
if (runStart >= runEnd) return;
|
|
1805
|
+
if (!hasExtOnRow && !hasDbcsOnRow) {
|
|
1806
|
+
segs.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: line.substring(runStart, runEnd) }, keyPrefix));
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1809
|
+
let pos = runStart;
|
|
1810
|
+
while (pos < runEnd) {
|
|
1811
|
+
const addr = rowIndex * cols + pos;
|
|
1812
|
+
const ext = extAttrs?.[addr];
|
|
1813
|
+
const isContCell = dbcsContSet?.has(addr);
|
|
1814
|
+
if (isContCell) {
|
|
1815
|
+
segs.push(
|
|
1816
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1817
|
+
"span",
|
|
1818
|
+
{
|
|
1819
|
+
style: { display: "inline-block", width: "1ch" },
|
|
1820
|
+
"aria-hidden": "true"
|
|
1821
|
+
},
|
|
1822
|
+
`${keyPrefix}-dc${pos}`
|
|
1823
|
+
)
|
|
1824
|
+
);
|
|
1825
|
+
pos++;
|
|
1826
|
+
continue;
|
|
1827
|
+
}
|
|
1828
|
+
if (ext) {
|
|
1829
|
+
const color = ext.color !== void 0 ? decodeExtColor(ext.color) : void 0;
|
|
1830
|
+
const colorReverse = ext.color !== void 0 && extColorIsReverse(ext.color);
|
|
1831
|
+
const hl = ext.highlight !== void 0 ? decodeExtHighlight(ext.highlight) : void 0;
|
|
1832
|
+
const style2 = {};
|
|
1833
|
+
if (color) style2.color = cssVarForColor(color);
|
|
1834
|
+
if (colorReverse) {
|
|
1835
|
+
style2.background = cssVarForColor(color);
|
|
1836
|
+
style2.color = "#000";
|
|
1837
|
+
}
|
|
1838
|
+
if (hl?.reverse) {
|
|
1839
|
+
style2.background = "currentColor";
|
|
1840
|
+
style2.color = "#000";
|
|
1841
|
+
}
|
|
1842
|
+
if (hl?.underscore) style2.textDecoration = "underline";
|
|
1843
|
+
if (hl?.blink) style2.animation = "gs-blink 1s steps(2, start) infinite";
|
|
1844
|
+
segs.push(
|
|
1845
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: style2, children: line[pos] }, `${keyPrefix}-x${pos}`)
|
|
1846
|
+
);
|
|
1847
|
+
pos++;
|
|
1848
|
+
continue;
|
|
1849
|
+
}
|
|
1850
|
+
let runEndPlain = pos + 1;
|
|
1851
|
+
while (runEndPlain < runEnd) {
|
|
1852
|
+
const a = rowIndex * cols + runEndPlain;
|
|
1853
|
+
if (extAttrs?.[a] || dbcsContSet?.has(a)) break;
|
|
1854
|
+
runEndPlain++;
|
|
1855
|
+
}
|
|
1856
|
+
segs.push(
|
|
1857
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: line.substring(pos, runEndPlain) }, `${keyPrefix}-p${pos}`)
|
|
1858
|
+
);
|
|
1859
|
+
pos = runEndPlain;
|
|
1860
|
+
}
|
|
1861
|
+
};
|
|
1485
1862
|
sorted.forEach((field, idx) => {
|
|
1486
1863
|
const fs = field.col;
|
|
1487
1864
|
const fe = Math.min(field.col + field.length, cols);
|
|
1488
|
-
if (fs > lastEnd)
|
|
1865
|
+
if (fs > lastEnd) renderPlainRun(lastEnd, fs, `t${idx}`);
|
|
1489
1866
|
const fc = line.substring(fs, fe);
|
|
1490
|
-
const
|
|
1867
|
+
const cursorInField = cursorRow === field.row && cursorCol >= fs && cursorCol < field.col + field.length;
|
|
1868
|
+
const entryAttr = cursorInField && field.highlight_entry_attr !== void 0 ? decodeAttrByte(field.highlight_entry_attr) : null;
|
|
1869
|
+
const baseColor = entryAttr?.color ?? field.color;
|
|
1870
|
+
const colorVar = baseColor ? cssVarForColor(baseColor) : void 0;
|
|
1871
|
+
const fieldStyle = {};
|
|
1872
|
+
if (colorVar) fieldStyle.color = colorVar;
|
|
1873
|
+
if (entryAttr?.reverse) {
|
|
1874
|
+
fieldStyle.background = "currentColor";
|
|
1875
|
+
fieldStyle.color = "#000";
|
|
1876
|
+
}
|
|
1877
|
+
if (entryAttr?.underscore) fieldStyle.textDecoration = "underline";
|
|
1878
|
+
if (entryAttr?.highIntensity) fieldStyle.fontWeight = "bold";
|
|
1879
|
+
const isPassword = field.is_non_display;
|
|
1880
|
+
const displayText = isPassword ? " ".repeat(fc.length) : fc;
|
|
1491
1881
|
if (field.is_input) {
|
|
1492
1882
|
const fieldWidth = Math.min(field.length, cols - fs);
|
|
1493
|
-
const fieldClass =
|
|
1494
|
-
|
|
1883
|
+
const fieldClass = field.is_underscored ? "gs-input-field" : void 0;
|
|
1884
|
+
const extra = [];
|
|
1885
|
+
if (field.is_dbcs) extra.push("gs-dbcs-field");
|
|
1886
|
+
if (cursorInField) extra.push("gs-field-active");
|
|
1887
|
+
const composedClass = [fieldClass, ...extra].filter(Boolean).join(" ") || void 0;
|
|
1888
|
+
segs.push(
|
|
1889
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1890
|
+
"span",
|
|
1891
|
+
{
|
|
1892
|
+
className: composedClass,
|
|
1893
|
+
style: {
|
|
1894
|
+
display: "inline-block",
|
|
1895
|
+
width: `${fieldWidth}ch`,
|
|
1896
|
+
overflow: "hidden",
|
|
1897
|
+
...fieldStyle
|
|
1898
|
+
},
|
|
1899
|
+
children: displayText
|
|
1900
|
+
},
|
|
1901
|
+
`f${idx}`
|
|
1902
|
+
)
|
|
1903
|
+
);
|
|
1495
1904
|
} else if (field.is_reverse) {
|
|
1496
|
-
segs.push(
|
|
1497
|
-
|
|
1498
|
-
|
|
1905
|
+
segs.push(
|
|
1906
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1907
|
+
"span",
|
|
1908
|
+
{
|
|
1909
|
+
style: { color: colorVar || "var(--gs-red, #FF5555)", fontWeight: "bold", ...fieldStyle },
|
|
1910
|
+
children: displayText
|
|
1911
|
+
},
|
|
1912
|
+
`v${idx}`
|
|
1913
|
+
)
|
|
1914
|
+
);
|
|
1915
|
+
} else if (colorVar || entryAttr) {
|
|
1916
|
+
segs.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: fieldStyle, children: displayText }, `h${idx}`));
|
|
1499
1917
|
} else if (field.is_highlighted) {
|
|
1500
|
-
segs.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { color: "var(--gs-white, #FFFFFF)" }, children:
|
|
1918
|
+
segs.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { color: "var(--gs-white, #FFFFFF)" }, children: displayText }, `h${idx}`));
|
|
1501
1919
|
} else {
|
|
1502
|
-
segs.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children:
|
|
1920
|
+
segs.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: displayText }, `h${idx}`));
|
|
1503
1921
|
}
|
|
1504
1922
|
lastEnd = fe;
|
|
1505
1923
|
});
|
|
1506
|
-
if (lastEnd < line.length)
|
|
1924
|
+
if (lastEnd < line.length) renderPlainRun(lastEnd, line.length, "te");
|
|
1507
1925
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: segs });
|
|
1508
|
-
}, [renderTextWithUnderlines, screenData
|
|
1926
|
+
}, [renderTextWithUnderlines, screenData, profile.defaultCols]);
|
|
1509
1927
|
const renderScreen = () => {
|
|
1510
1928
|
if (showBootLoader && !screenData?.content) {
|
|
1511
1929
|
if (bootLoader === false) return null;
|
|
@@ -1522,7 +1940,7 @@ function GreenScreenTerminal({
|
|
|
1522
1940
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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" }),
|
|
1523
1941
|
!connStatus?.connected && isUsingDefaultAdapter && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { style: { fontFamily: "var(--gs-font)", fontSize: "11px", color: "#606060", marginTop: "8px" }, children: [
|
|
1524
1942
|
"Start the proxy: ",
|
|
1525
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("code", { style: { color: "#10b981" }, children: "npx green-screen-proxy
|
|
1943
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("code", { style: { color: "#10b981" }, children: "npx green-screen-proxy" })
|
|
1526
1944
|
] })
|
|
1527
1945
|
] }) });
|
|
1528
1946
|
}
|
|
@@ -1555,11 +1973,33 @@ function GreenScreenTerminal({
|
|
|
1555
1973
|
}
|
|
1556
1974
|
const headerSegments = index === 0 ? profile.colors.parseHeaderRow(displayLine) : null;
|
|
1557
1975
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: headerSegments ? "" : profile.colors.getRowColorClass(index, displayLine, termRows), style: { height: `${ROW_HEIGHT}px`, lineHeight: `${ROW_HEIGHT}px`, whiteSpace: "pre", position: "relative" }, children: [
|
|
1558
|
-
headerSegments ? headerSegments.map((seg, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: seg.colorClass, children: seg.text }, i)) : renderRowWithFields(displayLine, index, fields),
|
|
1559
|
-
cursorInInputField &&
|
|
1976
|
+
headerSegments ? headerSegments.map((seg, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: seg.colorClass, children: seg.text }, i)) : renderRowWithFields(displayLine, index, fields, cursor.row, cursor.col),
|
|
1977
|
+
cursorInInputField && index === cursor.row && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "gs-cursor", style: { position: "absolute", left: `${cursor.col}ch`, width: "1ch", height: `${ROW_HEIGHT}px`, top: 0, pointerEvents: "none" } })
|
|
1560
1978
|
] }, index);
|
|
1561
1979
|
}),
|
|
1562
|
-
|
|
1980
|
+
validationError && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
1981
|
+
"div",
|
|
1982
|
+
{
|
|
1983
|
+
role: "alert",
|
|
1984
|
+
style: {
|
|
1985
|
+
position: "absolute",
|
|
1986
|
+
left: 0,
|
|
1987
|
+
right: 0,
|
|
1988
|
+
bottom: 0,
|
|
1989
|
+
padding: "2px 6px",
|
|
1990
|
+
background: "var(--gs-red, #FF5555)",
|
|
1991
|
+
color: "#000",
|
|
1992
|
+
fontFamily: "var(--gs-font)",
|
|
1993
|
+
fontSize: "12px",
|
|
1994
|
+
zIndex: 10
|
|
1995
|
+
},
|
|
1996
|
+
onClick: () => setValidationError(null),
|
|
1997
|
+
children: [
|
|
1998
|
+
validationError,
|
|
1999
|
+
" \u2014 click to dismiss"
|
|
2000
|
+
]
|
|
2001
|
+
}
|
|
2002
|
+
),
|
|
1563
2003
|
screenData.cursor_row !== void 0 && screenData.cursor_col !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { style: { position: "absolute", bottom: 0, right: 0, fontFamily: "var(--gs-font)", fontSize: "10px", color: "var(--gs-green, #10b981)", pointerEvents: "none", opacity: 0.6 }, children: [
|
|
1564
2004
|
String(screenData.cursor_row + 1).padStart(2, "0"),
|
|
1565
2005
|
"/",
|
|
@@ -1590,6 +2030,15 @@ function GreenScreenTerminal({
|
|
|
1590
2030
|
return "#64748b";
|
|
1591
2031
|
}
|
|
1592
2032
|
};
|
|
2033
|
+
const handleShortcutSend = (0, import_react5.useCallback)(async (key) => {
|
|
2034
|
+
if (readOnly) return;
|
|
2035
|
+
const isSubmit = key === "ENTER" || key === "PAGEUP" || key === "PAGEDOWN" || /^F([1-9]|1[0-9]|2[0-4])$/.test(key);
|
|
2036
|
+
if (isSubmit && !runSelfCheck()) return;
|
|
2037
|
+
setIsFocused(true);
|
|
2038
|
+
inputRef.current?.focus();
|
|
2039
|
+
const kr = await sendKey(key);
|
|
2040
|
+
if (kr.cursor_row !== void 0) setSyncedCursor({ row: kr.cursor_row, col: kr.cursor_col });
|
|
2041
|
+
}, [readOnly, runSelfCheck, sendKey]);
|
|
1593
2042
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `gs-terminal ${isFocused ? "gs-terminal-focused" : ""} ${className || ""}`, style, children: [
|
|
1594
2043
|
showHeader && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "gs-header", children: embedded ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
1595
2044
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "gs-header-left", children: [
|
|
@@ -1609,10 +2058,10 @@ function GreenScreenTerminal({
|
|
|
1609
2058
|
e.stopPropagation();
|
|
1610
2059
|
onMinimize();
|
|
1611
2060
|
}, className: "gs-btn-icon", title: "Minimize terminal", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MinimizeIcon, {}) }),
|
|
1612
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: (e) => {
|
|
2061
|
+
showShortcutsButton && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: (e) => {
|
|
1613
2062
|
e.stopPropagation();
|
|
1614
2063
|
setShowShortcuts((s) => !s);
|
|
1615
|
-
}, className: "gs-btn-icon", title: "Keyboard shortcuts", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2064
|
+
}, className: "gs-btn-icon", title: "Keyboard shortcuts", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(KeyboardIcon, { size: 12 }) }),
|
|
1616
2065
|
headerRight
|
|
1617
2066
|
] })
|
|
1618
2067
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
@@ -1639,10 +2088,10 @@ function GreenScreenTerminal({
|
|
|
1639
2088
|
connStatus.username && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "gs-host", children: connStatus.username })
|
|
1640
2089
|
] }),
|
|
1641
2090
|
statusActions,
|
|
1642
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: (e) => {
|
|
2091
|
+
showShortcutsButton && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: (e) => {
|
|
1643
2092
|
e.stopPropagation();
|
|
1644
2093
|
setShowShortcuts((s) => !s);
|
|
1645
|
-
}, className: "gs-btn-icon", title: "Keyboard shortcuts", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2094
|
+
}, className: "gs-btn-icon", title: "Keyboard shortcuts", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(KeyboardIcon, { size: 12 }) }),
|
|
1646
2095
|
headerRight
|
|
1647
2096
|
] })
|
|
1648
2097
|
] }) }),
|
|
@@ -1663,29 +2112,47 @@ function GreenScreenTerminal({
|
|
|
1663
2112
|
showShortcuts && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "gs-shortcuts-panel", children: [
|
|
1664
2113
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "gs-shortcuts-header", children: [
|
|
1665
2114
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Keyboard Shortcuts" }),
|
|
1666
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: "gs-btn-icon", onClick: () => setShowShortcuts(false),
|
|
2115
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: "gs-btn-icon", onClick: () => setShowShortcuts(false), children: "\xD7" })
|
|
1667
2116
|
] }),
|
|
2117
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "gs-shortcuts-section-title", children: "Actions" }),
|
|
2118
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("table", { className: "gs-shortcuts-table", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("tbody", { children: [
|
|
2119
|
+
["Enter", "Submit", "ENTER"],
|
|
2120
|
+
["Tab", "Next field", "TAB"],
|
|
2121
|
+
["Backspace", "Backspace", "BACKSPACE"],
|
|
2122
|
+
["Delete", "Delete", "DELETE"],
|
|
2123
|
+
["Insert", "Insert / Overwrite", "INSERT"],
|
|
2124
|
+
["Home", "Home", "HOME"],
|
|
2125
|
+
["End", "End", "END"],
|
|
2126
|
+
["Page Up", "Roll Down", "PAGEUP"],
|
|
2127
|
+
["Page Down", "Roll Up", "PAGEDOWN"],
|
|
2128
|
+
["Ctrl+Enter", "Field Exit", "FIELD_EXIT"],
|
|
2129
|
+
["Ctrl+R", "Reset", "RESET"],
|
|
2130
|
+
["\u2014", "Help", "HELP"],
|
|
2131
|
+
["\u2014", "Clear", "CLEAR"],
|
|
2132
|
+
["\u2014", "Print", "PRINT"]
|
|
2133
|
+
].map(([label, desc, key]) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { className: "gs-shortcut-row", onClick: (e) => {
|
|
2134
|
+
e.stopPropagation();
|
|
2135
|
+
handleShortcutSend(key);
|
|
2136
|
+
}, children: [
|
|
2137
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { className: "gs-shortcut-key", children: label }),
|
|
2138
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: desc })
|
|
2139
|
+
] }, key)) }) }),
|
|
2140
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "gs-shortcuts-section-title", children: "Function keys" }),
|
|
2141
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "gs-shortcuts-fkeys", children: Array.from({ length: 24 }, (_, i) => `F${i + 1}`).map((fk) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2142
|
+
"button",
|
|
2143
|
+
{
|
|
2144
|
+
className: "gs-shortcut-fkey",
|
|
2145
|
+
onClick: (e) => {
|
|
2146
|
+
e.stopPropagation();
|
|
2147
|
+
handleShortcutSend(fk);
|
|
2148
|
+
},
|
|
2149
|
+
title: `Send ${fk}`,
|
|
2150
|
+
children: fk
|
|
2151
|
+
},
|
|
2152
|
+
fk
|
|
2153
|
+
)) }),
|
|
2154
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "gs-shortcuts-section-title", children: "Info" }),
|
|
1668
2155
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("table", { className: "gs-shortcuts-table", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tbody", { children: [
|
|
1669
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { children: [
|
|
1670
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { className: "gs-shortcut-key", children: "Ctrl+Enter" }),
|
|
1671
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: "Field Exit" })
|
|
1672
|
-
] }),
|
|
1673
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { children: [
|
|
1674
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { className: "gs-shortcut-key", children: "Ctrl+R" }),
|
|
1675
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: "Reset" })
|
|
1676
|
-
] }),
|
|
1677
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { children: [
|
|
1678
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { className: "gs-shortcut-key", children: "Insert" }),
|
|
1679
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: "Insert / Overwrite" })
|
|
1680
|
-
] }),
|
|
1681
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { children: [
|
|
1682
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { className: "gs-shortcut-key", children: "Page Up" }),
|
|
1683
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: "Roll Down" })
|
|
1684
|
-
] }),
|
|
1685
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { children: [
|
|
1686
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { className: "gs-shortcut-key", children: "Page Down" }),
|
|
1687
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: "Roll Up" })
|
|
1688
|
-
] }),
|
|
1689
2156
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { children: [
|
|
1690
2157
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { className: "gs-shortcut-key", children: "Click" }),
|
|
1691
2158
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: "Focus / Position cursor" })
|
|
@@ -1713,7 +2180,19 @@ function GreenScreenTerminal({
|
|
|
1713
2180
|
autoComplete: "off",
|
|
1714
2181
|
autoCorrect: "off",
|
|
1715
2182
|
autoCapitalize: "off",
|
|
1716
|
-
spellCheck: false
|
|
2183
|
+
spellCheck: false,
|
|
2184
|
+
lang: (() => {
|
|
2185
|
+
const f = getCurrentField();
|
|
2186
|
+
if (!f) return void 0;
|
|
2187
|
+
if (f.is_dbcs || f.is_dbcs_either) return "ja";
|
|
2188
|
+
return void 0;
|
|
2189
|
+
})(),
|
|
2190
|
+
inputMode: (() => {
|
|
2191
|
+
const f = getCurrentField();
|
|
2192
|
+
if (!f) return void 0;
|
|
2193
|
+
if (f.is_dbcs) return "text";
|
|
2194
|
+
return "text";
|
|
2195
|
+
})()
|
|
1717
2196
|
}
|
|
1718
2197
|
)
|
|
1719
2198
|
]
|