green-screen-react 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +185 -57
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +185 -57
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +63 -0
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -49,6 +49,9 @@ var RestAdapter = class {
|
|
|
49
49
|
async sendKey(key) {
|
|
50
50
|
return this.request("POST", "/send-key", { key });
|
|
51
51
|
}
|
|
52
|
+
async setCursor(row, col) {
|
|
53
|
+
return this.request("POST", "/set-cursor", { row, col });
|
|
54
|
+
}
|
|
52
55
|
async connect(config) {
|
|
53
56
|
return this.request("POST", "/connect", config);
|
|
54
57
|
}
|
|
@@ -117,6 +120,9 @@ var WebSocketAdapter = class _WebSocketAdapter {
|
|
|
117
120
|
async sendKey(key) {
|
|
118
121
|
return this.sendAndWaitForScreen({ type: "key", key });
|
|
119
122
|
}
|
|
123
|
+
async setCursor(row, col) {
|
|
124
|
+
return this.sendAndWaitForScreen({ type: "setCursor", row, col });
|
|
125
|
+
}
|
|
120
126
|
async connect(config) {
|
|
121
127
|
await this.ensureWebSocket();
|
|
122
128
|
if (!config) {
|
|
@@ -137,7 +143,8 @@ var WebSocketAdapter = class _WebSocketAdapter {
|
|
|
137
143
|
port: config.port,
|
|
138
144
|
protocol: config.protocol,
|
|
139
145
|
username: config.username,
|
|
140
|
-
password: config.password
|
|
146
|
+
password: config.password,
|
|
147
|
+
terminalType: config.terminalType
|
|
141
148
|
});
|
|
142
149
|
});
|
|
143
150
|
}
|
|
@@ -216,6 +223,19 @@ var WebSocketAdapter = class _WebSocketAdapter {
|
|
|
216
223
|
}
|
|
217
224
|
break;
|
|
218
225
|
}
|
|
226
|
+
case "cursor": {
|
|
227
|
+
if (this.pendingScreenResolver) {
|
|
228
|
+
const resolver = this.pendingScreenResolver;
|
|
229
|
+
this.pendingScreenResolver = null;
|
|
230
|
+
resolver({
|
|
231
|
+
cursor_row: msg.data.cursor_row,
|
|
232
|
+
cursor_col: msg.data.cursor_col,
|
|
233
|
+
content: this.screen?.content ?? "",
|
|
234
|
+
screen_signature: this.screen?.screen_signature ?? ""
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
219
239
|
case "status":
|
|
220
240
|
this.status = msg.data;
|
|
221
241
|
for (const listener of this.statusListeners) listener(msg.data);
|
|
@@ -886,6 +906,11 @@ var RefreshIcon = ({ size = 12, className }) => /* @__PURE__ */ jsxs2("svg", { w
|
|
|
886
906
|
/* @__PURE__ */ jsx2("path", { d: "M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15" })
|
|
887
907
|
] });
|
|
888
908
|
var KeyIcon = ({ size = 12, style: s }) => /* @__PURE__ */ jsx2("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: s, children: /* @__PURE__ */ jsx2("path", { d: "M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4" }) });
|
|
909
|
+
var HelpIcon = ({ size = 14 }) => /* @__PURE__ */ jsxs2("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
910
|
+
/* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "10" }),
|
|
911
|
+
/* @__PURE__ */ jsx2("path", { d: "M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" }),
|
|
912
|
+
/* @__PURE__ */ jsx2("line", { x1: "12", y1: "17", x2: "12.01", y2: "17" })
|
|
913
|
+
] });
|
|
889
914
|
var MinimizeIcon = () => /* @__PURE__ */ jsx2("svg", { width: 14, height: 14, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx2("path", { d: "M4 14h6v6M3 21l7-7M20 10h-6V4M21 3l-7 7" }) });
|
|
890
915
|
|
|
891
916
|
// src/components/InlineSignIn.tsx
|
|
@@ -897,12 +922,26 @@ var PROTOCOL_OPTIONS = [
|
|
|
897
922
|
{ value: "vt", label: "VT220" },
|
|
898
923
|
{ value: "hp6530", label: "HP 6530 (NonStop)" }
|
|
899
924
|
];
|
|
925
|
+
var TERMINAL_TYPE_OPTIONS = {
|
|
926
|
+
tn5250: [
|
|
927
|
+
{ value: "IBM-3179-2", label: "24 \xD7 80 (Standard)" },
|
|
928
|
+
{ value: "IBM-3477-FC", label: "27 \xD7 132 (Wide)" }
|
|
929
|
+
],
|
|
930
|
+
tn3270: [
|
|
931
|
+
{ value: "IBM-3278-2", label: "24 \xD7 80 (Model 2)" },
|
|
932
|
+
{ value: "IBM-3278-3", label: "32 \xD7 80 (Model 3)" },
|
|
933
|
+
{ value: "IBM-3278-4", label: "43 \xD7 80 (Model 4)" },
|
|
934
|
+
{ value: "IBM-3278-5", label: "27 \xD7 132 (Model 5)" }
|
|
935
|
+
]
|
|
936
|
+
};
|
|
900
937
|
function InlineSignIn({ defaultProtocol, loading: externalLoading, error, onConnect }) {
|
|
901
938
|
const [host, setHost] = useState4("");
|
|
902
939
|
const [port, setPort] = useState4("");
|
|
903
940
|
const [selectedProtocol, setSelectedProtocol] = useState4(defaultProtocol);
|
|
941
|
+
const [terminalType, setTerminalType] = useState4("");
|
|
904
942
|
const [username, setUsername] = useState4("");
|
|
905
943
|
const [password, setPassword] = useState4("");
|
|
944
|
+
const termTypeOptions = TERMINAL_TYPE_OPTIONS[selectedProtocol];
|
|
906
945
|
const [submitted, setSubmitted] = useState4(false);
|
|
907
946
|
const loading = externalLoading || submitted;
|
|
908
947
|
const handleSubmit = (e) => {
|
|
@@ -913,7 +952,8 @@ function InlineSignIn({ defaultProtocol, loading: externalLoading, error, onConn
|
|
|
913
952
|
port: port ? parseInt(port, 10) : 23,
|
|
914
953
|
protocol: selectedProtocol,
|
|
915
954
|
...username.trim() ? { username: username.trim() } : {},
|
|
916
|
-
...password ? { password } : {}
|
|
955
|
+
...password ? { password } : {},
|
|
956
|
+
...terminalType ? { terminalType } : {}
|
|
917
957
|
});
|
|
918
958
|
};
|
|
919
959
|
const inputStyle = {
|
|
@@ -969,7 +1009,14 @@ function InlineSignIn({ defaultProtocol, loading: externalLoading, error, onConn
|
|
|
969
1009
|
"Protocol ",
|
|
970
1010
|
/* @__PURE__ */ jsx3("span", { style: { color: "#ef4444" }, children: "*" })
|
|
971
1011
|
] }),
|
|
972
|
-
/* @__PURE__ */ jsx3("select", { style: { ...inputStyle, appearance: "none" }, value: selectedProtocol, onChange: (e) =>
|
|
1012
|
+
/* @__PURE__ */ jsx3("select", { style: { ...inputStyle, appearance: "none" }, value: selectedProtocol, onChange: (e) => {
|
|
1013
|
+
setSelectedProtocol(e.target.value);
|
|
1014
|
+
setTerminalType("");
|
|
1015
|
+
}, children: PROTOCOL_OPTIONS.map((o) => /* @__PURE__ */ jsx3("option", { value: o.value, children: o.label }, o.value)) })
|
|
1016
|
+
] }),
|
|
1017
|
+
termTypeOptions && termTypeOptions.length > 1 && /* @__PURE__ */ jsxs3("div", { children: [
|
|
1018
|
+
/* @__PURE__ */ jsx3("label", { style: labelStyle, children: "Screen Size" }),
|
|
1019
|
+
/* @__PURE__ */ jsx3("select", { style: { ...inputStyle, appearance: "none" }, value: terminalType || termTypeOptions[0].value, onChange: (e) => setTerminalType(e.target.value), children: termTypeOptions.map((o) => /* @__PURE__ */ jsx3("option", { value: o.value, children: o.label }, o.value)) })
|
|
973
1020
|
] }),
|
|
974
1021
|
/* @__PURE__ */ jsxs3("div", { children: [
|
|
975
1022
|
/* @__PURE__ */ jsx3("label", { style: labelStyle, children: "Username" }),
|
|
@@ -995,6 +1042,7 @@ var noopAdapter = {
|
|
|
995
1042
|
getStatus: async () => ({ connected: false, status: "disconnected" }),
|
|
996
1043
|
sendText: async () => noopResult,
|
|
997
1044
|
sendKey: async () => noopResult,
|
|
1045
|
+
setCursor: async () => noopResult,
|
|
998
1046
|
connect: async () => noopResult,
|
|
999
1047
|
disconnect: async () => noopResult,
|
|
1000
1048
|
reconnect: async () => noopResult
|
|
@@ -1013,7 +1061,7 @@ function GreenScreenTerminal({
|
|
|
1013
1061
|
maxReconnectAttempts: maxAttempts = 5,
|
|
1014
1062
|
embedded = false,
|
|
1015
1063
|
showHeader = true,
|
|
1016
|
-
typingAnimation =
|
|
1064
|
+
typingAnimation = false,
|
|
1017
1065
|
typingBudgetMs = 60,
|
|
1018
1066
|
inlineSignIn = true,
|
|
1019
1067
|
defaultProtocol: signInDefaultProtocol,
|
|
@@ -1069,9 +1117,18 @@ function GreenScreenTerminal({
|
|
|
1069
1117
|
}, [screenData, onScreenChange]);
|
|
1070
1118
|
const sendText = useCallback3(async (text) => _sendText(text), [_sendText]);
|
|
1071
1119
|
const sendKey = useCallback3(async (key) => _sendKey(key), [_sendKey]);
|
|
1120
|
+
const [optimisticEdits, setOptimisticEdits] = useState5([]);
|
|
1121
|
+
const prevScreenSigForEdits = useRef4(void 0);
|
|
1122
|
+
useEffect4(() => {
|
|
1123
|
+
if (rawScreenData?.screen_signature && rawScreenData.screen_signature !== prevScreenSigForEdits.current) {
|
|
1124
|
+
prevScreenSigForEdits.current = rawScreenData.screen_signature;
|
|
1125
|
+
setOptimisticEdits([]);
|
|
1126
|
+
}
|
|
1127
|
+
}, [rawScreenData?.screen_signature]);
|
|
1072
1128
|
const [inputText, setInputText] = useState5("");
|
|
1073
1129
|
const [isFocused, setIsFocused] = useState5(false);
|
|
1074
1130
|
const [showSignInHint, setShowSignInHint] = useState5(false);
|
|
1131
|
+
const [showShortcuts, setShowShortcuts] = useState5(false);
|
|
1075
1132
|
const prevAutoSignedIn = useRef4(false);
|
|
1076
1133
|
useEffect4(() => {
|
|
1077
1134
|
if (autoSignedIn && !prevAutoSignedIn.current) setShowSignInHint(true);
|
|
@@ -1214,11 +1271,38 @@ function GreenScreenTerminal({
|
|
|
1214
1271
|
inputRef.current?.blur();
|
|
1215
1272
|
}
|
|
1216
1273
|
}, [readOnly, isFocused]);
|
|
1217
|
-
const
|
|
1274
|
+
const screenContentRef = useRef4(null);
|
|
1275
|
+
const charWidthRef = useRef4(0);
|
|
1276
|
+
const handleTerminalClick = useCallback3((e) => {
|
|
1218
1277
|
if (readOnly) return;
|
|
1219
1278
|
setIsFocused(true);
|
|
1220
1279
|
inputRef.current?.focus();
|
|
1221
|
-
|
|
1280
|
+
const contentEl = screenContentRef.current;
|
|
1281
|
+
if (!contentEl || !screenData?.fields) return;
|
|
1282
|
+
if (!charWidthRef.current) {
|
|
1283
|
+
const span = document.createElement("span");
|
|
1284
|
+
span.style.cssText = "position:absolute;visibility:hidden;font:inherit;white-space:pre";
|
|
1285
|
+
span.textContent = "X";
|
|
1286
|
+
contentEl.appendChild(span);
|
|
1287
|
+
charWidthRef.current = span.getBoundingClientRect().width;
|
|
1288
|
+
contentEl.removeChild(span);
|
|
1289
|
+
}
|
|
1290
|
+
const rect = contentEl.getBoundingClientRect();
|
|
1291
|
+
const x = e.clientX - rect.left;
|
|
1292
|
+
const y = e.clientY - rect.top;
|
|
1293
|
+
const ROW_HEIGHT = 21;
|
|
1294
|
+
const charWidth = charWidthRef.current;
|
|
1295
|
+
if (!charWidth) return;
|
|
1296
|
+
const clickedRow = Math.floor(y / ROW_HEIGHT);
|
|
1297
|
+
const clickedCol = Math.floor(x / charWidth);
|
|
1298
|
+
if (clickedRow < 0 || clickedRow >= (screenData.rows || 24) || clickedCol < 0 || clickedCol >= (screenData.cols || 80)) return;
|
|
1299
|
+
setSyncedCursor({ row: clickedRow, col: clickedCol });
|
|
1300
|
+
adapter.setCursor?.(clickedRow, clickedCol).then((r) => {
|
|
1301
|
+
if (r?.cursor_row !== void 0) {
|
|
1302
|
+
setSyncedCursor({ row: r.cursor_row, col: r.cursor_col });
|
|
1303
|
+
}
|
|
1304
|
+
});
|
|
1305
|
+
}, [readOnly, screenData, adapter]);
|
|
1222
1306
|
const getCurrentField = useCallback3(() => {
|
|
1223
1307
|
const fields = screenData?.fields || [];
|
|
1224
1308
|
const cursorRow = syncedCursor?.row ?? screenData?.cursor_row ?? 0;
|
|
@@ -1228,12 +1312,6 @@ function GreenScreenTerminal({
|
|
|
1228
1312
|
}
|
|
1229
1313
|
return null;
|
|
1230
1314
|
}, [screenData, syncedCursor]);
|
|
1231
|
-
const canTypeMore = useCallback3((additionalChars = 1) => {
|
|
1232
|
-
const currentField = getCurrentField();
|
|
1233
|
-
if (!currentField) return true;
|
|
1234
|
-
const cursorCol = (syncedCursor?.col ?? screenData?.cursor_col ?? 0) + inputText.length;
|
|
1235
|
-
return cursorCol + additionalChars <= currentField.col + currentField.length;
|
|
1236
|
-
}, [getCurrentField, syncedCursor, screenData, inputText]);
|
|
1237
1315
|
const handleKeyDown = async (e) => {
|
|
1238
1316
|
if (readOnly) {
|
|
1239
1317
|
e.preventDefault();
|
|
@@ -1245,19 +1323,22 @@ function GreenScreenTerminal({
|
|
|
1245
1323
|
inputRef.current?.blur();
|
|
1246
1324
|
return;
|
|
1247
1325
|
}
|
|
1248
|
-
if (e.key === "
|
|
1326
|
+
if (e.ctrlKey && e.key === "r") {
|
|
1249
1327
|
e.preventDefault();
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1328
|
+
const kr = await sendKey("RESET");
|
|
1329
|
+
if (kr.cursor_row !== void 0) setSyncedCursor({ row: kr.cursor_row, col: kr.cursor_col });
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
if (e.ctrlKey && e.key === "Enter") {
|
|
1333
|
+
e.preventDefault();
|
|
1334
|
+
const kr = await sendKey("FIELD_EXIT");
|
|
1335
|
+
if (kr.cursor_row !== void 0) setSyncedCursor({ row: kr.cursor_row, col: kr.cursor_col });
|
|
1256
1336
|
return;
|
|
1257
1337
|
}
|
|
1258
1338
|
const keyMap = {
|
|
1259
1339
|
Enter: "ENTER",
|
|
1260
1340
|
Tab: "TAB",
|
|
1341
|
+
Backspace: "BACKSPACE",
|
|
1261
1342
|
Delete: "DELETE",
|
|
1262
1343
|
ArrowUp: "UP",
|
|
1263
1344
|
ArrowDown: "DOWN",
|
|
@@ -1273,11 +1354,6 @@ function GreenScreenTerminal({
|
|
|
1273
1354
|
e.preventDefault();
|
|
1274
1355
|
const fKey = e.key.toUpperCase();
|
|
1275
1356
|
if (/^F([1-9]|1[0-9]|2[0-4])$/.test(fKey)) {
|
|
1276
|
-
if (inputText) {
|
|
1277
|
-
const r = await sendText(inputText);
|
|
1278
|
-
setInputText("");
|
|
1279
|
-
if (r.cursor_row !== void 0) setSyncedCursor({ row: r.cursor_row, col: r.cursor_col });
|
|
1280
|
-
}
|
|
1281
1357
|
const kr = await sendKey(fKey);
|
|
1282
1358
|
if (kr.cursor_row !== void 0) setSyncedCursor({ row: kr.cursor_row, col: kr.cursor_col });
|
|
1283
1359
|
return;
|
|
@@ -1285,11 +1361,6 @@ function GreenScreenTerminal({
|
|
|
1285
1361
|
}
|
|
1286
1362
|
if (keyMap[e.key]) {
|
|
1287
1363
|
e.preventDefault();
|
|
1288
|
-
if (inputText) {
|
|
1289
|
-
const r = await sendText(inputText);
|
|
1290
|
-
setInputText("");
|
|
1291
|
-
if (r.cursor_row !== void 0) setSyncedCursor({ row: r.cursor_row, col: r.cursor_col });
|
|
1292
|
-
}
|
|
1293
1364
|
const kr = await sendKey(keyMap[e.key]);
|
|
1294
1365
|
if (kr.cursor_row !== void 0) setSyncedCursor({ row: kr.cursor_row, col: kr.cursor_col });
|
|
1295
1366
|
}
|
|
@@ -1300,30 +1371,28 @@ function GreenScreenTerminal({
|
|
|
1300
1371
|
return;
|
|
1301
1372
|
}
|
|
1302
1373
|
const newText = e.target.value;
|
|
1303
|
-
if (newText.
|
|
1304
|
-
const
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1374
|
+
if (newText.length > inputText.length) {
|
|
1375
|
+
const newChars = newText.substring(inputText.length);
|
|
1376
|
+
const curRow = syncedCursor?.row ?? screenData?.cursor_row ?? 0;
|
|
1377
|
+
const curCol = syncedCursor?.col ?? screenData?.cursor_col ?? 0;
|
|
1378
|
+
const edits = [];
|
|
1379
|
+
for (let i = 0; i < newChars.length; i++) {
|
|
1380
|
+
edits.push({ row: curRow, col: curCol + i, ch: newChars[i] });
|
|
1308
1381
|
}
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
const charsToAdd = newText.length - inputText.length;
|
|
1315
|
-
if (charsToAdd > 0 && !canTypeMore(charsToAdd)) {
|
|
1316
|
-
e.target.value = inputText;
|
|
1317
|
-
return;
|
|
1318
|
-
}
|
|
1319
|
-
setInputText(newText);
|
|
1382
|
+
setOptimisticEdits((prev) => [...prev, ...edits]);
|
|
1383
|
+
setSyncedCursor({ row: curRow, col: curCol + newChars.length });
|
|
1384
|
+
sendText(newChars).then((r) => {
|
|
1385
|
+
if (r.cursor_row !== void 0) setSyncedCursor({ row: r.cursor_row, col: r.cursor_col });
|
|
1386
|
+
});
|
|
1320
1387
|
}
|
|
1388
|
+
setInputText("");
|
|
1389
|
+
e.target.value = "";
|
|
1321
1390
|
};
|
|
1322
1391
|
const termCols = screenData?.cols || profile.defaultCols;
|
|
1323
1392
|
const getCursorPos = () => {
|
|
1324
1393
|
if (animatedCursorPos) return animatedCursorPos;
|
|
1325
1394
|
let cursorRow = syncedCursor?.row ?? screenData?.cursor_row ?? 0;
|
|
1326
|
-
let cursorCol =
|
|
1395
|
+
let cursorCol = syncedCursor?.col ?? screenData?.cursor_col ?? 0;
|
|
1327
1396
|
while (cursorCol >= termCols) {
|
|
1328
1397
|
cursorCol -= termCols;
|
|
1329
1398
|
cursorRow += 1;
|
|
@@ -1350,7 +1419,8 @@ function GreenScreenTerminal({
|
|
|
1350
1419
|
const inputFields = fields.filter((f) => f.row === rowIndex && f.is_input);
|
|
1351
1420
|
const highlightedFields = fields.filter((f) => f.row === rowIndex && f.is_protected && f.is_highlighted);
|
|
1352
1421
|
const reverseFields = fields.filter((f) => f.row === rowIndex && f.is_protected && f.is_reverse);
|
|
1353
|
-
const
|
|
1422
|
+
const colorFields = fields.filter((f) => f.row === rowIndex && f.is_protected && f.color && !f.is_highlighted && !f.is_reverse);
|
|
1423
|
+
const allRowFields = [...inputFields, ...highlightedFields, ...reverseFields, ...colorFields];
|
|
1354
1424
|
if (allRowFields.length === 0) return /* @__PURE__ */ jsx4("span", { children: line });
|
|
1355
1425
|
const sorted = [...allRowFields].sort((a, b) => a.col - b.col);
|
|
1356
1426
|
const segs = [];
|
|
@@ -1361,12 +1431,15 @@ function GreenScreenTerminal({
|
|
|
1361
1431
|
const fe = Math.min(field.col + field.length, cols);
|
|
1362
1432
|
if (fs > lastEnd) segs.push(/* @__PURE__ */ jsx4("span", { children: line.substring(lastEnd, fs) }, `t${idx}`));
|
|
1363
1433
|
const fc = line.substring(fs, fe);
|
|
1434
|
+
const colorVar = field.color ? `var(--gs-${field.color}, var(--gs-green))` : void 0;
|
|
1364
1435
|
if (field.is_input) {
|
|
1365
1436
|
const fieldWidth = Math.min(field.length, cols - fs);
|
|
1366
1437
|
const fieldClass = showSignInHint ? "gs-confirmed-field" : field.is_underscored ? "gs-input-field" : void 0;
|
|
1367
|
-
segs.push(/* @__PURE__ */ jsx4("span", { className: fieldClass || void 0, style: { display: "inline-block", width: `${fieldWidth}ch`, overflow: "hidden" }, children: fc }, `f${idx}`));
|
|
1438
|
+
segs.push(/* @__PURE__ */ jsx4("span", { className: fieldClass || void 0, style: { display: "inline-block", width: `${fieldWidth}ch`, overflow: "hidden", color: colorVar }, children: fc }, `f${idx}`));
|
|
1368
1439
|
} else if (field.is_reverse) {
|
|
1369
|
-
segs.push(/* @__PURE__ */ jsx4("span", { style: { color: "#
|
|
1440
|
+
segs.push(/* @__PURE__ */ jsx4("span", { style: { color: colorVar || "var(--gs-red, #FF5555)", fontWeight: "bold" }, children: fc }, `v${idx}`));
|
|
1441
|
+
} else if (colorVar) {
|
|
1442
|
+
segs.push(/* @__PURE__ */ jsx4("span", { style: { color: colorVar }, children: fc }, `h${idx}`));
|
|
1370
1443
|
} else if (field.is_highlighted) {
|
|
1371
1444
|
segs.push(/* @__PURE__ */ jsx4("span", { style: { color: "var(--gs-white, #FFFFFF)" }, children: fc }, `h${idx}`));
|
|
1372
1445
|
} else {
|
|
@@ -1410,17 +1483,24 @@ function GreenScreenTerminal({
|
|
|
1410
1483
|
const ROW_HEIGHT = 21;
|
|
1411
1484
|
const cursor = getCursorPos();
|
|
1412
1485
|
const hasCursor = screenData.cursor_row !== void 0 && screenData.cursor_col !== void 0;
|
|
1486
|
+
const cursorInInputField = hasCursor && fields.some(
|
|
1487
|
+
(f) => f.is_input && f.row === cursor.row && cursor.col >= f.col && cursor.col < f.col + f.length
|
|
1488
|
+
);
|
|
1413
1489
|
return /* @__PURE__ */ jsxs4("div", { style: { fontFamily: "var(--gs-font)", fontSize: "13px", position: "relative", width: `${cols}ch` }, children: [
|
|
1414
1490
|
rows.map((line, index) => {
|
|
1415
1491
|
let displayLine = line;
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1492
|
+
const rowEdits = optimisticEdits.filter((e) => e.row === index);
|
|
1493
|
+
if (rowEdits.length > 0) {
|
|
1494
|
+
const chars = displayLine.split("");
|
|
1495
|
+
for (const edit of rowEdits) {
|
|
1496
|
+
if (edit.col >= 0 && edit.col < chars.length) chars[edit.col] = edit.ch;
|
|
1497
|
+
}
|
|
1498
|
+
displayLine = chars.join("");
|
|
1419
1499
|
}
|
|
1420
1500
|
const headerSegments = index === 0 ? profile.colors.parseHeaderRow(displayLine) : null;
|
|
1421
1501
|
return /* @__PURE__ */ jsxs4("div", { className: headerSegments ? "" : profile.colors.getRowColorClass(index, displayLine, termRows), style: { height: `${ROW_HEIGHT}px`, lineHeight: `${ROW_HEIGHT}px`, whiteSpace: "pre", position: "relative" }, children: [
|
|
1422
1502
|
headerSegments ? headerSegments.map((seg, i) => /* @__PURE__ */ jsx4("span", { className: seg.colorClass, children: seg.text }, i)) : renderRowWithFields(displayLine, index, fields),
|
|
1423
|
-
|
|
1503
|
+
cursorInInputField && !showSignInHint && index === cursor.row && /* @__PURE__ */ jsx4("span", { className: "gs-cursor", style: { position: "absolute", left: `${cursor.col}ch`, width: "1ch", height: `${ROW_HEIGHT}px`, top: 0, pointerEvents: "none" } })
|
|
1424
1504
|
] }, index);
|
|
1425
1505
|
}),
|
|
1426
1506
|
showSignInHint && /* @__PURE__ */ jsx4("div", { className: "gs-signin-hint", children: "Signed in \u2014 press Enter to continue" }),
|
|
@@ -1461,7 +1541,9 @@ function GreenScreenTerminal({
|
|
|
1461
1541
|
/* @__PURE__ */ jsx4("span", { children: "TERMINAL" }),
|
|
1462
1542
|
isFocused && /* @__PURE__ */ jsx4("span", { className: "gs-badge-focused", children: "FOCUSED" }),
|
|
1463
1543
|
screenData?.timestamp && /* @__PURE__ */ jsx4("span", { className: "gs-timestamp", children: new Date(screenData.timestamp).toLocaleTimeString() }),
|
|
1464
|
-
/* @__PURE__ */ jsx4("span", { className: "gs-hint", children: readOnly ? "Read-only" : isFocused ? "ESC to exit focus" : "Click to control" })
|
|
1544
|
+
/* @__PURE__ */ jsx4("span", { className: "gs-hint", children: readOnly ? "Read-only" : isFocused ? "ESC to exit focus" : "Click to control" }),
|
|
1545
|
+
screenData?.keyboard_locked && /* @__PURE__ */ jsx4("span", { className: "gs-badge-lock", children: "X II" }),
|
|
1546
|
+
screenData?.insert_mode && /* @__PURE__ */ jsx4("span", { className: "gs-badge-ins", children: "INS" })
|
|
1465
1547
|
] }),
|
|
1466
1548
|
/* @__PURE__ */ jsxs4("div", { className: "gs-header-right", children: [
|
|
1467
1549
|
connStatus?.status && /* @__PURE__ */ jsx4(KeyIcon, { size: 12, style: { color: getStatusColor(connStatus.status) } }),
|
|
@@ -1470,6 +1552,10 @@ function GreenScreenTerminal({
|
|
|
1470
1552
|
e.stopPropagation();
|
|
1471
1553
|
onMinimize();
|
|
1472
1554
|
}, className: "gs-btn-icon", title: "Minimize terminal", children: /* @__PURE__ */ jsx4(MinimizeIcon, {}) }),
|
|
1555
|
+
/* @__PURE__ */ jsx4("button", { onClick: (e) => {
|
|
1556
|
+
e.stopPropagation();
|
|
1557
|
+
setShowShortcuts((s) => !s);
|
|
1558
|
+
}, className: "gs-btn-icon", title: "Keyboard shortcuts", children: /* @__PURE__ */ jsx4(HelpIcon, { size: 12 }) }),
|
|
1473
1559
|
headerRight
|
|
1474
1560
|
] })
|
|
1475
1561
|
] }) : /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
@@ -1478,7 +1564,9 @@ function GreenScreenTerminal({
|
|
|
1478
1564
|
/* @__PURE__ */ jsx4("span", { children: profile.headerLabel }),
|
|
1479
1565
|
isFocused && /* @__PURE__ */ jsx4("span", { className: "gs-badge-focused", children: "FOCUSED" }),
|
|
1480
1566
|
screenData?.timestamp && /* @__PURE__ */ jsx4("span", { className: "gs-timestamp", children: new Date(screenData.timestamp).toLocaleTimeString() }),
|
|
1481
|
-
/* @__PURE__ */ jsx4("span", { className: "gs-hint", children: readOnly ? "Read-only mode" : isFocused ? "ESC to exit focus" : "Click terminal to control" })
|
|
1567
|
+
/* @__PURE__ */ jsx4("span", { className: "gs-hint", children: readOnly ? "Read-only mode" : isFocused ? "ESC to exit focus" : "Click terminal to control" }),
|
|
1568
|
+
screenData?.keyboard_locked && /* @__PURE__ */ jsx4("span", { className: "gs-badge-lock", children: "X II" }),
|
|
1569
|
+
screenData?.insert_mode && /* @__PURE__ */ jsx4("span", { className: "gs-badge-ins", children: "INS" })
|
|
1482
1570
|
] }),
|
|
1483
1571
|
/* @__PURE__ */ jsxs4("div", { className: "gs-header-right", children: [
|
|
1484
1572
|
connStatus && /* @__PURE__ */ jsx4("div", { className: "gs-status-group", children: connStatus.connected ? /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
@@ -1493,6 +1581,10 @@ function GreenScreenTerminal({
|
|
|
1493
1581
|
/* @__PURE__ */ jsx4(KeyIcon, { size: 12, style: { color: getStatusColor(connStatus.status) } }),
|
|
1494
1582
|
connStatus.username && /* @__PURE__ */ jsx4("span", { className: "gs-host", children: connStatus.username })
|
|
1495
1583
|
] }),
|
|
1584
|
+
/* @__PURE__ */ jsx4("button", { onClick: (e) => {
|
|
1585
|
+
e.stopPropagation();
|
|
1586
|
+
setShowShortcuts((s) => !s);
|
|
1587
|
+
}, className: "gs-btn-icon", title: "Keyboard shortcuts", children: /* @__PURE__ */ jsx4(HelpIcon, { size: 12 }) }),
|
|
1496
1588
|
headerRight
|
|
1497
1589
|
] })
|
|
1498
1590
|
] }) }),
|
|
@@ -1508,8 +1600,44 @@ function GreenScreenTerminal({
|
|
|
1508
1600
|
/* @__PURE__ */ jsx4(AlertTriangleIcon, { size: 14 }),
|
|
1509
1601
|
/* @__PURE__ */ jsx4("span", { children: String(screenError) })
|
|
1510
1602
|
] }),
|
|
1511
|
-
/* @__PURE__ */ jsx4("div", { className: "gs-screen-content", children: renderScreen() }),
|
|
1603
|
+
/* @__PURE__ */ jsx4("div", { ref: screenContentRef, className: "gs-screen-content", children: renderScreen() }),
|
|
1512
1604
|
overlay,
|
|
1605
|
+
showShortcuts && /* @__PURE__ */ jsxs4("div", { className: "gs-shortcuts-panel", children: [
|
|
1606
|
+
/* @__PURE__ */ jsxs4("div", { className: "gs-shortcuts-header", children: [
|
|
1607
|
+
/* @__PURE__ */ jsx4("span", { children: "Keyboard Shortcuts" }),
|
|
1608
|
+
/* @__PURE__ */ jsx4("button", { className: "gs-btn-icon", onClick: () => setShowShortcuts(false), style: { pointerEvents: "auto" }, children: "\xD7" })
|
|
1609
|
+
] }),
|
|
1610
|
+
/* @__PURE__ */ jsx4("table", { className: "gs-shortcuts-table", children: /* @__PURE__ */ jsxs4("tbody", { children: [
|
|
1611
|
+
/* @__PURE__ */ jsxs4("tr", { children: [
|
|
1612
|
+
/* @__PURE__ */ jsx4("td", { className: "gs-shortcut-key", children: "Ctrl+Enter" }),
|
|
1613
|
+
/* @__PURE__ */ jsx4("td", { children: "Field Exit" })
|
|
1614
|
+
] }),
|
|
1615
|
+
/* @__PURE__ */ jsxs4("tr", { children: [
|
|
1616
|
+
/* @__PURE__ */ jsx4("td", { className: "gs-shortcut-key", children: "Ctrl+R" }),
|
|
1617
|
+
/* @__PURE__ */ jsx4("td", { children: "Reset" })
|
|
1618
|
+
] }),
|
|
1619
|
+
/* @__PURE__ */ jsxs4("tr", { children: [
|
|
1620
|
+
/* @__PURE__ */ jsx4("td", { className: "gs-shortcut-key", children: "Insert" }),
|
|
1621
|
+
/* @__PURE__ */ jsx4("td", { children: "Insert / Overwrite" })
|
|
1622
|
+
] }),
|
|
1623
|
+
/* @__PURE__ */ jsxs4("tr", { children: [
|
|
1624
|
+
/* @__PURE__ */ jsx4("td", { className: "gs-shortcut-key", children: "Page Up" }),
|
|
1625
|
+
/* @__PURE__ */ jsx4("td", { children: "Roll Down" })
|
|
1626
|
+
] }),
|
|
1627
|
+
/* @__PURE__ */ jsxs4("tr", { children: [
|
|
1628
|
+
/* @__PURE__ */ jsx4("td", { className: "gs-shortcut-key", children: "Page Down" }),
|
|
1629
|
+
/* @__PURE__ */ jsx4("td", { children: "Roll Up" })
|
|
1630
|
+
] }),
|
|
1631
|
+
/* @__PURE__ */ jsxs4("tr", { children: [
|
|
1632
|
+
/* @__PURE__ */ jsx4("td", { className: "gs-shortcut-key", children: "Click" }),
|
|
1633
|
+
/* @__PURE__ */ jsx4("td", { children: "Focus / Position cursor" })
|
|
1634
|
+
] }),
|
|
1635
|
+
/* @__PURE__ */ jsxs4("tr", { children: [
|
|
1636
|
+
/* @__PURE__ */ jsx4("td", { className: "gs-shortcut-key", children: "Escape" }),
|
|
1637
|
+
/* @__PURE__ */ jsx4("td", { children: "Exit focus mode" })
|
|
1638
|
+
] })
|
|
1639
|
+
] }) })
|
|
1640
|
+
] }),
|
|
1513
1641
|
connStatus && !connStatus.connected && screenData && /* @__PURE__ */ jsxs4("div", { className: "gs-overlay", children: [
|
|
1514
1642
|
/* @__PURE__ */ jsx4(WifiOffIcon, { size: 28 }),
|
|
1515
1643
|
/* @__PURE__ */ jsx4("span", { children: isAutoReconnecting || reconnecting ? "Reconnecting..." : "Disconnected" })
|
|
@@ -1522,7 +1650,7 @@ function GreenScreenTerminal({
|
|
|
1522
1650
|
value: inputText,
|
|
1523
1651
|
onChange: handleInput,
|
|
1524
1652
|
onKeyDown: handleKeyDown,
|
|
1525
|
-
style: { position: "absolute", opacity: 0, pointerEvents: "none" },
|
|
1653
|
+
style: { position: "absolute", opacity: 0, pointerEvents: "none", fontSize: "13px", lineHeight: "21px", fontFamily: "var(--gs-font)", padding: 0, border: "none", height: "21px" },
|
|
1526
1654
|
autoComplete: "off",
|
|
1527
1655
|
autoCorrect: "off",
|
|
1528
1656
|
autoCapitalize: "off",
|