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.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,14 +1377,16 @@ function GreenScreenTerminal({
|
|
|
1117
1377
|
inlineSignIn = true,
|
|
1118
1378
|
defaultProtocol: signInDefaultProtocol,
|
|
1119
1379
|
onSignIn,
|
|
1120
|
-
autoSignedIn,
|
|
1121
1380
|
autoFocusDisabled = false,
|
|
1122
1381
|
bootLoader,
|
|
1382
|
+
bootLoaderReady,
|
|
1123
1383
|
headerRight,
|
|
1384
|
+
statusActions,
|
|
1124
1385
|
overlay,
|
|
1125
1386
|
onNotification,
|
|
1126
1387
|
onScreenChange,
|
|
1127
1388
|
onMinimize,
|
|
1389
|
+
showShortcutsButton = true,
|
|
1128
1390
|
className,
|
|
1129
1391
|
style
|
|
1130
1392
|
}) {
|
|
@@ -1169,22 +1431,17 @@ function GreenScreenTerminal({
|
|
|
1169
1431
|
const sendText = (0, import_react5.useCallback)(async (text) => _sendText(text), [_sendText]);
|
|
1170
1432
|
const sendKey = (0, import_react5.useCallback)(async (key) => _sendKey(key), [_sendKey]);
|
|
1171
1433
|
const [optimisticEdits, setOptimisticEdits] = (0, import_react5.useState)([]);
|
|
1172
|
-
const
|
|
1434
|
+
const prevScreenContentForEdits = (0, import_react5.useRef)(void 0);
|
|
1173
1435
|
(0, import_react5.useEffect)(() => {
|
|
1174
|
-
|
|
1175
|
-
|
|
1436
|
+
const content = rawScreenData?.content;
|
|
1437
|
+
if (content && content !== prevScreenContentForEdits.current) {
|
|
1438
|
+
prevScreenContentForEdits.current = content;
|
|
1176
1439
|
setOptimisticEdits([]);
|
|
1177
1440
|
}
|
|
1178
|
-
}, [rawScreenData?.
|
|
1441
|
+
}, [rawScreenData?.content]);
|
|
1179
1442
|
const [inputText, setInputText] = (0, import_react5.useState)("");
|
|
1180
1443
|
const [isFocused, setIsFocused] = (0, import_react5.useState)(false);
|
|
1181
|
-
const [showSignInHint, setShowSignInHint] = (0, import_react5.useState)(false);
|
|
1182
1444
|
const [showShortcuts, setShowShortcuts] = (0, import_react5.useState)(false);
|
|
1183
|
-
const prevAutoSignedIn = (0, import_react5.useRef)(false);
|
|
1184
|
-
(0, import_react5.useEffect)(() => {
|
|
1185
|
-
if (autoSignedIn && !prevAutoSignedIn.current) setShowSignInHint(true);
|
|
1186
|
-
prevAutoSignedIn.current = !!autoSignedIn;
|
|
1187
|
-
}, [autoSignedIn]);
|
|
1188
1445
|
const terminalRef = (0, import_react5.useRef)(null);
|
|
1189
1446
|
const inputRef = (0, import_react5.useRef)(null);
|
|
1190
1447
|
const [syncedCursor, setSyncedCursor] = (0, import_react5.useState)(null);
|
|
@@ -1194,7 +1451,6 @@ function GreenScreenTerminal({
|
|
|
1194
1451
|
if (prevRawContentRef.current && newContent && newContent !== prevRawContentRef.current) {
|
|
1195
1452
|
setSyncedCursor(null);
|
|
1196
1453
|
setInputText("");
|
|
1197
|
-
if (showSignInHint) setShowSignInHint(false);
|
|
1198
1454
|
}
|
|
1199
1455
|
prevRawContentRef.current = newContent;
|
|
1200
1456
|
}, [rawScreenData?.content]);
|
|
@@ -1257,11 +1513,9 @@ function GreenScreenTerminal({
|
|
|
1257
1513
|
const newAdapter = new RestAdapter({ baseUrl: `http://${config.host}${port}` });
|
|
1258
1514
|
setInternalAdapter(newAdapter);
|
|
1259
1515
|
await newAdapter.connect(config);
|
|
1260
|
-
if (config.username && config.password) setShowSignInHint(true);
|
|
1261
1516
|
return;
|
|
1262
1517
|
}
|
|
1263
1518
|
await connect(config);
|
|
1264
|
-
if (config.username && config.password) setShowSignInHint(true);
|
|
1265
1519
|
} catch (err) {
|
|
1266
1520
|
setSignInError(err instanceof Error ? err.message : String(err));
|
|
1267
1521
|
setConnecting(false);
|
|
@@ -1273,13 +1527,15 @@ function GreenScreenTerminal({
|
|
|
1273
1527
|
const [showBootLoader, setShowBootLoader] = (0, import_react5.useState)(bootLoader !== false);
|
|
1274
1528
|
const [bootFadingOut, setBootFadingOut] = (0, import_react5.useState)(false);
|
|
1275
1529
|
(0, import_react5.useEffect)(() => {
|
|
1276
|
-
if (
|
|
1530
|
+
if (!showBootLoader) return;
|
|
1531
|
+
const shouldDismiss = bootLoaderReady !== void 0 ? bootLoaderReady : !!screenData?.content;
|
|
1532
|
+
if (shouldDismiss) {
|
|
1277
1533
|
setBootFadingOut(true);
|
|
1278
1534
|
setShowBootLoader(false);
|
|
1279
1535
|
const timer = setTimeout(() => setBootFadingOut(false), 400);
|
|
1280
1536
|
return () => clearTimeout(timer);
|
|
1281
1537
|
}
|
|
1282
|
-
}, [screenData?.content, showBootLoader]);
|
|
1538
|
+
}, [screenData?.content, showBootLoader, bootLoaderReady]);
|
|
1283
1539
|
const FOCUS_STORAGE_KEY = "gs-terminal-focused";
|
|
1284
1540
|
(0, import_react5.useEffect)(() => {
|
|
1285
1541
|
if (!autoFocusDisabled && !readOnly) {
|
|
@@ -1347,13 +1603,25 @@ function GreenScreenTerminal({
|
|
|
1347
1603
|
const clickedRow = Math.floor(y / ROW_HEIGHT);
|
|
1348
1604
|
const clickedCol = Math.floor(x / charWidth);
|
|
1349
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
|
+
}
|
|
1350
1618
|
setSyncedCursor({ row: clickedRow, col: clickedCol });
|
|
1351
1619
|
adapter.setCursor?.(clickedRow, clickedCol).then((r) => {
|
|
1352
1620
|
if (r?.cursor_row !== void 0) {
|
|
1353
1621
|
setSyncedCursor({ row: r.cursor_row, col: r.cursor_col });
|
|
1354
1622
|
}
|
|
1355
1623
|
});
|
|
1356
|
-
}, [readOnly, screenData, adapter]);
|
|
1624
|
+
}, [readOnly, screenData, adapter, sendKey]);
|
|
1357
1625
|
const getCurrentField = (0, import_react5.useCallback)(() => {
|
|
1358
1626
|
const fields = screenData?.fields || [];
|
|
1359
1627
|
const cursorRow = syncedCursor?.row ?? screenData?.cursor_row ?? 0;
|
|
@@ -1363,6 +1631,31 @@ function GreenScreenTerminal({
|
|
|
1363
1631
|
}
|
|
1364
1632
|
return null;
|
|
1365
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]);
|
|
1366
1659
|
const handleKeyDown = async (e) => {
|
|
1367
1660
|
if (readOnly) {
|
|
1368
1661
|
e.preventDefault();
|
|
@@ -1401,18 +1694,19 @@ function GreenScreenTerminal({
|
|
|
1401
1694
|
End: "END",
|
|
1402
1695
|
Insert: "INSERT"
|
|
1403
1696
|
};
|
|
1404
|
-
if (
|
|
1697
|
+
if (/^F([1-9]|1[0-9]|2[0-4])$/.test(e.key)) {
|
|
1405
1698
|
e.preventDefault();
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
return;
|
|
1411
|
-
}
|
|
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;
|
|
1412
1703
|
}
|
|
1413
1704
|
if (keyMap[e.key]) {
|
|
1414
1705
|
e.preventDefault();
|
|
1415
|
-
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);
|
|
1416
1710
|
if (kr.cursor_row !== void 0) setSyncedCursor({ row: kr.cursor_row, col: kr.cursor_col });
|
|
1417
1711
|
}
|
|
1418
1712
|
};
|
|
@@ -1423,18 +1717,32 @@ function GreenScreenTerminal({
|
|
|
1423
1717
|
}
|
|
1424
1718
|
const newText = e.target.value;
|
|
1425
1719
|
if (newText.length > inputText.length) {
|
|
1426
|
-
|
|
1427
|
-
const
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
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
|
+
});
|
|
1432
1745
|
}
|
|
1433
|
-
setOptimisticEdits((prev) => [...prev, ...edits]);
|
|
1434
|
-
setSyncedCursor({ row: curRow, col: curCol + newChars.length });
|
|
1435
|
-
sendText(newChars).then((r) => {
|
|
1436
|
-
if (r.cursor_row !== void 0) setSyncedCursor({ row: r.cursor_row, col: r.cursor_col });
|
|
1437
|
-
});
|
|
1438
1746
|
}
|
|
1439
1747
|
setInputText("");
|
|
1440
1748
|
e.target.value = "";
|
|
@@ -1466,41 +1774,156 @@ function GreenScreenTerminal({
|
|
|
1466
1774
|
if (lastIndex < text.length) segments.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: text.substring(lastIndex) }, `${keyPrefix}-e`));
|
|
1467
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 });
|
|
1468
1776
|
}, []);
|
|
1469
|
-
const renderRowWithFields = (0, import_react5.useCallback)((line, rowIndex, fields) => {
|
|
1777
|
+
const renderRowWithFields = (0, import_react5.useCallback)((line, rowIndex, fields, cursorRow, cursorCol) => {
|
|
1470
1778
|
const inputFields = fields.filter((f) => f.row === rowIndex && f.is_input);
|
|
1471
1779
|
const highlightedFields = fields.filter((f) => f.row === rowIndex && f.is_protected && f.is_highlighted);
|
|
1472
1780
|
const reverseFields = fields.filter((f) => f.row === rowIndex && f.is_protected && f.is_reverse);
|
|
1473
1781
|
const colorFields = fields.filter((f) => f.row === rowIndex && f.is_protected && f.color && !f.is_highlighted && !f.is_reverse);
|
|
1474
1782
|
const allRowFields = [...inputFields, ...highlightedFields, ...reverseFields, ...colorFields];
|
|
1475
|
-
|
|
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 });
|
|
1476
1800
|
const sorted = [...allRowFields].sort((a, b) => a.col - b.col);
|
|
1477
1801
|
const segs = [];
|
|
1478
1802
|
let lastEnd = 0;
|
|
1479
|
-
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
|
+
};
|
|
1480
1862
|
sorted.forEach((field, idx) => {
|
|
1481
1863
|
const fs = field.col;
|
|
1482
1864
|
const fe = Math.min(field.col + field.length, cols);
|
|
1483
|
-
if (fs > lastEnd)
|
|
1865
|
+
if (fs > lastEnd) renderPlainRun(lastEnd, fs, `t${idx}`);
|
|
1484
1866
|
const fc = line.substring(fs, fe);
|
|
1485
|
-
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;
|
|
1486
1881
|
if (field.is_input) {
|
|
1487
1882
|
const fieldWidth = Math.min(field.length, cols - fs);
|
|
1488
|
-
const fieldClass =
|
|
1489
|
-
|
|
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
|
+
);
|
|
1490
1904
|
} else if (field.is_reverse) {
|
|
1491
|
-
segs.push(
|
|
1492
|
-
|
|
1493
|
-
|
|
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}`));
|
|
1494
1917
|
} else if (field.is_highlighted) {
|
|
1495
|
-
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}`));
|
|
1496
1919
|
} else {
|
|
1497
|
-
segs.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children:
|
|
1920
|
+
segs.push(/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: displayText }, `h${idx}`));
|
|
1498
1921
|
}
|
|
1499
1922
|
lastEnd = fe;
|
|
1500
1923
|
});
|
|
1501
|
-
if (lastEnd < line.length)
|
|
1924
|
+
if (lastEnd < line.length) renderPlainRun(lastEnd, line.length, "te");
|
|
1502
1925
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: segs });
|
|
1503
|
-
}, [renderTextWithUnderlines, screenData
|
|
1926
|
+
}, [renderTextWithUnderlines, screenData, profile.defaultCols]);
|
|
1504
1927
|
const renderScreen = () => {
|
|
1505
1928
|
if (showBootLoader && !screenData?.content) {
|
|
1506
1929
|
if (bootLoader === false) return null;
|
|
@@ -1514,10 +1937,10 @@ function GreenScreenTerminal({
|
|
|
1514
1937
|
}
|
|
1515
1938
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { width: `${screenData?.cols || profile.defaultCols}ch`, height: `${(screenData?.rows || profile.defaultRows) * 21}px`, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { textAlign: "center" }, children: [
|
|
1516
1939
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { color: "#808080", marginBottom: "12px" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TerminalIcon, { size: 40 }) }),
|
|
1517
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: { fontFamily: "var(--gs-font)", fontSize: "12px", color: "#808080" }, children: connStatus?.connected ? "Waiting for screen data..." : "Not connected" }),
|
|
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" }),
|
|
1518
1941
|
!connStatus?.connected && isUsingDefaultAdapter && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { style: { fontFamily: "var(--gs-font)", fontSize: "11px", color: "#606060", marginTop: "8px" }, children: [
|
|
1519
1942
|
"Start the proxy: ",
|
|
1520
|
-
/* @__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" })
|
|
1521
1944
|
] })
|
|
1522
1945
|
] }) });
|
|
1523
1946
|
}
|
|
@@ -1535,7 +1958,7 @@ function GreenScreenTerminal({
|
|
|
1535
1958
|
const cursor = getCursorPos();
|
|
1536
1959
|
const hasCursor = screenData.cursor_row !== void 0 && screenData.cursor_col !== void 0;
|
|
1537
1960
|
const cursorInInputField = hasCursor && fields.some(
|
|
1538
|
-
(f) => f.is_input && f.row === cursor.row && cursor.col >= f.col && cursor.col < f.col + f.length
|
|
1961
|
+
(f) => f.is_input && !f.is_non_display && f.row === cursor.row && cursor.col >= f.col && cursor.col < f.col + f.length
|
|
1539
1962
|
);
|
|
1540
1963
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { fontFamily: "var(--gs-font)", fontSize: "13px", position: "relative", width: `${cols}ch` }, children: [
|
|
1541
1964
|
rows.map((line, index) => {
|
|
@@ -1550,11 +1973,33 @@ function GreenScreenTerminal({
|
|
|
1550
1973
|
}
|
|
1551
1974
|
const headerSegments = index === 0 ? profile.colors.parseHeaderRow(displayLine) : null;
|
|
1552
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: [
|
|
1553
|
-
headerSegments ? headerSegments.map((seg, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: seg.colorClass, children: seg.text }, i)) : renderRowWithFields(displayLine, index, fields),
|
|
1554
|
-
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" } })
|
|
1555
1978
|
] }, index);
|
|
1556
1979
|
}),
|
|
1557
|
-
|
|
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
|
+
),
|
|
1558
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: [
|
|
1559
2004
|
String(screenData.cursor_row + 1).padStart(2, "0"),
|
|
1560
2005
|
"/",
|
|
@@ -1585,6 +2030,15 @@ function GreenScreenTerminal({
|
|
|
1585
2030
|
return "#64748b";
|
|
1586
2031
|
}
|
|
1587
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]);
|
|
1588
2042
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `gs-terminal ${isFocused ? "gs-terminal-focused" : ""} ${className || ""}`, style, children: [
|
|
1589
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: [
|
|
1590
2044
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "gs-header-left", children: [
|
|
@@ -1597,16 +2051,17 @@ function GreenScreenTerminal({
|
|
|
1597
2051
|
screenData?.insert_mode && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "gs-badge-ins", children: "INS" })
|
|
1598
2052
|
] }),
|
|
1599
2053
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "gs-header-right", children: [
|
|
1600
|
-
connStatus?.status && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(KeyIcon, { size: 12, style: { color: getStatusColor(connStatus.status) } }),
|
|
1601
|
-
connStatus && (connStatus.connected ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(WifiIcon, { size: 12, style: { color: "var(--gs-green, #10b981)" } }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(WifiOffIcon, { size: 12, style: { color: "#FF6B00" } })),
|
|
2054
|
+
connStatus?.status && connStatus.status !== "loading" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(KeyIcon, { size: 12, style: { color: getStatusColor(connStatus.status) } }),
|
|
2055
|
+
connStatus && connStatus.status !== "loading" && (connStatus.connected ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(WifiIcon, { size: 12, style: { color: "var(--gs-green, #10b981)" } }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(WifiOffIcon, { size: 12, style: { color: "#FF6B00" } })),
|
|
2056
|
+
statusActions,
|
|
1602
2057
|
onMinimize && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: (e) => {
|
|
1603
2058
|
e.stopPropagation();
|
|
1604
2059
|
onMinimize();
|
|
1605
2060
|
}, className: "gs-btn-icon", title: "Minimize terminal", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MinimizeIcon, {}) }),
|
|
1606
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: (e) => {
|
|
2061
|
+
showShortcutsButton && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: (e) => {
|
|
1607
2062
|
e.stopPropagation();
|
|
1608
2063
|
setShowShortcuts((s) => !s);
|
|
1609
|
-
}, 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 }) }),
|
|
1610
2065
|
headerRight
|
|
1611
2066
|
] })
|
|
1612
2067
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
@@ -1620,7 +2075,7 @@ function GreenScreenTerminal({
|
|
|
1620
2075
|
screenData?.insert_mode && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "gs-badge-ins", children: "INS" })
|
|
1621
2076
|
] }),
|
|
1622
2077
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "gs-header-right", children: [
|
|
1623
|
-
connStatus && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "gs-status-group", children: connStatus.connected ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
2078
|
+
connStatus && connStatus.status !== "loading" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "gs-status-group", children: connStatus.connected ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
1624
2079
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(WifiIcon, { size: 12, style: { color: "var(--gs-green, #10b981)" } }),
|
|
1625
2080
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "gs-host", children: connStatus.host })
|
|
1626
2081
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
@@ -1632,10 +2087,11 @@ function GreenScreenTerminal({
|
|
|
1632
2087
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(KeyIcon, { size: 12, style: { color: getStatusColor(connStatus.status) } }),
|
|
1633
2088
|
connStatus.username && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "gs-host", children: connStatus.username })
|
|
1634
2089
|
] }),
|
|
1635
|
-
|
|
2090
|
+
statusActions,
|
|
2091
|
+
showShortcutsButton && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { onClick: (e) => {
|
|
1636
2092
|
e.stopPropagation();
|
|
1637
2093
|
setShowShortcuts((s) => !s);
|
|
1638
|
-
}, 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 }) }),
|
|
1639
2095
|
headerRight
|
|
1640
2096
|
] })
|
|
1641
2097
|
] }) }),
|
|
@@ -1656,29 +2112,47 @@ function GreenScreenTerminal({
|
|
|
1656
2112
|
showShortcuts && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "gs-shortcuts-panel", children: [
|
|
1657
2113
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "gs-shortcuts-header", children: [
|
|
1658
2114
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Keyboard Shortcuts" }),
|
|
1659
|
-
/* @__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" })
|
|
1660
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" }),
|
|
1661
2155
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("table", { className: "gs-shortcuts-table", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tbody", { children: [
|
|
1662
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { children: [
|
|
1663
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { className: "gs-shortcut-key", children: "Ctrl+Enter" }),
|
|
1664
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: "Field Exit" })
|
|
1665
|
-
] }),
|
|
1666
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { children: [
|
|
1667
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { className: "gs-shortcut-key", children: "Ctrl+R" }),
|
|
1668
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: "Reset" })
|
|
1669
|
-
] }),
|
|
1670
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { children: [
|
|
1671
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { className: "gs-shortcut-key", children: "Insert" }),
|
|
1672
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: "Insert / Overwrite" })
|
|
1673
|
-
] }),
|
|
1674
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { children: [
|
|
1675
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { className: "gs-shortcut-key", children: "Page Up" }),
|
|
1676
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: "Roll Down" })
|
|
1677
|
-
] }),
|
|
1678
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { children: [
|
|
1679
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { className: "gs-shortcut-key", children: "Page Down" }),
|
|
1680
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: "Roll Up" })
|
|
1681
|
-
] }),
|
|
1682
2156
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { children: [
|
|
1683
2157
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { className: "gs-shortcut-key", children: "Click" }),
|
|
1684
2158
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: "Focus / Position cursor" })
|
|
@@ -1691,7 +2165,8 @@ function GreenScreenTerminal({
|
|
|
1691
2165
|
] }),
|
|
1692
2166
|
connStatus && !connStatus.connected && screenData && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "gs-overlay", children: [
|
|
1693
2167
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(WifiOffIcon, { size: 28 }),
|
|
1694
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: isAutoReconnecting || reconnecting ? "Reconnecting..." : "Disconnected" })
|
|
2168
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: isAutoReconnecting || reconnecting ? "Reconnecting..." : connStatus?.status === "connecting" ? "Connecting..." : "Disconnected" }),
|
|
2169
|
+
connStatus.error && !isAutoReconnecting && !reconnecting && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { fontSize: "0.75em", opacity: 0.7, maxWidth: "80%", textAlign: "center", wordBreak: "break-word" }, children: connStatus.error })
|
|
1695
2170
|
] }),
|
|
1696
2171
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1697
2172
|
"input",
|
|
@@ -1705,7 +2180,19 @@ function GreenScreenTerminal({
|
|
|
1705
2180
|
autoComplete: "off",
|
|
1706
2181
|
autoCorrect: "off",
|
|
1707
2182
|
autoCapitalize: "off",
|
|
1708
|
-
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
|
+
})()
|
|
1709
2196
|
}
|
|
1710
2197
|
)
|
|
1711
2198
|
]
|