lit-shell.js 1.1.0 → 1.2.1
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/CHANGELOG.md +36 -0
- package/README.md +438 -9
- package/dist/client/browser-bundle.js +61 -1
- package/dist/client/browser-bundle.js.map +3 -3
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/terminal-client.d.ts +19 -0
- package/dist/client/terminal-client.d.ts.map +1 -1
- package/dist/client/terminal-client.js +61 -0
- package/dist/client/terminal-client.js.map +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/session-manager.d.ts +6 -0
- package/dist/server/session-manager.d.ts.map +1 -1
- package/dist/server/session-manager.js +12 -2
- package/dist/server/session-manager.js.map +1 -1
- package/dist/server/terminal-server.d.ts.map +1 -1
- package/dist/server/terminal-server.js +23 -4
- package/dist/server/terminal-server.js.map +1 -1
- package/dist/shared/types.d.ts +6 -0
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/ui/browser-bundle.js +1625 -96
- package/dist/ui/browser-bundle.js.map +4 -4
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +1 -0
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/lit-shell-terminal.d.ts +225 -6
- package/dist/ui/lit-shell-terminal.d.ts.map +1 -1
- package/dist/ui/lit-shell-terminal.js +1605 -60
- package/dist/ui/lit-shell-terminal.js.map +1 -1
- package/dist/ui/styles.d.ts.map +1 -1
- package/dist/ui/styles.js +22 -0
- package/dist/ui/styles.js.map +1 -1
- package/dist/version.d.ts +6 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +6 -0
- package/dist/version.js.map +1 -0
- package/package.json +1 -1
|
@@ -10,6 +10,9 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
10
10
|
return result;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
// src/version.ts
|
|
14
|
+
var VERSION = "1.2.1";
|
|
15
|
+
|
|
13
16
|
// node_modules/@lit/reactive-element/css-tag.js
|
|
14
17
|
var t = globalThis;
|
|
15
18
|
var e = t.ShadowRoot && (void 0 === t.ShadyCSS || t.ShadyCSS.nativeShadow) && "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype;
|
|
@@ -748,6 +751,28 @@ var buttonStyles = i`
|
|
|
748
751
|
opacity: 0.5;
|
|
749
752
|
cursor: not-allowed;
|
|
750
753
|
}
|
|
754
|
+
|
|
755
|
+
button.btn-primary,
|
|
756
|
+
.btn-primary {
|
|
757
|
+
background: var(--ls-status-connected);
|
|
758
|
+
color: #ffffff;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
button.btn-primary:hover,
|
|
762
|
+
.btn-primary:hover {
|
|
763
|
+
background: #16a34a;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
button.btn-danger,
|
|
767
|
+
.btn-danger {
|
|
768
|
+
background: var(--ls-status-disconnected);
|
|
769
|
+
color: #ffffff;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
button.btn-danger:hover,
|
|
773
|
+
.btn-danger:hover {
|
|
774
|
+
background: #dc2626;
|
|
775
|
+
}
|
|
751
776
|
`;
|
|
752
777
|
|
|
753
778
|
// src/client/terminal-client.ts
|
|
@@ -760,6 +785,8 @@ var TerminalClient = class {
|
|
|
760
785
|
this.serverInfo = null;
|
|
761
786
|
this.reconnectAttempts = 0;
|
|
762
787
|
this.reconnectTimeout = null;
|
|
788
|
+
this.previousSessionId = null;
|
|
789
|
+
this.isReconnecting = false;
|
|
763
790
|
// Event handlers
|
|
764
791
|
this.connectHandlers = [];
|
|
765
792
|
this.disconnectHandlers = [];
|
|
@@ -776,6 +803,7 @@ var TerminalClient = class {
|
|
|
776
803
|
this.clientJoinedHandlers = [];
|
|
777
804
|
this.clientLeftHandlers = [];
|
|
778
805
|
this.sessionClosedHandlers = [];
|
|
806
|
+
this.reconnectWithSessionHandlers = [];
|
|
779
807
|
// Promise resolvers for spawn/join
|
|
780
808
|
this.spawnResolve = null;
|
|
781
809
|
this.spawnReject = null;
|
|
@@ -810,17 +838,25 @@ var TerminalClient = class {
|
|
|
810
838
|
this.state = "connected";
|
|
811
839
|
this.reconnectAttempts = 0;
|
|
812
840
|
this.connectHandlers.forEach((handler) => handler());
|
|
841
|
+
if (this.isReconnecting && this.previousSessionId) {
|
|
842
|
+
this.checkPreviousSessionAndNotify();
|
|
843
|
+
}
|
|
844
|
+
this.isReconnecting = false;
|
|
813
845
|
resolve();
|
|
814
846
|
};
|
|
815
847
|
this.ws.onclose = () => {
|
|
816
848
|
const wasConnected = this.state === "connected";
|
|
817
849
|
this.state = "disconnected";
|
|
850
|
+
if (this.sessionId) {
|
|
851
|
+
this.previousSessionId = this.sessionId;
|
|
852
|
+
}
|
|
818
853
|
this.sessionId = null;
|
|
819
854
|
this.sessionInfo = null;
|
|
820
855
|
if (wasConnected) {
|
|
821
856
|
this.disconnectHandlers.forEach((handler) => handler());
|
|
822
857
|
}
|
|
823
858
|
if (this.config.reconnect && this.reconnectAttempts < this.config.maxReconnectAttempts) {
|
|
859
|
+
this.isReconnecting = true;
|
|
824
860
|
this.scheduleReconnect();
|
|
825
861
|
}
|
|
826
862
|
};
|
|
@@ -1295,6 +1331,51 @@ var TerminalClient = class {
|
|
|
1295
1331
|
getServerInfo() {
|
|
1296
1332
|
return this.serverInfo;
|
|
1297
1333
|
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Get previous session ID (available after disconnect)
|
|
1336
|
+
*/
|
|
1337
|
+
getPreviousSessionId() {
|
|
1338
|
+
return this.previousSessionId;
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Clear previous session ID (call after user declines to rejoin)
|
|
1342
|
+
*/
|
|
1343
|
+
clearPreviousSessionId() {
|
|
1344
|
+
this.previousSessionId = null;
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* Called when reconnected and previous session is available
|
|
1348
|
+
*/
|
|
1349
|
+
onReconnectWithSession(handler) {
|
|
1350
|
+
this.reconnectWithSessionHandlers.push(handler);
|
|
1351
|
+
}
|
|
1352
|
+
/**
|
|
1353
|
+
* Check if previous session exists and notify handlers
|
|
1354
|
+
*/
|
|
1355
|
+
async checkPreviousSessionAndNotify() {
|
|
1356
|
+
if (!this.previousSessionId)
|
|
1357
|
+
return;
|
|
1358
|
+
try {
|
|
1359
|
+
const sessions = await this.listSessions();
|
|
1360
|
+
const previousSession = sessions.find(
|
|
1361
|
+
(s4) => s4.sessionId === this.previousSessionId
|
|
1362
|
+
);
|
|
1363
|
+
if (previousSession && previousSession.accepting) {
|
|
1364
|
+
this.reconnectWithSessionHandlers.forEach((handler) => {
|
|
1365
|
+
try {
|
|
1366
|
+
handler(this.previousSessionId);
|
|
1367
|
+
} catch (e5) {
|
|
1368
|
+
console.error("[lit-shell] Error in reconnectWithSession handler:", e5);
|
|
1369
|
+
}
|
|
1370
|
+
});
|
|
1371
|
+
} else {
|
|
1372
|
+
this.previousSessionId = null;
|
|
1373
|
+
}
|
|
1374
|
+
} catch (e5) {
|
|
1375
|
+
console.error("[lit-shell] Failed to check previous session:", e5);
|
|
1376
|
+
this.previousSessionId = null;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1298
1379
|
};
|
|
1299
1380
|
|
|
1300
1381
|
// src/ui/lit-shell-terminal.ts
|
|
@@ -1310,8 +1391,16 @@ var LitShellTerminal = class extends i4 {
|
|
|
1310
1391
|
this.noHeader = false;
|
|
1311
1392
|
this.autoConnect = false;
|
|
1312
1393
|
this.autoSpawn = false;
|
|
1394
|
+
this.container = "";
|
|
1395
|
+
this.containerShell = "";
|
|
1396
|
+
this.containerUser = "";
|
|
1397
|
+
this.containerCwd = "";
|
|
1398
|
+
this.showConnectionPanel = false;
|
|
1399
|
+
this.showSettings = false;
|
|
1400
|
+
this.showStatusBar = false;
|
|
1401
|
+
this.showTabs = false;
|
|
1313
1402
|
this.fontSize = 14;
|
|
1314
|
-
this.fontFamily = 'Menlo, Monaco, "Courier New", monospace';
|
|
1403
|
+
this.fontFamily = '"Cascadia Mono", "Cascadia Code", Consolas, "Ubuntu Mono", "DejaVu Sans Mono", "Liberation Mono", Hack, "Fira Code", "JetBrains Mono", Menlo, Monaco, "Courier New", monospace';
|
|
1315
1404
|
this.client = null;
|
|
1316
1405
|
this.terminal = null;
|
|
1317
1406
|
this.fitAddon = null;
|
|
@@ -1320,17 +1409,73 @@ var LitShellTerminal = class extends i4 {
|
|
|
1320
1409
|
this.loading = false;
|
|
1321
1410
|
this.error = null;
|
|
1322
1411
|
this.sessionInfo = null;
|
|
1412
|
+
this.containers = [];
|
|
1413
|
+
this.serverInfo = null;
|
|
1414
|
+
this.selectedContainer = "";
|
|
1415
|
+
this.selectedShell = "/bin/sh";
|
|
1416
|
+
this.connectionMode = "local";
|
|
1417
|
+
this.orphanTimeout = 36e5;
|
|
1418
|
+
this.useTmux = false;
|
|
1419
|
+
this.availableSessions = [];
|
|
1420
|
+
this.selectedSessionId = "";
|
|
1421
|
+
this.clientCount = 1;
|
|
1422
|
+
this.settingsMenuOpen = false;
|
|
1423
|
+
this.showReconnectDialog = false;
|
|
1424
|
+
this.reconnectSessionId = null;
|
|
1425
|
+
this.isMobile = false;
|
|
1426
|
+
this.showTouchKeyboard = true;
|
|
1427
|
+
this.showExtraKeyRows = false;
|
|
1428
|
+
this.statusMessage = "";
|
|
1429
|
+
this.statusType = "info";
|
|
1430
|
+
this.tabs = [];
|
|
1431
|
+
this.activeTabId = "";
|
|
1432
|
+
this.tabCounter = 0;
|
|
1323
1433
|
// xterm.js module (loaded dynamically)
|
|
1324
1434
|
this.xtermModule = null;
|
|
1325
1435
|
this.fitAddonModule = null;
|
|
1326
1436
|
this.resizeObserver = null;
|
|
1437
|
+
this.ctrlPressed = false;
|
|
1438
|
+
this.altPressed = false;
|
|
1327
1439
|
}
|
|
1328
1440
|
connectedCallback() {
|
|
1329
1441
|
super.connectedCallback();
|
|
1442
|
+
this.setAttribute("data-version", VERSION);
|
|
1443
|
+
this.detectMobile();
|
|
1444
|
+
if (this.showTabs && this.tabs.length === 0) {
|
|
1445
|
+
this.createTab();
|
|
1446
|
+
}
|
|
1330
1447
|
if (this.autoConnect && this.url) {
|
|
1331
1448
|
this.connect();
|
|
1332
1449
|
}
|
|
1333
1450
|
}
|
|
1451
|
+
/**
|
|
1452
|
+
* Detect if running on a mobile device
|
|
1453
|
+
*/
|
|
1454
|
+
detectMobile() {
|
|
1455
|
+
const hasTouch = navigator.maxTouchPoints > 0;
|
|
1456
|
+
const isMobileWidth = window.matchMedia("(max-width: 768px)").matches;
|
|
1457
|
+
const mobileUA = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
|
|
1458
|
+
navigator.userAgent
|
|
1459
|
+
);
|
|
1460
|
+
this.isMobile = hasTouch && (isMobileWidth || mobileUA);
|
|
1461
|
+
this.updateMobileAttribute();
|
|
1462
|
+
window.matchMedia("(max-width: 768px)").addEventListener("change", (e5) => {
|
|
1463
|
+
if (navigator.maxTouchPoints > 0) {
|
|
1464
|
+
this.isMobile = e5.matches;
|
|
1465
|
+
this.updateMobileAttribute();
|
|
1466
|
+
}
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Update mobile attribute for CSS targeting
|
|
1471
|
+
*/
|
|
1472
|
+
updateMobileAttribute() {
|
|
1473
|
+
if (this.isMobile) {
|
|
1474
|
+
this.setAttribute("mobile", "");
|
|
1475
|
+
} else {
|
|
1476
|
+
this.removeAttribute("mobile");
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1334
1479
|
disconnectedCallback() {
|
|
1335
1480
|
super.disconnectedCallback();
|
|
1336
1481
|
this.cleanup();
|
|
@@ -1352,29 +1497,25 @@ var LitShellTerminal = class extends i4 {
|
|
|
1352
1497
|
throw new Error("Failed to load xterm.js. Make sure it is available.");
|
|
1353
1498
|
}
|
|
1354
1499
|
}
|
|
1355
|
-
await this.
|
|
1500
|
+
await this.injectXtermCSS();
|
|
1356
1501
|
}
|
|
1357
1502
|
/**
|
|
1358
|
-
*
|
|
1359
|
-
* This is necessary because CSS loaded in the main document doesn't apply inside shadow DOM
|
|
1503
|
+
* Inject xterm.js CSS into shadow DOM
|
|
1360
1504
|
*/
|
|
1361
|
-
async
|
|
1505
|
+
async injectXtermCSS() {
|
|
1362
1506
|
if (!this.shadowRoot)
|
|
1363
1507
|
return;
|
|
1364
1508
|
if (this.shadowRoot.querySelector("#xterm-styles"))
|
|
1365
1509
|
return;
|
|
1366
1510
|
try {
|
|
1367
1511
|
const response = await fetch("https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css");
|
|
1368
|
-
|
|
1369
|
-
throw new Error(`Failed to fetch xterm.css: ${response.status}`);
|
|
1370
|
-
}
|
|
1371
|
-
const cssText = await response.text();
|
|
1512
|
+
const css = await response.text();
|
|
1372
1513
|
const style = document.createElement("style");
|
|
1373
1514
|
style.id = "xterm-styles";
|
|
1374
|
-
style.textContent =
|
|
1375
|
-
this.shadowRoot.
|
|
1376
|
-
} catch (
|
|
1377
|
-
console.warn("[lit-shell] Failed to load xterm
|
|
1515
|
+
style.textContent = css;
|
|
1516
|
+
this.shadowRoot.prepend(style);
|
|
1517
|
+
} catch (e5) {
|
|
1518
|
+
console.warn("[lit-shell] Failed to load xterm CSS:", e5);
|
|
1378
1519
|
}
|
|
1379
1520
|
}
|
|
1380
1521
|
/**
|
|
@@ -1404,33 +1545,133 @@ var LitShellTerminal = class extends i4 {
|
|
|
1404
1545
|
});
|
|
1405
1546
|
this.client.onError((err) => {
|
|
1406
1547
|
this.error = err.message;
|
|
1548
|
+
this.setStatus(err.message, "error");
|
|
1407
1549
|
this.dispatchEvent(
|
|
1408
1550
|
new CustomEvent("error", { detail: { error: err }, bubbles: true, composed: true })
|
|
1409
1551
|
);
|
|
1410
1552
|
});
|
|
1553
|
+
const client = this.client;
|
|
1411
1554
|
this.client.onData((data) => {
|
|
1412
|
-
if (this.
|
|
1555
|
+
if (this.showTabs) {
|
|
1556
|
+
const tab = this.tabs.find((t4) => t4.client === client);
|
|
1557
|
+
if (tab?.terminal) {
|
|
1558
|
+
tab.terminal.write(data);
|
|
1559
|
+
}
|
|
1560
|
+
} else if (this.terminal) {
|
|
1413
1561
|
this.terminal.write(data);
|
|
1414
1562
|
}
|
|
1415
1563
|
});
|
|
1416
1564
|
this.client.onExit((code) => {
|
|
1417
|
-
this.
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1565
|
+
if (this.showTabs) {
|
|
1566
|
+
const tab = this.tabs.find((t4) => t4.client === client);
|
|
1567
|
+
if (tab) {
|
|
1568
|
+
tab.sessionActive = false;
|
|
1569
|
+
tab.sessionInfo = null;
|
|
1570
|
+
if (tab.terminal) {
|
|
1571
|
+
tab.terminal.writeln("");
|
|
1572
|
+
tab.terminal.writeln(`\x1B[1;33m[Process exited with code: ${code}]\x1B[0m`);
|
|
1573
|
+
}
|
|
1574
|
+
if (tab.id === this.activeTabId) {
|
|
1575
|
+
this.sessionActive = false;
|
|
1576
|
+
this.sessionInfo = null;
|
|
1577
|
+
}
|
|
1578
|
+
this.tabs = [...this.tabs];
|
|
1579
|
+
}
|
|
1580
|
+
} else {
|
|
1581
|
+
this.sessionActive = false;
|
|
1582
|
+
this.sessionInfo = null;
|
|
1583
|
+
if (this.terminal) {
|
|
1584
|
+
this.terminal.writeln("");
|
|
1585
|
+
this.terminal.writeln(`\x1B[1;33m[Process exited with code: ${code}]\x1B[0m`);
|
|
1586
|
+
}
|
|
1422
1587
|
}
|
|
1423
1588
|
this.dispatchEvent(
|
|
1424
1589
|
new CustomEvent("exit", { detail: { exitCode: code }, bubbles: true, composed: true })
|
|
1425
1590
|
);
|
|
1426
1591
|
});
|
|
1427
1592
|
this.client.onSpawned((info) => {
|
|
1593
|
+
if (this.showTabs) {
|
|
1594
|
+
const tab = this.tabs.find((t4) => t4.client === client);
|
|
1595
|
+
if (tab) {
|
|
1596
|
+
tab.sessionInfo = info;
|
|
1597
|
+
tab.sessionActive = true;
|
|
1598
|
+
tab.label = info.container || info.shell.split("/").pop() || "Terminal";
|
|
1599
|
+
this.tabs = [...this.tabs];
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1428
1602
|
this.sessionInfo = info;
|
|
1603
|
+
this.setStatus(`Session started: ${info.container || info.shell}`, "success");
|
|
1429
1604
|
this.dispatchEvent(
|
|
1430
1605
|
new CustomEvent("spawned", { detail: { session: info }, bubbles: true, composed: true })
|
|
1431
1606
|
);
|
|
1432
1607
|
});
|
|
1608
|
+
this.client.onServerInfo((info) => {
|
|
1609
|
+
this.serverInfo = info;
|
|
1610
|
+
if (info.dockerEnabled) {
|
|
1611
|
+
this.connectionMode = "docker";
|
|
1612
|
+
this.client?.requestContainerList();
|
|
1613
|
+
}
|
|
1614
|
+
this.selectedShell = info.defaultShell;
|
|
1615
|
+
});
|
|
1616
|
+
this.client.onContainerList((containers) => {
|
|
1617
|
+
this.containers = containers;
|
|
1618
|
+
if (containers.length > 0 && !this.selectedContainer) {
|
|
1619
|
+
this.selectedContainer = containers[0].name;
|
|
1620
|
+
}
|
|
1621
|
+
});
|
|
1622
|
+
this.client.onSessionList((sessions) => {
|
|
1623
|
+
this.availableSessions = sessions;
|
|
1624
|
+
if (sessions.length > 0 && !this.selectedSessionId) {
|
|
1625
|
+
this.selectedSessionId = sessions[0].sessionId;
|
|
1626
|
+
}
|
|
1627
|
+
});
|
|
1628
|
+
this.client.onClientJoined((sessionId, count) => {
|
|
1629
|
+
if (this.showTabs) {
|
|
1630
|
+
const tab = this.tabs.find((t4) => t4.client === client);
|
|
1631
|
+
if (tab) {
|
|
1632
|
+
tab.clientCount = count;
|
|
1633
|
+
this.tabs = [...this.tabs];
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
this.clientCount = count;
|
|
1637
|
+
this.setStatus(`Client joined (${count} total)`, "info");
|
|
1638
|
+
});
|
|
1639
|
+
this.client.onClientLeft((sessionId, count) => {
|
|
1640
|
+
if (this.showTabs) {
|
|
1641
|
+
const tab = this.tabs.find((t4) => t4.client === client);
|
|
1642
|
+
if (tab) {
|
|
1643
|
+
tab.clientCount = count;
|
|
1644
|
+
this.tabs = [...this.tabs];
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
this.clientCount = count;
|
|
1648
|
+
this.setStatus(`Client left (${count} remaining)`, "info");
|
|
1649
|
+
});
|
|
1650
|
+
this.client.onSessionClosed((sessionId, reason) => {
|
|
1651
|
+
if (this.showTabs) {
|
|
1652
|
+
const tab = this.tabs.find((t4) => t4.client === client && t4.sessionInfo?.sessionId === sessionId);
|
|
1653
|
+
if (tab) {
|
|
1654
|
+
tab.sessionActive = false;
|
|
1655
|
+
tab.sessionInfo = null;
|
|
1656
|
+
this.tabs = [...this.tabs];
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
if (this.sessionInfo?.sessionId === sessionId) {
|
|
1660
|
+
this.sessionActive = false;
|
|
1661
|
+
this.sessionInfo = null;
|
|
1662
|
+
this.setStatus(`Session closed: ${reason}`, "info");
|
|
1663
|
+
}
|
|
1664
|
+
client.requestSessionList();
|
|
1665
|
+
});
|
|
1666
|
+
this.client.onReconnectWithSession((sessionId) => {
|
|
1667
|
+
this.reconnectSessionId = sessionId;
|
|
1668
|
+
this.showReconnectDialog = true;
|
|
1669
|
+
});
|
|
1433
1670
|
await this.client.connect();
|
|
1671
|
+
this.client.requestSessionList();
|
|
1672
|
+
if (this.showTabs) {
|
|
1673
|
+
this.syncStateToActiveTab();
|
|
1674
|
+
}
|
|
1434
1675
|
} catch (err) {
|
|
1435
1676
|
this.error = err instanceof Error ? err.message : "Connection failed";
|
|
1436
1677
|
} finally {
|
|
@@ -1464,16 +1705,27 @@ var LitShellTerminal = class extends i4 {
|
|
|
1464
1705
|
cwd: options?.cwd || this.cwd || void 0,
|
|
1465
1706
|
cols: this.terminal?.cols || this.cols,
|
|
1466
1707
|
rows: this.terminal?.rows || this.rows,
|
|
1467
|
-
env: options?.env
|
|
1708
|
+
env: options?.env,
|
|
1709
|
+
// Docker container options
|
|
1710
|
+
container: options?.container || this.container || void 0,
|
|
1711
|
+
containerShell: options?.containerShell || this.containerShell || void 0,
|
|
1712
|
+
containerUser: options?.containerUser || this.containerUser || void 0,
|
|
1713
|
+
containerCwd: options?.containerCwd || this.containerCwd || void 0
|
|
1468
1714
|
};
|
|
1469
1715
|
const info = await this.client.spawn(spawnOptions);
|
|
1470
1716
|
this.sessionActive = true;
|
|
1471
1717
|
this.sessionInfo = info;
|
|
1718
|
+
if (this.showTabs) {
|
|
1719
|
+
this.syncStateToActiveTab();
|
|
1720
|
+
}
|
|
1721
|
+
this.client.requestSessionList();
|
|
1472
1722
|
if (this.terminal) {
|
|
1473
1723
|
this.terminal.focus();
|
|
1474
1724
|
}
|
|
1725
|
+
return info;
|
|
1475
1726
|
} catch (err) {
|
|
1476
1727
|
this.error = err instanceof Error ? err.message : "Failed to spawn session";
|
|
1728
|
+
throw err;
|
|
1477
1729
|
} finally {
|
|
1478
1730
|
this.loading = false;
|
|
1479
1731
|
}
|
|
@@ -1486,7 +1738,12 @@ var LitShellTerminal = class extends i4 {
|
|
|
1486
1738
|
return;
|
|
1487
1739
|
await this.loadXterm();
|
|
1488
1740
|
await this.updateComplete;
|
|
1489
|
-
|
|
1741
|
+
let container = null;
|
|
1742
|
+
if (this.showTabs && this.activeTabId) {
|
|
1743
|
+
container = this.shadowRoot?.querySelector(`.tab-terminal-container[data-tab-id="${this.activeTabId}"]`) ?? null;
|
|
1744
|
+
} else {
|
|
1745
|
+
container = this.shadowRoot?.querySelector(".terminal-container") ?? null;
|
|
1746
|
+
}
|
|
1490
1747
|
if (!container)
|
|
1491
1748
|
return;
|
|
1492
1749
|
const terminalTheme = this.getTerminalTheme();
|
|
@@ -1516,30 +1773,48 @@ var LitShellTerminal = class extends i4 {
|
|
|
1516
1773
|
this.client.resize(cols, rows);
|
|
1517
1774
|
}
|
|
1518
1775
|
});
|
|
1519
|
-
this.resizeObserver
|
|
1520
|
-
|
|
1521
|
-
this.
|
|
1522
|
-
|
|
1523
|
-
|
|
1776
|
+
if (!this.resizeObserver) {
|
|
1777
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
1778
|
+
if (this.showTabs) {
|
|
1779
|
+
const activeTab = this.getActiveTab();
|
|
1780
|
+
if (activeTab?.fitAddon) {
|
|
1781
|
+
activeTab.fitAddon.fit();
|
|
1782
|
+
}
|
|
1783
|
+
} else if (this.fitAddon) {
|
|
1784
|
+
this.fitAddon.fit();
|
|
1785
|
+
}
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1524
1788
|
this.resizeObserver.observe(container);
|
|
1789
|
+
if (this.showTabs) {
|
|
1790
|
+
this.syncStateToActiveTab();
|
|
1791
|
+
}
|
|
1525
1792
|
}
|
|
1526
1793
|
/**
|
|
1527
1794
|
* Get terminal theme based on component theme
|
|
1528
1795
|
*/
|
|
1529
1796
|
getTerminalTheme() {
|
|
1530
|
-
|
|
1797
|
+
let effectiveTheme = this.theme;
|
|
1798
|
+
if (this.theme === "auto") {
|
|
1799
|
+
effectiveTheme = window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
|
|
1800
|
+
}
|
|
1801
|
+
if (effectiveTheme === "light") {
|
|
1531
1802
|
return {
|
|
1532
1803
|
background: "#ffffff",
|
|
1533
1804
|
foreground: "#1f2937",
|
|
1534
1805
|
cursor: "#1f2937",
|
|
1535
|
-
|
|
1806
|
+
cursorAccent: "#ffffff",
|
|
1807
|
+
selection: "#b4d5fe",
|
|
1808
|
+
selectionForeground: "#1f2937"
|
|
1536
1809
|
};
|
|
1537
1810
|
}
|
|
1538
1811
|
return {
|
|
1539
1812
|
background: "#1e1e1e",
|
|
1540
1813
|
foreground: "#cccccc",
|
|
1541
1814
|
cursor: "#ffffff",
|
|
1542
|
-
|
|
1815
|
+
cursorAccent: "#1e1e1e",
|
|
1816
|
+
selection: "#264f78",
|
|
1817
|
+
selectionForeground: "#ffffff"
|
|
1543
1818
|
};
|
|
1544
1819
|
}
|
|
1545
1820
|
/**
|
|
@@ -1602,83 +1877,811 @@ var LitShellTerminal = class extends i4 {
|
|
|
1602
1877
|
}
|
|
1603
1878
|
this.fitAddon = null;
|
|
1604
1879
|
}
|
|
1605
|
-
|
|
1880
|
+
// ==================== Tab Management Methods ====================
|
|
1881
|
+
/**
|
|
1882
|
+
* Get the active tab
|
|
1883
|
+
*/
|
|
1884
|
+
getActiveTab() {
|
|
1885
|
+
return this.tabs.find((t4) => t4.id === this.activeTabId);
|
|
1886
|
+
}
|
|
1887
|
+
/**
|
|
1888
|
+
* Create a new tab
|
|
1889
|
+
*/
|
|
1890
|
+
createTab(label) {
|
|
1891
|
+
this.tabCounter++;
|
|
1892
|
+
const tab = {
|
|
1893
|
+
id: `tab-${this.tabCounter}`,
|
|
1894
|
+
label: label || `Terminal ${this.tabCounter}`,
|
|
1895
|
+
client: null,
|
|
1896
|
+
terminal: null,
|
|
1897
|
+
fitAddon: null,
|
|
1898
|
+
connected: false,
|
|
1899
|
+
sessionActive: false,
|
|
1900
|
+
sessionInfo: null,
|
|
1901
|
+
clientCount: 1,
|
|
1902
|
+
containerEl: null
|
|
1903
|
+
};
|
|
1904
|
+
this.tabs = [...this.tabs, tab];
|
|
1905
|
+
this.switchTab(tab.id);
|
|
1906
|
+
return tab;
|
|
1907
|
+
}
|
|
1908
|
+
/**
|
|
1909
|
+
* Switch to a tab
|
|
1910
|
+
*/
|
|
1911
|
+
switchTab(tabId) {
|
|
1912
|
+
const tab = this.tabs.find((t4) => t4.id === tabId);
|
|
1913
|
+
if (!tab)
|
|
1914
|
+
return;
|
|
1915
|
+
this.activeTabId = tabId;
|
|
1916
|
+
this.client = tab.client;
|
|
1917
|
+
this.terminal = tab.terminal;
|
|
1918
|
+
this.fitAddon = tab.fitAddon;
|
|
1919
|
+
this.connected = tab.connected;
|
|
1920
|
+
this.sessionActive = tab.sessionActive;
|
|
1921
|
+
this.sessionInfo = tab.sessionInfo;
|
|
1922
|
+
this.clientCount = tab.clientCount;
|
|
1923
|
+
this.updateComplete.then(() => {
|
|
1924
|
+
if (tab.terminal) {
|
|
1925
|
+
tab.terminal.focus();
|
|
1926
|
+
}
|
|
1927
|
+
if (tab.fitAddon) {
|
|
1928
|
+
tab.fitAddon.fit();
|
|
1929
|
+
}
|
|
1930
|
+
});
|
|
1931
|
+
}
|
|
1932
|
+
/**
|
|
1933
|
+
* Close a tab
|
|
1934
|
+
*/
|
|
1935
|
+
closeTab(tabId) {
|
|
1936
|
+
const tabIndex = this.tabs.findIndex((t4) => t4.id === tabId);
|
|
1937
|
+
if (tabIndex === -1)
|
|
1938
|
+
return;
|
|
1939
|
+
const tab = this.tabs[tabIndex];
|
|
1940
|
+
if (tab.terminal) {
|
|
1941
|
+
tab.terminal.dispose();
|
|
1942
|
+
}
|
|
1943
|
+
if (tab.client) {
|
|
1944
|
+
tab.client.disconnect();
|
|
1945
|
+
}
|
|
1946
|
+
this.tabs = this.tabs.filter((t4) => t4.id !== tabId);
|
|
1947
|
+
if (this.activeTabId === tabId && this.tabs.length > 0) {
|
|
1948
|
+
const newIndex = Math.max(0, tabIndex - 1);
|
|
1949
|
+
this.switchTab(this.tabs[newIndex].id);
|
|
1950
|
+
}
|
|
1951
|
+
if (this.tabs.length === 0) {
|
|
1952
|
+
this.activeTabId = "";
|
|
1953
|
+
this.client = null;
|
|
1954
|
+
this.terminal = null;
|
|
1955
|
+
this.fitAddon = null;
|
|
1956
|
+
this.connected = false;
|
|
1957
|
+
this.sessionActive = false;
|
|
1958
|
+
this.sessionInfo = null;
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
/**
|
|
1962
|
+
* Update the active tab's state from component state
|
|
1963
|
+
*/
|
|
1964
|
+
syncStateToActiveTab() {
|
|
1965
|
+
const tab = this.getActiveTab();
|
|
1966
|
+
if (tab) {
|
|
1967
|
+
tab.client = this.client;
|
|
1968
|
+
tab.terminal = this.terminal;
|
|
1969
|
+
tab.fitAddon = this.fitAddon;
|
|
1970
|
+
tab.connected = this.connected;
|
|
1971
|
+
tab.sessionActive = this.sessionActive;
|
|
1972
|
+
tab.sessionInfo = this.sessionInfo;
|
|
1973
|
+
tab.clientCount = this.clientCount;
|
|
1974
|
+
this.tabs = [...this.tabs];
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
/**
|
|
1978
|
+
* Render the tab bar
|
|
1979
|
+
*/
|
|
1980
|
+
renderTabBar() {
|
|
1981
|
+
if (!this.showTabs)
|
|
1982
|
+
return A;
|
|
1606
1983
|
return b2`
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
<
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1984
|
+
<div class="tab-bar">
|
|
1985
|
+
${this.tabs.map(
|
|
1986
|
+
(tab) => b2`
|
|
1987
|
+
<button
|
|
1988
|
+
class="tab ${tab.id === this.activeTabId ? "active" : ""}"
|
|
1989
|
+
@click=${() => this.switchTab(tab.id)}
|
|
1990
|
+
>
|
|
1991
|
+
<span class="tab-status ${tab.sessionActive ? "connected" : ""}"></span>
|
|
1992
|
+
<span>${tab.label}</span>
|
|
1993
|
+
${this.tabs.length > 1 ? b2`
|
|
1994
|
+
<button
|
|
1995
|
+
class="tab-close"
|
|
1996
|
+
@click=${(e5) => {
|
|
1997
|
+
e5.stopPropagation();
|
|
1998
|
+
this.closeTab(tab.id);
|
|
1999
|
+
}}
|
|
2000
|
+
title="Close tab"
|
|
2001
|
+
>
|
|
2002
|
+
×
|
|
2003
|
+
</button>
|
|
2004
|
+
` : A}
|
|
2005
|
+
</button>
|
|
2006
|
+
`
|
|
2007
|
+
)}
|
|
2008
|
+
<button class="tab-add" @click=${() => this.createTab()} title="New tab">
|
|
2009
|
+
+
|
|
2010
|
+
</button>
|
|
1632
2011
|
</div>
|
|
1633
2012
|
`;
|
|
1634
2013
|
}
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
2014
|
+
// ==================== End Tab Management Methods ====================
|
|
2015
|
+
/**
|
|
2016
|
+
* Set status message
|
|
2017
|
+
*/
|
|
2018
|
+
setStatus(message, type = "info") {
|
|
2019
|
+
this.statusMessage = message;
|
|
2020
|
+
this.statusType = type;
|
|
2021
|
+
if (type !== "error") {
|
|
2022
|
+
setTimeout(() => {
|
|
2023
|
+
if (this.statusMessage === message) {
|
|
2024
|
+
this.statusMessage = "";
|
|
2025
|
+
}
|
|
2026
|
+
}, 5e3);
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
/**
|
|
2030
|
+
* Clear status message
|
|
2031
|
+
*/
|
|
2032
|
+
clearStatus() {
|
|
2033
|
+
this.statusMessage = "";
|
|
2034
|
+
this.statusType = "info";
|
|
2035
|
+
}
|
|
2036
|
+
/**
|
|
2037
|
+
* Handle theme change
|
|
2038
|
+
*/
|
|
2039
|
+
handleThemeChange(e5) {
|
|
2040
|
+
const select = e5.target;
|
|
2041
|
+
this.theme = select.value;
|
|
2042
|
+
this.applyTerminalTheme();
|
|
2043
|
+
this.dispatchEvent(new CustomEvent("theme-change", {
|
|
2044
|
+
detail: { theme: this.theme },
|
|
2045
|
+
bubbles: true,
|
|
2046
|
+
composed: true
|
|
2047
|
+
}));
|
|
2048
|
+
}
|
|
2049
|
+
/**
|
|
2050
|
+
* Apply current theme to xterm.js terminal
|
|
2051
|
+
*/
|
|
2052
|
+
applyTerminalTheme() {
|
|
2053
|
+
if (!this.terminal)
|
|
2054
|
+
return;
|
|
2055
|
+
const terminalTheme = this.getTerminalTheme();
|
|
2056
|
+
this.terminal.options.theme = terminalTheme;
|
|
2057
|
+
}
|
|
2058
|
+
/**
|
|
2059
|
+
* Apply current font size to xterm.js terminal
|
|
2060
|
+
*/
|
|
2061
|
+
applyTerminalFontSize() {
|
|
2062
|
+
if (!this.terminal)
|
|
2063
|
+
return;
|
|
2064
|
+
this.terminal.options.fontSize = this.fontSize;
|
|
2065
|
+
if (this.fitAddon) {
|
|
2066
|
+
this.fitAddon.fit();
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
/**
|
|
2070
|
+
* Handle connection mode change
|
|
2071
|
+
*/
|
|
2072
|
+
handleModeChange(e5) {
|
|
2073
|
+
const select = e5.target;
|
|
2074
|
+
this.connectionMode = select.value;
|
|
2075
|
+
if ((this.connectionMode === "docker" || this.connectionMode === "docker-attach") && this.client && this.connected) {
|
|
2076
|
+
this.client.requestContainerList();
|
|
2077
|
+
}
|
|
2078
|
+
if (this.connectionMode === "join" && this.client && this.connected) {
|
|
2079
|
+
this.client.requestSessionList();
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
/**
|
|
2083
|
+
* Refresh session list
|
|
2084
|
+
*/
|
|
2085
|
+
refreshSessions() {
|
|
2086
|
+
if (this.client && this.connected) {
|
|
2087
|
+
this.client.requestSessionList();
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
/**
|
|
2091
|
+
* Join an existing session
|
|
2092
|
+
*/
|
|
2093
|
+
async join(sessionId, requestHistory = true) {
|
|
2094
|
+
if (!this.client || !this.connected) {
|
|
2095
|
+
throw new Error("Not connected to server");
|
|
2096
|
+
}
|
|
2097
|
+
this.loading = true;
|
|
2098
|
+
this.error = null;
|
|
2099
|
+
try {
|
|
2100
|
+
await this.initTerminalUI();
|
|
2101
|
+
const session = await this.client.join({
|
|
2102
|
+
sessionId,
|
|
2103
|
+
requestHistory,
|
|
2104
|
+
historyLimit: 5e4
|
|
2105
|
+
});
|
|
2106
|
+
this.sessionActive = true;
|
|
2107
|
+
this.sessionInfo = {
|
|
2108
|
+
sessionId: session.sessionId,
|
|
2109
|
+
shell: session.shell,
|
|
2110
|
+
cwd: session.cwd,
|
|
2111
|
+
cols: session.cols,
|
|
2112
|
+
rows: session.rows,
|
|
2113
|
+
createdAt: session.createdAt || /* @__PURE__ */ new Date(),
|
|
2114
|
+
container: session.container
|
|
2115
|
+
};
|
|
2116
|
+
this.clientCount = session.clientCount;
|
|
2117
|
+
if (this.showTabs) {
|
|
2118
|
+
this.syncStateToActiveTab();
|
|
1649
2119
|
}
|
|
1650
|
-
|
|
1651
|
-
.
|
|
1652
|
-
|
|
1653
|
-
align-items: center;
|
|
1654
|
-
justify-content: space-between;
|
|
1655
|
-
padding: 8px 12px;
|
|
1656
|
-
background: var(--ls-bg-header);
|
|
1657
|
-
border-bottom: 1px solid var(--ls-border);
|
|
2120
|
+
this.setStatus(`Joined session (${session.clientCount} clients)`, "success");
|
|
2121
|
+
if (this.terminal) {
|
|
2122
|
+
this.terminal.focus();
|
|
1658
2123
|
}
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
2124
|
+
return session;
|
|
2125
|
+
} catch (err) {
|
|
2126
|
+
this.error = err instanceof Error ? err.message : "Failed to join session";
|
|
2127
|
+
throw err;
|
|
2128
|
+
} finally {
|
|
2129
|
+
this.loading = false;
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
/**
|
|
2133
|
+
* Leave current session without killing it
|
|
2134
|
+
*/
|
|
2135
|
+
leave() {
|
|
2136
|
+
if (this.client && this.sessionInfo) {
|
|
2137
|
+
this.client.leave(this.sessionInfo.sessionId);
|
|
2138
|
+
this.sessionActive = false;
|
|
2139
|
+
this.sessionInfo = null;
|
|
2140
|
+
this.setStatus("Left session", "info");
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
/**
|
|
2144
|
+
* Handle connect from connection panel
|
|
2145
|
+
*/
|
|
2146
|
+
async handlePanelConnect() {
|
|
2147
|
+
if (!this.connected) {
|
|
2148
|
+
await this.connect();
|
|
2149
|
+
}
|
|
2150
|
+
if (this.connected) {
|
|
2151
|
+
if (this.connectionMode === "join" && this.selectedSessionId) {
|
|
2152
|
+
await this.join(this.selectedSessionId);
|
|
2153
|
+
} else if (this.connectionMode === "docker-attach" && this.selectedContainer) {
|
|
2154
|
+
const options = {
|
|
2155
|
+
container: this.selectedContainer,
|
|
2156
|
+
attachMode: true,
|
|
2157
|
+
orphanTimeout: this.orphanTimeout
|
|
2158
|
+
};
|
|
2159
|
+
await this.spawn(options);
|
|
2160
|
+
} else {
|
|
2161
|
+
const options = {
|
|
2162
|
+
orphanTimeout: this.orphanTimeout,
|
|
2163
|
+
useTmux: this.useTmux
|
|
2164
|
+
};
|
|
2165
|
+
if (this.connectionMode === "docker" && this.selectedContainer) {
|
|
2166
|
+
options.container = this.selectedContainer;
|
|
2167
|
+
options.containerShell = this.selectedShell || "/bin/sh";
|
|
2168
|
+
} else {
|
|
2169
|
+
options.shell = this.selectedShell || void 0;
|
|
2170
|
+
}
|
|
2171
|
+
await this.spawn(options);
|
|
1665
2172
|
}
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
/**
|
|
2176
|
+
* Toggle settings menu
|
|
2177
|
+
*/
|
|
2178
|
+
toggleSettingsMenu() {
|
|
2179
|
+
this.settingsMenuOpen = !this.settingsMenuOpen;
|
|
2180
|
+
}
|
|
2181
|
+
/**
|
|
2182
|
+
* Handle reconnect dialog - Yes button
|
|
2183
|
+
*/
|
|
2184
|
+
async handleReconnectYes() {
|
|
2185
|
+
if (!this.reconnectSessionId || !this.client)
|
|
2186
|
+
return;
|
|
2187
|
+
this.showReconnectDialog = false;
|
|
2188
|
+
try {
|
|
2189
|
+
await this.initTerminalUI();
|
|
2190
|
+
await this.join(this.reconnectSessionId, true);
|
|
2191
|
+
this.setStatus("Rejoined previous session", "success");
|
|
2192
|
+
} catch (err) {
|
|
2193
|
+
this.setStatus(`Failed to rejoin: ${err instanceof Error ? err.message : "Unknown error"}`, "error");
|
|
2194
|
+
}
|
|
2195
|
+
this.reconnectSessionId = null;
|
|
2196
|
+
this.client?.clearPreviousSessionId();
|
|
2197
|
+
}
|
|
2198
|
+
/**
|
|
2199
|
+
* Handle reconnect dialog - No button
|
|
2200
|
+
*/
|
|
2201
|
+
handleReconnectNo() {
|
|
2202
|
+
this.showReconnectDialog = false;
|
|
2203
|
+
this.reconnectSessionId = null;
|
|
2204
|
+
this.client?.clearPreviousSessionId();
|
|
2205
|
+
}
|
|
2206
|
+
/**
|
|
2207
|
+
* Render reconnect dialog
|
|
2208
|
+
*/
|
|
2209
|
+
renderReconnectDialog() {
|
|
2210
|
+
if (!this.showReconnectDialog)
|
|
2211
|
+
return A;
|
|
2212
|
+
return b2`
|
|
2213
|
+
<div class="reconnect-dialog-overlay">
|
|
2214
|
+
<div class="reconnect-dialog">
|
|
2215
|
+
<h3>Session Available</h3>
|
|
2216
|
+
<p>Your previous session is still active. Would you like to rejoin?</p>
|
|
2217
|
+
<div class="reconnect-dialog-buttons">
|
|
2218
|
+
<button class="btn-primary" @click=${this.handleReconnectYes}>
|
|
2219
|
+
Yes, Rejoin
|
|
2220
|
+
</button>
|
|
2221
|
+
<button class="btn-secondary" @click=${this.handleReconnectNo}>
|
|
2222
|
+
No, Start New
|
|
2223
|
+
</button>
|
|
2224
|
+
</div>
|
|
2225
|
+
</div>
|
|
2226
|
+
</div>
|
|
2227
|
+
`;
|
|
2228
|
+
}
|
|
2229
|
+
/**
|
|
2230
|
+
* Render connection panel
|
|
2231
|
+
*/
|
|
2232
|
+
renderConnectionPanel() {
|
|
2233
|
+
if (!this.showConnectionPanel)
|
|
2234
|
+
return A;
|
|
2235
|
+
const runningContainers = this.containers.filter((c4) => c4.state === "running");
|
|
2236
|
+
const acceptingSessions = this.availableSessions.filter((s4) => s4.accepting);
|
|
2237
|
+
return b2`
|
|
2238
|
+
<div class="connection-panel">
|
|
2239
|
+
<div class="connection-panel-title">
|
|
2240
|
+
<span>Connection</span>
|
|
2241
|
+
${this.availableSessions.length > 0 ? b2`<span style="font-size: 11px; color: var(--ls-status-connected);">${this.availableSessions.length} session(s) available</span>` : A}
|
|
2242
|
+
${this.serverInfo?.dockerEnabled ? b2`<span style="font-size: 11px; color: var(--ls-text-muted);">Docker enabled</span>` : A}
|
|
2243
|
+
</div>
|
|
2244
|
+
<div class="connection-form">
|
|
2245
|
+
<div class="form-group">
|
|
2246
|
+
<label>Mode</label>
|
|
2247
|
+
<select
|
|
2248
|
+
.value=${this.connectionMode}
|
|
2249
|
+
@change=${this.handleModeChange}
|
|
2250
|
+
?disabled=${this.sessionActive}
|
|
2251
|
+
>
|
|
2252
|
+
<option value="local">New Local Shell</option>
|
|
2253
|
+
${this.serverInfo?.dockerEnabled ? b2`
|
|
2254
|
+
<option value="docker">Docker Exec (new shell)</option>
|
|
2255
|
+
<option value="docker-attach">Docker Attach (main process)</option>
|
|
2256
|
+
` : A}
|
|
2257
|
+
${acceptingSessions.length > 0 ? b2`<option value="join">Join Existing Session</option>` : A}
|
|
2258
|
+
</select>
|
|
2259
|
+
</div>
|
|
1666
2260
|
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
2261
|
+
${this.connectionMode === "join" ? b2`
|
|
2262
|
+
<div class="form-group">
|
|
2263
|
+
<label style="display: flex; justify-content: space-between; align-items: center;">
|
|
2264
|
+
<span>Session</span>
|
|
2265
|
+
<button
|
|
2266
|
+
style="font-size: 10px; padding: 2px 6px;"
|
|
2267
|
+
@click=${this.refreshSessions}
|
|
2268
|
+
?disabled=${!this.connected}
|
|
2269
|
+
>Refresh</button>
|
|
2270
|
+
</label>
|
|
2271
|
+
<select
|
|
2272
|
+
.value=${this.selectedSessionId}
|
|
2273
|
+
@change=${(e5) => this.selectedSessionId = e5.target.value}
|
|
2274
|
+
?disabled=${this.sessionActive}
|
|
2275
|
+
>
|
|
2276
|
+
${acceptingSessions.length === 0 ? b2`<option value="">No sessions available</option>` : acceptingSessions.map((s4) => b2`
|
|
2277
|
+
<option value=${s4.sessionId}>
|
|
2278
|
+
${s4.label || s4.sessionId.substring(0, 12)}
|
|
2279
|
+
(${s4.type === "local" ? s4.shell : s4.container || s4.type})
|
|
2280
|
+
- ${s4.clientCount} client(s)
|
|
2281
|
+
</option>
|
|
2282
|
+
`)}
|
|
2283
|
+
</select>
|
|
2284
|
+
</div>
|
|
2285
|
+
` : this.connectionMode === "docker" || this.connectionMode === "docker-attach" ? b2`
|
|
2286
|
+
<div class="form-group">
|
|
2287
|
+
<label>Container</label>
|
|
2288
|
+
<select
|
|
2289
|
+
.value=${this.selectedContainer}
|
|
2290
|
+
@change=${(e5) => this.selectedContainer = e5.target.value}
|
|
2291
|
+
?disabled=${this.sessionActive}
|
|
2292
|
+
>
|
|
2293
|
+
${runningContainers.length === 0 ? b2`<option value="">No containers running</option>` : runningContainers.map((c4) => b2`
|
|
2294
|
+
<option value=${c4.name}>${c4.name} (${c4.image})</option>
|
|
2295
|
+
`)}
|
|
2296
|
+
</select>
|
|
2297
|
+
</div>
|
|
2298
|
+
` : A}
|
|
1671
2299
|
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
2300
|
+
${this.connectionMode !== "join" && this.connectionMode !== "docker-attach" ? b2`
|
|
2301
|
+
<div class="form-group">
|
|
2302
|
+
<label>Shell</label>
|
|
2303
|
+
<select
|
|
2304
|
+
.value=${this.selectedShell}
|
|
2305
|
+
@change=${(e5) => this.selectedShell = e5.target.value}
|
|
2306
|
+
?disabled=${this.sessionActive}
|
|
2307
|
+
>
|
|
2308
|
+
${this.serverInfo?.allowedShells.length ? this.serverInfo.allowedShells.map((s4) => b2`<option value=${s4}>${s4}</option>`) : b2`
|
|
2309
|
+
<option value="/bin/bash">/bin/bash</option>
|
|
2310
|
+
<option value="/bin/sh">/bin/sh</option>
|
|
2311
|
+
<option value="/bin/zsh">/bin/zsh</option>
|
|
2312
|
+
`}
|
|
2313
|
+
</select>
|
|
2314
|
+
</div>
|
|
2315
|
+
` : A}
|
|
1679
2316
|
|
|
1680
|
-
|
|
1681
|
-
|
|
2317
|
+
${this.connectionMode === "local" || this.connectionMode === "docker" ? b2`
|
|
2318
|
+
<div class="form-group">
|
|
2319
|
+
<label>Session Timeout</label>
|
|
2320
|
+
<select
|
|
2321
|
+
.value=${String(this.orphanTimeout)}
|
|
2322
|
+
@change=${(e5) => this.orphanTimeout = parseInt(e5.target.value)}
|
|
2323
|
+
?disabled=${this.sessionActive}
|
|
2324
|
+
>
|
|
2325
|
+
<option value="60000">1 minute</option>
|
|
2326
|
+
<option value="300000">5 minutes</option>
|
|
2327
|
+
<option value="900000">15 minutes</option>
|
|
2328
|
+
<option value="3600000">1 hour</option>
|
|
2329
|
+
<option value="21600000">6 hours</option>
|
|
2330
|
+
<option value="86400000">24 hours</option>
|
|
2331
|
+
<option value="604800000">1 week</option>
|
|
2332
|
+
</select>
|
|
2333
|
+
</div>
|
|
2334
|
+
` : A}
|
|
2335
|
+
|
|
2336
|
+
${this.connectionMode === "docker" ? b2`
|
|
2337
|
+
<div class="form-group">
|
|
2338
|
+
<label style="display: flex; align-items: center; gap: 6px;">
|
|
2339
|
+
<input
|
|
2340
|
+
type="checkbox"
|
|
2341
|
+
.checked=${this.useTmux}
|
|
2342
|
+
@change=${(e5) => this.useTmux = e5.target.checked}
|
|
2343
|
+
?disabled=${this.sessionActive}
|
|
2344
|
+
/>
|
|
2345
|
+
Use tmux (persist forever)
|
|
2346
|
+
</label>
|
|
2347
|
+
</div>
|
|
2348
|
+
` : A}
|
|
2349
|
+
|
|
2350
|
+
<div class="form-group">
|
|
2351
|
+
${!this.connected ? b2`<button class="btn-primary" @click=${this.handlePanelConnect} ?disabled=${this.loading}>
|
|
2352
|
+
${this.loading ? "Connecting..." : "Connect"}
|
|
2353
|
+
</button>` : !this.sessionActive ? b2`<button class="btn-primary" @click=${this.handlePanelConnect} ?disabled=${this.loading || this.connectionMode === "join" && !this.selectedSessionId || (this.connectionMode === "docker" || this.connectionMode === "docker-attach") && !this.selectedContainer}>
|
|
2354
|
+
${this.loading ? "Starting..." : this.connectionMode === "join" ? "Join Session" : this.connectionMode === "docker-attach" ? "Attach" : "Start Session"}
|
|
2355
|
+
</button>` : b2`<button class="btn-danger" @click=${this.kill}>
|
|
2356
|
+
${this.clientCount > 1 ? "Leave Session" : "Stop Session"}
|
|
2357
|
+
</button>`}
|
|
2358
|
+
</div>
|
|
2359
|
+
</div>
|
|
2360
|
+
</div>
|
|
2361
|
+
`;
|
|
2362
|
+
}
|
|
2363
|
+
/**
|
|
2364
|
+
* Render settings dropdown
|
|
2365
|
+
*/
|
|
2366
|
+
renderSettingsDropdown() {
|
|
2367
|
+
if (!this.showSettings)
|
|
2368
|
+
return A;
|
|
2369
|
+
return b2`
|
|
2370
|
+
<div class="settings-dropdown">
|
|
2371
|
+
<button @click=${this.toggleSettingsMenu} title="Settings">
|
|
2372
|
+
⚙️
|
|
2373
|
+
</button>
|
|
2374
|
+
${this.settingsMenuOpen ? b2`
|
|
2375
|
+
<div class="settings-menu">
|
|
2376
|
+
<div class="settings-menu-item">
|
|
2377
|
+
<span>Theme</span>
|
|
2378
|
+
<select
|
|
2379
|
+
.value=${this.theme}
|
|
2380
|
+
@change=${this.handleThemeChange}
|
|
2381
|
+
>
|
|
2382
|
+
<option value="dark">Dark</option>
|
|
2383
|
+
<option value="light">Light</option>
|
|
2384
|
+
<option value="auto">Auto</option>
|
|
2385
|
+
</select>
|
|
2386
|
+
</div>
|
|
2387
|
+
<div class="settings-divider"></div>
|
|
2388
|
+
<div class="settings-menu-item">
|
|
2389
|
+
<span>Font Size</span>
|
|
2390
|
+
<select
|
|
2391
|
+
.value=${String(this.fontSize)}
|
|
2392
|
+
@change=${(e5) => {
|
|
2393
|
+
this.fontSize = parseInt(e5.target.value);
|
|
2394
|
+
this.applyTerminalFontSize();
|
|
2395
|
+
}}
|
|
2396
|
+
>
|
|
2397
|
+
<option value="12">12px</option>
|
|
2398
|
+
<option value="14">14px</option>
|
|
2399
|
+
<option value="16">16px</option>
|
|
2400
|
+
<option value="18">18px</option>
|
|
2401
|
+
</select>
|
|
2402
|
+
</div>
|
|
2403
|
+
<div class="settings-divider"></div>
|
|
2404
|
+
<div class="settings-menu-item" @click=${this.clear}>
|
|
2405
|
+
<span>Clear Terminal</span>
|
|
2406
|
+
</div>
|
|
2407
|
+
</div>
|
|
2408
|
+
` : A}
|
|
2409
|
+
</div>
|
|
2410
|
+
`;
|
|
2411
|
+
}
|
|
2412
|
+
/**
|
|
2413
|
+
* Send a special key sequence to the terminal
|
|
2414
|
+
*/
|
|
2415
|
+
sendKey(key) {
|
|
2416
|
+
if (!this.client || !this.sessionActive)
|
|
2417
|
+
return;
|
|
2418
|
+
let sequence = key;
|
|
2419
|
+
if (this.ctrlPressed) {
|
|
2420
|
+
if (key.length === 1 && key >= "a" && key <= "z") {
|
|
2421
|
+
sequence = String.fromCharCode(key.charCodeAt(0) - 96);
|
|
2422
|
+
} else if (key.length === 1 && key >= "A" && key <= "Z") {
|
|
2423
|
+
sequence = String.fromCharCode(key.charCodeAt(0) - 64);
|
|
2424
|
+
}
|
|
2425
|
+
this.ctrlPressed = false;
|
|
2426
|
+
}
|
|
2427
|
+
if (this.altPressed) {
|
|
2428
|
+
sequence = "\x1B" + sequence;
|
|
2429
|
+
this.altPressed = false;
|
|
2430
|
+
}
|
|
2431
|
+
this.client.write(sequence);
|
|
2432
|
+
this.terminal?.focus();
|
|
2433
|
+
}
|
|
2434
|
+
/**
|
|
2435
|
+
* Send ANSI escape sequence
|
|
2436
|
+
*/
|
|
2437
|
+
sendEscape(code) {
|
|
2438
|
+
if (!this.client || !this.sessionActive)
|
|
2439
|
+
return;
|
|
2440
|
+
this.client.write("\x1B" + code);
|
|
2441
|
+
this.terminal?.focus();
|
|
2442
|
+
}
|
|
2443
|
+
/**
|
|
2444
|
+
* Send control character directly
|
|
2445
|
+
*/
|
|
2446
|
+
sendCtrl(char) {
|
|
2447
|
+
if (!this.client || !this.sessionActive)
|
|
2448
|
+
return;
|
|
2449
|
+
const code = char.toUpperCase().charCodeAt(0) - 64;
|
|
2450
|
+
this.client.write(String.fromCharCode(code));
|
|
2451
|
+
this.terminal?.focus();
|
|
2452
|
+
}
|
|
2453
|
+
/**
|
|
2454
|
+
* Toggle Ctrl modifier
|
|
2455
|
+
*/
|
|
2456
|
+
toggleCtrl() {
|
|
2457
|
+
this.ctrlPressed = !this.ctrlPressed;
|
|
2458
|
+
this.altPressed = false;
|
|
2459
|
+
}
|
|
2460
|
+
/**
|
|
2461
|
+
* Toggle Alt modifier
|
|
2462
|
+
*/
|
|
2463
|
+
toggleAlt() {
|
|
2464
|
+
this.altPressed = !this.altPressed;
|
|
2465
|
+
this.ctrlPressed = false;
|
|
2466
|
+
}
|
|
2467
|
+
/**
|
|
2468
|
+
* Toggle extra key rows visibility
|
|
2469
|
+
*/
|
|
2470
|
+
toggleExtraRows() {
|
|
2471
|
+
this.showExtraKeyRows = !this.showExtraKeyRows;
|
|
2472
|
+
}
|
|
2473
|
+
/**
|
|
2474
|
+
* Toggle touch keyboard visibility
|
|
2475
|
+
*/
|
|
2476
|
+
toggleTouchKeyboard() {
|
|
2477
|
+
this.showTouchKeyboard = !this.showTouchKeyboard;
|
|
2478
|
+
}
|
|
2479
|
+
/**
|
|
2480
|
+
* Render touch keyboard for mobile
|
|
2481
|
+
*/
|
|
2482
|
+
renderTouchKeyboard() {
|
|
2483
|
+
if (!this.isMobile)
|
|
2484
|
+
return A;
|
|
2485
|
+
return b2`
|
|
2486
|
+
<!-- Toggle bar to show/hide keyboard -->
|
|
2487
|
+
<div class="touch-keyboard-toggle">
|
|
2488
|
+
<button @click=${this.toggleTouchKeyboard} title="${this.showTouchKeyboard ? "Hide" : "Show"} keyboard">
|
|
2489
|
+
${this.showTouchKeyboard ? "\u25BC" : "\u25B2"}
|
|
2490
|
+
</button>
|
|
2491
|
+
</div>
|
|
2492
|
+
|
|
2493
|
+
${this.showTouchKeyboard ? b2`
|
|
2494
|
+
<div class="touch-keyboard">
|
|
2495
|
+
<!-- Row 1: ESC, navigation, special -->
|
|
2496
|
+
<div class="touch-keyboard-row">
|
|
2497
|
+
<button class="touch-key" @click=${() => this.sendEscape("")}>ESC</button>
|
|
2498
|
+
<button class="touch-key" @click=${() => this.sendKey("/")}>/</button>
|
|
2499
|
+
<button class="touch-key" @click=${() => this.sendKey("-")}>-</button>
|
|
2500
|
+
<button class="touch-key" @click=${() => this.sendEscape("[H")}>HOME</button>
|
|
2501
|
+
<button class="touch-key" @click=${() => this.sendEscape("[A")}>↑</button>
|
|
2502
|
+
<button class="touch-key" @click=${() => this.sendEscape("[F")}>END</button>
|
|
2503
|
+
<button class="touch-key" @click=${() => this.sendEscape("[5~")}>PGUP</button>
|
|
2504
|
+
</div>
|
|
2505
|
+
|
|
2506
|
+
<!-- Row 2: TAB, modifiers, arrows -->
|
|
2507
|
+
<div class="touch-keyboard-row">
|
|
2508
|
+
<button class="touch-key" @click=${() => this.sendKey(" ")}>TAB</button>
|
|
2509
|
+
<button class="touch-key toggle-btn ${this.ctrlPressed ? "active" : ""}" @click=${this.toggleCtrl}>CTRL</button>
|
|
2510
|
+
<button class="touch-key toggle-btn ${this.altPressed ? "active" : ""}" @click=${this.toggleAlt}>ALT</button>
|
|
2511
|
+
<button class="touch-key" @click=${() => this.sendEscape("[D")}>←</button>
|
|
2512
|
+
<button class="touch-key" @click=${() => this.sendEscape("[B")}>↓</button>
|
|
2513
|
+
<button class="touch-key" @click=${() => this.sendEscape("[C")}>→</button>
|
|
2514
|
+
<button class="touch-key" @click=${() => this.sendEscape("[6~")}>PGDN</button>
|
|
2515
|
+
</div>
|
|
2516
|
+
|
|
2517
|
+
<!-- Expandable extra rows -->
|
|
2518
|
+
<div class="touch-keyboard-extra ${this.showExtraKeyRows ? "expanded" : ""}">
|
|
2519
|
+
<!-- Row 3: Common control sequences -->
|
|
2520
|
+
<div class="touch-keyboard-row">
|
|
2521
|
+
<button class="touch-key danger" @click=${() => this.sendCtrl("C")}>^C</button>
|
|
2522
|
+
<button class="touch-key" @click=${() => this.sendCtrl("D")}>^D</button>
|
|
2523
|
+
<button class="touch-key" @click=${() => this.sendCtrl("Z")}>^Z</button>
|
|
2524
|
+
<button class="touch-key" @click=${() => this.sendCtrl("L")}>^L</button>
|
|
2525
|
+
<button class="touch-key" @click=${() => this.sendCtrl("A")}>^A</button>
|
|
2526
|
+
<button class="touch-key" @click=${() => this.sendCtrl("E")}>^E</button>
|
|
2527
|
+
<button class="touch-key" @click=${() => this.sendCtrl("R")}>^R</button>
|
|
2528
|
+
</div>
|
|
2529
|
+
</div>
|
|
2530
|
+
|
|
2531
|
+
<!-- Toggle for extra rows -->
|
|
2532
|
+
<div class="touch-keyboard-row">
|
|
2533
|
+
<button
|
|
2534
|
+
class="touch-key wide"
|
|
2535
|
+
@click=${this.toggleExtraRows}
|
|
2536
|
+
title="${this.showExtraKeyRows ? "Hide" : "Show"} extra keys"
|
|
2537
|
+
>
|
|
2538
|
+
${this.showExtraKeyRows ? "\u25B2 Less" : "\u25BC More"}
|
|
2539
|
+
</button>
|
|
2540
|
+
</div>
|
|
2541
|
+
</div>
|
|
2542
|
+
` : A}
|
|
2543
|
+
`;
|
|
2544
|
+
}
|
|
2545
|
+
// ==================== End Touch Keyboard Methods ====================
|
|
2546
|
+
/**
|
|
2547
|
+
* Render status bar
|
|
2548
|
+
*/
|
|
2549
|
+
renderStatusBar() {
|
|
2550
|
+
if (!this.showStatusBar)
|
|
2551
|
+
return A;
|
|
2552
|
+
return b2`
|
|
2553
|
+
<div class="status-bar">
|
|
2554
|
+
<div class="status-bar-left">
|
|
2555
|
+
<span class="status-dot ${this.connected ? "connected" : ""}"></span>
|
|
2556
|
+
<span>${this.connected ? this.sessionActive ? "Session active" : "Connected" : "Disconnected"}</span>
|
|
2557
|
+
${this.sessionInfo ? b2`
|
|
2558
|
+
<span style="color: var(--ls-text-muted)">|</span>
|
|
2559
|
+
<span>${this.sessionInfo.container || this.sessionInfo.shell}</span>
|
|
2560
|
+
<span style="color: var(--ls-text-muted)">${this.sessionInfo.cols}x${this.sessionInfo.rows}</span>
|
|
2561
|
+
` : A}
|
|
2562
|
+
</div>
|
|
2563
|
+
<div class="status-bar-right">
|
|
2564
|
+
${this.statusMessage ? b2`
|
|
2565
|
+
<span class="${this.statusType === "error" ? "status-bar-error" : this.statusType === "success" ? "status-bar-success" : ""}">
|
|
2566
|
+
${this.statusType === "error" ? "\u26A0\uFE0F" : this.statusType === "success" ? "\u2713" : ""}
|
|
2567
|
+
${this.statusMessage}
|
|
2568
|
+
</span>
|
|
2569
|
+
<button
|
|
2570
|
+
style="background: none; border: none; cursor: pointer; padding: 0; font-size: 10px;"
|
|
2571
|
+
@click=${this.clearStatus}
|
|
2572
|
+
title="Dismiss"
|
|
2573
|
+
>✕</button>
|
|
2574
|
+
` : A}
|
|
2575
|
+
</div>
|
|
2576
|
+
</div>
|
|
2577
|
+
`;
|
|
2578
|
+
}
|
|
2579
|
+
render() {
|
|
2580
|
+
return b2`
|
|
2581
|
+
${this.noHeader ? A : b2`
|
|
2582
|
+
<div class="header">
|
|
2583
|
+
<div class="header-title">
|
|
2584
|
+
<span>Terminal</span>
|
|
2585
|
+
${this.sessionInfo ? b2`<span style="font-weight: normal; font-size: 12px; color: var(--ls-text-muted)">
|
|
2586
|
+
${this.sessionInfo.container ? `${this.sessionInfo.container} (${this.sessionInfo.shell})` : this.sessionInfo.shell}
|
|
2587
|
+
</span>` : A}
|
|
2588
|
+
</div>
|
|
2589
|
+
<div class="header-actions">
|
|
2590
|
+
${!this.showConnectionPanel ? b2`
|
|
2591
|
+
${!this.connected ? b2`<button @click=${this.connect} ?disabled=${this.loading}>
|
|
2592
|
+
${this.loading ? "Connecting..." : "Connect"}
|
|
2593
|
+
</button>` : !this.sessionActive ? b2`<button @click=${() => this.spawn()} ?disabled=${this.loading}>
|
|
2594
|
+
${this.loading ? "Spawning..." : "Start"}
|
|
2595
|
+
</button>` : b2`<button @click=${this.kill}>Stop</button>`}
|
|
2596
|
+
` : A}
|
|
2597
|
+
<button @click=${this.clear} ?disabled=${!this.sessionActive}>Clear</button>
|
|
2598
|
+
${this.renderSettingsDropdown()}
|
|
2599
|
+
${!this.showStatusBar ? b2`
|
|
2600
|
+
<div class="status">
|
|
2601
|
+
<span class="status-dot ${this.connected ? "connected" : ""}"></span>
|
|
2602
|
+
<span>${this.connected ? "Connected" : "Disconnected"}</span>
|
|
2603
|
+
</div>
|
|
2604
|
+
` : A}
|
|
2605
|
+
</div>
|
|
2606
|
+
</div>
|
|
2607
|
+
`}
|
|
2608
|
+
|
|
2609
|
+
${this.renderConnectionPanel()}
|
|
2610
|
+
${this.renderTabBar()}
|
|
2611
|
+
|
|
2612
|
+
${this.showTabs && this.tabs.length > 0 ? b2`
|
|
2613
|
+
<div class="terminals-wrapper">
|
|
2614
|
+
${this.tabs.map(
|
|
2615
|
+
(tab) => b2`
|
|
2616
|
+
<div
|
|
2617
|
+
class="tab-terminal-container ${tab.id === this.activeTabId ? "active" : ""}"
|
|
2618
|
+
data-tab-id=${tab.id}
|
|
2619
|
+
>
|
|
2620
|
+
${this.loading && tab.id === this.activeTabId && !tab.terminal ? b2`<div class="loading"><span class="loading-spinner">⏳</span> Loading...</div>` : this.error && tab.id === this.activeTabId && !tab.terminal ? b2`<div class="error">❌ ${this.error}</div>` : A}
|
|
2621
|
+
</div>
|
|
2622
|
+
`
|
|
2623
|
+
)}
|
|
2624
|
+
</div>
|
|
2625
|
+
` : b2`
|
|
2626
|
+
<div class="terminal-container">
|
|
2627
|
+
${this.loading && !this.terminal ? b2`<div class="loading"><span class="loading-spinner">⏳</span> Loading...</div>` : this.error && !this.terminal ? b2`<div class="error">❌ ${this.error}</div>` : A}
|
|
2628
|
+
</div>
|
|
2629
|
+
`}
|
|
2630
|
+
|
|
2631
|
+
${this.renderStatusBar()}
|
|
2632
|
+
${this.renderTouchKeyboard()}
|
|
2633
|
+
${this.renderReconnectDialog()}
|
|
2634
|
+
`;
|
|
2635
|
+
}
|
|
2636
|
+
};
|
|
2637
|
+
/** lit-shell.js version */
|
|
2638
|
+
LitShellTerminal.VERSION = VERSION;
|
|
2639
|
+
LitShellTerminal.styles = [
|
|
2640
|
+
sharedStyles,
|
|
2641
|
+
themeStyles,
|
|
2642
|
+
buttonStyles,
|
|
2643
|
+
i`
|
|
2644
|
+
:host {
|
|
2645
|
+
display: flex;
|
|
2646
|
+
flex-direction: column;
|
|
2647
|
+
height: 100%;
|
|
2648
|
+
min-height: 200px;
|
|
2649
|
+
border: 1px solid var(--ls-border);
|
|
2650
|
+
border-radius: 4px;
|
|
2651
|
+
overflow: hidden;
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
.header {
|
|
2655
|
+
display: flex;
|
|
2656
|
+
align-items: center;
|
|
2657
|
+
justify-content: space-between;
|
|
2658
|
+
padding: 8px 12px;
|
|
2659
|
+
background: var(--ls-bg-header);
|
|
2660
|
+
border-bottom: 1px solid var(--ls-border);
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
.header-title {
|
|
2664
|
+
display: flex;
|
|
2665
|
+
align-items: center;
|
|
2666
|
+
gap: 8px;
|
|
2667
|
+
font-weight: 600;
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
.header-actions {
|
|
2671
|
+
display: flex;
|
|
2672
|
+
gap: 8px;
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
.status {
|
|
2676
|
+
display: flex;
|
|
2677
|
+
align-items: center;
|
|
2678
|
+
gap: 6px;
|
|
2679
|
+
font-size: 12px;
|
|
2680
|
+
color: var(--ls-text-muted);
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2683
|
+
.status-dot {
|
|
2684
|
+
width: 8px;
|
|
1682
2685
|
height: 8px;
|
|
1683
2686
|
border-radius: 50%;
|
|
1684
2687
|
background: var(--ls-status-disconnected);
|
|
@@ -1736,6 +2739,441 @@ LitShellTerminal.styles = [
|
|
|
1736
2739
|
:host([no-header]) .header {
|
|
1737
2740
|
display: none;
|
|
1738
2741
|
}
|
|
2742
|
+
|
|
2743
|
+
/* Connection panel */
|
|
2744
|
+
.connection-panel {
|
|
2745
|
+
padding: 12px;
|
|
2746
|
+
background: var(--ls-bg-header);
|
|
2747
|
+
border-bottom: 1px solid var(--ls-border);
|
|
2748
|
+
}
|
|
2749
|
+
|
|
2750
|
+
.connection-panel-title {
|
|
2751
|
+
font-weight: 600;
|
|
2752
|
+
margin-bottom: 12px;
|
|
2753
|
+
display: flex;
|
|
2754
|
+
align-items: center;
|
|
2755
|
+
gap: 8px;
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2758
|
+
.connection-form {
|
|
2759
|
+
display: grid;
|
|
2760
|
+
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
2761
|
+
gap: 10px;
|
|
2762
|
+
align-items: end;
|
|
2763
|
+
}
|
|
2764
|
+
|
|
2765
|
+
.form-group {
|
|
2766
|
+
display: flex;
|
|
2767
|
+
flex-direction: column;
|
|
2768
|
+
gap: 4px;
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2771
|
+
.form-group label {
|
|
2772
|
+
font-size: 11px;
|
|
2773
|
+
text-transform: uppercase;
|
|
2774
|
+
color: var(--ls-text-muted);
|
|
2775
|
+
letter-spacing: 0.5px;
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2778
|
+
.form-group select,
|
|
2779
|
+
.form-group input {
|
|
2780
|
+
padding: 6px 10px;
|
|
2781
|
+
border: 1px solid var(--ls-border);
|
|
2782
|
+
border-radius: 4px;
|
|
2783
|
+
background: var(--ls-bg);
|
|
2784
|
+
color: var(--ls-text);
|
|
2785
|
+
font-size: 13px;
|
|
2786
|
+
}
|
|
2787
|
+
|
|
2788
|
+
.form-group select:focus,
|
|
2789
|
+
.form-group input:focus {
|
|
2790
|
+
outline: none;
|
|
2791
|
+
border-color: var(--ls-status-connected);
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
/* Settings dropdown */
|
|
2795
|
+
.settings-dropdown {
|
|
2796
|
+
position: relative;
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
.settings-menu {
|
|
2800
|
+
position: absolute;
|
|
2801
|
+
top: 100%;
|
|
2802
|
+
right: 0;
|
|
2803
|
+
margin-top: 4px;
|
|
2804
|
+
min-width: 180px;
|
|
2805
|
+
background: var(--ls-bg-header);
|
|
2806
|
+
border: 1px solid var(--ls-border);
|
|
2807
|
+
border-radius: 4px;
|
|
2808
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
|
2809
|
+
z-index: 100;
|
|
2810
|
+
padding: 8px 0;
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2813
|
+
.settings-menu-item {
|
|
2814
|
+
display: flex;
|
|
2815
|
+
align-items: center;
|
|
2816
|
+
justify-content: space-between;
|
|
2817
|
+
padding: 8px 12px;
|
|
2818
|
+
font-size: 13px;
|
|
2819
|
+
cursor: pointer;
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2822
|
+
.settings-menu-item:hover {
|
|
2823
|
+
background: var(--ls-btn-hover);
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
.settings-menu-item select {
|
|
2827
|
+
padding: 4px 8px;
|
|
2828
|
+
border: 1px solid var(--ls-border);
|
|
2829
|
+
border-radius: 3px;
|
|
2830
|
+
background: var(--ls-bg);
|
|
2831
|
+
color: var(--ls-text);
|
|
2832
|
+
font-size: 12px;
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2835
|
+
.settings-divider {
|
|
2836
|
+
height: 1px;
|
|
2837
|
+
background: var(--ls-border);
|
|
2838
|
+
margin: 4px 0;
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
/* Status bar */
|
|
2842
|
+
.status-bar {
|
|
2843
|
+
display: flex;
|
|
2844
|
+
align-items: center;
|
|
2845
|
+
justify-content: space-between;
|
|
2846
|
+
padding: 4px 12px;
|
|
2847
|
+
background: var(--ls-bg-header);
|
|
2848
|
+
border-top: 1px solid var(--ls-border);
|
|
2849
|
+
font-size: 12px;
|
|
2850
|
+
color: var(--ls-text-muted);
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
.status-bar-left {
|
|
2854
|
+
display: flex;
|
|
2855
|
+
align-items: center;
|
|
2856
|
+
gap: 12px;
|
|
2857
|
+
}
|
|
2858
|
+
|
|
2859
|
+
.status-bar-right {
|
|
2860
|
+
display: flex;
|
|
2861
|
+
align-items: center;
|
|
2862
|
+
gap: 8px;
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2865
|
+
.status-bar-error {
|
|
2866
|
+
color: #ef4444;
|
|
2867
|
+
display: flex;
|
|
2868
|
+
align-items: center;
|
|
2869
|
+
gap: 4px;
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
.status-bar-success {
|
|
2873
|
+
color: var(--ls-status-connected);
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2876
|
+
/* Tab bar styles */
|
|
2877
|
+
.tab-bar {
|
|
2878
|
+
display: flex;
|
|
2879
|
+
align-items: center;
|
|
2880
|
+
background: var(--ls-bg-header);
|
|
2881
|
+
border-bottom: 1px solid var(--ls-border);
|
|
2882
|
+
padding: 0 4px;
|
|
2883
|
+
gap: 2px;
|
|
2884
|
+
min-height: 32px;
|
|
2885
|
+
overflow-x: auto;
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
.tab-bar::-webkit-scrollbar {
|
|
2889
|
+
height: 4px;
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2892
|
+
.tab-bar::-webkit-scrollbar-thumb {
|
|
2893
|
+
background: var(--ls-border);
|
|
2894
|
+
border-radius: 2px;
|
|
2895
|
+
}
|
|
2896
|
+
|
|
2897
|
+
.tab {
|
|
2898
|
+
display: flex;
|
|
2899
|
+
align-items: center;
|
|
2900
|
+
gap: 6px;
|
|
2901
|
+
padding: 6px 12px;
|
|
2902
|
+
background: transparent;
|
|
2903
|
+
border: none;
|
|
2904
|
+
border-bottom: 2px solid transparent;
|
|
2905
|
+
color: var(--ls-text-muted);
|
|
2906
|
+
font-size: 12px;
|
|
2907
|
+
cursor: pointer;
|
|
2908
|
+
white-space: nowrap;
|
|
2909
|
+
transition: all 0.15s ease;
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2912
|
+
.tab:hover {
|
|
2913
|
+
background: var(--ls-btn-hover);
|
|
2914
|
+
color: var(--ls-text);
|
|
2915
|
+
}
|
|
2916
|
+
|
|
2917
|
+
.tab.active {
|
|
2918
|
+
color: var(--ls-text);
|
|
2919
|
+
border-bottom-color: var(--ls-status-connected);
|
|
2920
|
+
background: var(--ls-bg);
|
|
2921
|
+
}
|
|
2922
|
+
|
|
2923
|
+
.tab-status {
|
|
2924
|
+
width: 6px;
|
|
2925
|
+
height: 6px;
|
|
2926
|
+
border-radius: 50%;
|
|
2927
|
+
background: var(--ls-status-disconnected);
|
|
2928
|
+
}
|
|
2929
|
+
|
|
2930
|
+
.tab-status.connected {
|
|
2931
|
+
background: var(--ls-status-connected);
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2934
|
+
.tab-close {
|
|
2935
|
+
display: flex;
|
|
2936
|
+
align-items: center;
|
|
2937
|
+
justify-content: center;
|
|
2938
|
+
width: 16px;
|
|
2939
|
+
height: 16px;
|
|
2940
|
+
border-radius: 3px;
|
|
2941
|
+
background: none;
|
|
2942
|
+
border: none;
|
|
2943
|
+
color: var(--ls-text-muted);
|
|
2944
|
+
font-size: 14px;
|
|
2945
|
+
cursor: pointer;
|
|
2946
|
+
opacity: 0;
|
|
2947
|
+
transition: opacity 0.15s ease;
|
|
2948
|
+
}
|
|
2949
|
+
|
|
2950
|
+
.tab:hover .tab-close {
|
|
2951
|
+
opacity: 1;
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2954
|
+
.tab-close:hover {
|
|
2955
|
+
background: var(--ls-btn-hover);
|
|
2956
|
+
color: var(--ls-text);
|
|
2957
|
+
}
|
|
2958
|
+
|
|
2959
|
+
.tab-add {
|
|
2960
|
+
display: flex;
|
|
2961
|
+
align-items: center;
|
|
2962
|
+
justify-content: center;
|
|
2963
|
+
width: 28px;
|
|
2964
|
+
height: 28px;
|
|
2965
|
+
border-radius: 4px;
|
|
2966
|
+
background: none;
|
|
2967
|
+
border: none;
|
|
2968
|
+
color: var(--ls-text-muted);
|
|
2969
|
+
font-size: 18px;
|
|
2970
|
+
cursor: pointer;
|
|
2971
|
+
margin-left: 4px;
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
.tab-add:hover {
|
|
2975
|
+
background: var(--ls-btn-hover);
|
|
2976
|
+
color: var(--ls-text);
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2979
|
+
/* Multi-terminal container */
|
|
2980
|
+
.terminals-wrapper {
|
|
2981
|
+
flex: 1;
|
|
2982
|
+
position: relative;
|
|
2983
|
+
overflow: hidden;
|
|
2984
|
+
}
|
|
2985
|
+
|
|
2986
|
+
.tab-terminal-container {
|
|
2987
|
+
position: absolute;
|
|
2988
|
+
top: 0;
|
|
2989
|
+
left: 0;
|
|
2990
|
+
right: 0;
|
|
2991
|
+
bottom: 0;
|
|
2992
|
+
padding: 4px;
|
|
2993
|
+
background: var(--ls-terminal-bg);
|
|
2994
|
+
display: none;
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2997
|
+
.tab-terminal-container.active {
|
|
2998
|
+
display: block;
|
|
2999
|
+
}
|
|
3000
|
+
|
|
3001
|
+
.tab-terminal-container .xterm {
|
|
3002
|
+
height: 100%;
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3005
|
+
/* Reconnect dialog */
|
|
3006
|
+
.reconnect-dialog-overlay {
|
|
3007
|
+
position: absolute;
|
|
3008
|
+
top: 0;
|
|
3009
|
+
left: 0;
|
|
3010
|
+
right: 0;
|
|
3011
|
+
bottom: 0;
|
|
3012
|
+
background: rgba(0, 0, 0, 0.7);
|
|
3013
|
+
display: flex;
|
|
3014
|
+
align-items: center;
|
|
3015
|
+
justify-content: center;
|
|
3016
|
+
z-index: 1000;
|
|
3017
|
+
}
|
|
3018
|
+
|
|
3019
|
+
.reconnect-dialog {
|
|
3020
|
+
background: var(--ls-bg-header);
|
|
3021
|
+
border: 1px solid var(--ls-border);
|
|
3022
|
+
border-radius: 8px;
|
|
3023
|
+
padding: 24px;
|
|
3024
|
+
max-width: 320px;
|
|
3025
|
+
text-align: center;
|
|
3026
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
|
3027
|
+
}
|
|
3028
|
+
|
|
3029
|
+
.reconnect-dialog h3 {
|
|
3030
|
+
margin: 0 0 12px 0;
|
|
3031
|
+
font-size: 16px;
|
|
3032
|
+
color: var(--ls-text);
|
|
3033
|
+
}
|
|
3034
|
+
|
|
3035
|
+
.reconnect-dialog p {
|
|
3036
|
+
margin: 0 0 20px 0;
|
|
3037
|
+
font-size: 14px;
|
|
3038
|
+
color: var(--ls-text-muted);
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
.reconnect-dialog-buttons {
|
|
3042
|
+
display: flex;
|
|
3043
|
+
gap: 12px;
|
|
3044
|
+
justify-content: center;
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
.reconnect-dialog-buttons button {
|
|
3048
|
+
padding: 8px 20px;
|
|
3049
|
+
border-radius: 4px;
|
|
3050
|
+
font-size: 14px;
|
|
3051
|
+
cursor: pointer;
|
|
3052
|
+
transition: background 0.15s ease;
|
|
3053
|
+
}
|
|
3054
|
+
|
|
3055
|
+
.reconnect-dialog-buttons .btn-primary {
|
|
3056
|
+
background: var(--ls-status-connected);
|
|
3057
|
+
color: white;
|
|
3058
|
+
border: none;
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
.reconnect-dialog-buttons .btn-primary:hover {
|
|
3062
|
+
background: #1da34d;
|
|
3063
|
+
}
|
|
3064
|
+
|
|
3065
|
+
.reconnect-dialog-buttons .btn-secondary {
|
|
3066
|
+
background: transparent;
|
|
3067
|
+
color: var(--ls-text-muted);
|
|
3068
|
+
border: 1px solid var(--ls-border);
|
|
3069
|
+
}
|
|
3070
|
+
|
|
3071
|
+
.reconnect-dialog-buttons .btn-secondary:hover {
|
|
3072
|
+
background: var(--ls-btn-hover);
|
|
3073
|
+
color: var(--ls-text);
|
|
3074
|
+
}
|
|
3075
|
+
|
|
3076
|
+
/* Touch keyboard for mobile */
|
|
3077
|
+
.touch-keyboard {
|
|
3078
|
+
display: flex;
|
|
3079
|
+
flex-direction: column;
|
|
3080
|
+
background: var(--ls-bg-header);
|
|
3081
|
+
border-top: 1px solid var(--ls-border);
|
|
3082
|
+
padding: 4px;
|
|
3083
|
+
gap: 4px;
|
|
3084
|
+
}
|
|
3085
|
+
|
|
3086
|
+
.touch-keyboard-row {
|
|
3087
|
+
display: flex;
|
|
3088
|
+
gap: 4px;
|
|
3089
|
+
justify-content: center;
|
|
3090
|
+
}
|
|
3091
|
+
|
|
3092
|
+
.touch-key {
|
|
3093
|
+
display: flex;
|
|
3094
|
+
align-items: center;
|
|
3095
|
+
justify-content: center;
|
|
3096
|
+
min-width: 40px;
|
|
3097
|
+
height: 36px;
|
|
3098
|
+
padding: 0 8px;
|
|
3099
|
+
background: var(--ls-bg);
|
|
3100
|
+
border: 1px solid var(--ls-border);
|
|
3101
|
+
border-radius: 4px;
|
|
3102
|
+
color: var(--ls-text);
|
|
3103
|
+
font-size: 12px;
|
|
3104
|
+
font-family: inherit;
|
|
3105
|
+
cursor: pointer;
|
|
3106
|
+
touch-action: manipulation;
|
|
3107
|
+
user-select: none;
|
|
3108
|
+
-webkit-user-select: none;
|
|
3109
|
+
transition: background 0.1s ease;
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
.touch-key:active {
|
|
3113
|
+
background: var(--ls-btn-hover);
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
.touch-key.wide {
|
|
3117
|
+
min-width: 50px;
|
|
3118
|
+
}
|
|
3119
|
+
|
|
3120
|
+
.touch-key.danger {
|
|
3121
|
+
color: #ef4444;
|
|
3122
|
+
border-color: #ef4444;
|
|
3123
|
+
}
|
|
3124
|
+
|
|
3125
|
+
.touch-key.toggle-btn {
|
|
3126
|
+
background: var(--ls-bg-header);
|
|
3127
|
+
min-width: 30px;
|
|
3128
|
+
font-size: 14px;
|
|
3129
|
+
}
|
|
3130
|
+
|
|
3131
|
+
.touch-key.toggle-btn.active {
|
|
3132
|
+
background: var(--ls-status-connected);
|
|
3133
|
+
color: white;
|
|
3134
|
+
border-color: var(--ls-status-connected);
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3137
|
+
.touch-keyboard-toggle {
|
|
3138
|
+
display: flex;
|
|
3139
|
+
align-items: center;
|
|
3140
|
+
justify-content: center;
|
|
3141
|
+
padding: 2px;
|
|
3142
|
+
background: var(--ls-bg-header);
|
|
3143
|
+
border-top: 1px solid var(--ls-border);
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3146
|
+
.touch-keyboard-toggle button {
|
|
3147
|
+
background: none;
|
|
3148
|
+
border: none;
|
|
3149
|
+
color: var(--ls-text-muted);
|
|
3150
|
+
font-size: 18px;
|
|
3151
|
+
padding: 4px 12px;
|
|
3152
|
+
cursor: pointer;
|
|
3153
|
+
}
|
|
3154
|
+
|
|
3155
|
+
.touch-keyboard-toggle button:active {
|
|
3156
|
+
color: var(--ls-text);
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
/* Extra key rows (collapsible) */
|
|
3160
|
+
.touch-keyboard-extra {
|
|
3161
|
+
overflow: hidden;
|
|
3162
|
+
max-height: 0;
|
|
3163
|
+
opacity: 0;
|
|
3164
|
+
transition: max-height 0.2s ease, opacity 0.2s ease;
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
.touch-keyboard-extra.expanded {
|
|
3168
|
+
max-height: 100px;
|
|
3169
|
+
opacity: 1;
|
|
3170
|
+
}
|
|
3171
|
+
|
|
3172
|
+
/* Hide touch keyboard on desktop */
|
|
3173
|
+
:host(:not([mobile])) .touch-keyboard,
|
|
3174
|
+
:host(:not([mobile])) .touch-keyboard-toggle {
|
|
3175
|
+
display: none;
|
|
3176
|
+
}
|
|
1739
3177
|
`
|
|
1740
3178
|
];
|
|
1741
3179
|
__decorateClass([
|
|
@@ -1765,6 +3203,30 @@ __decorateClass([
|
|
|
1765
3203
|
__decorateClass([
|
|
1766
3204
|
n4({ type: Boolean, attribute: "auto-spawn" })
|
|
1767
3205
|
], LitShellTerminal.prototype, "autoSpawn", 2);
|
|
3206
|
+
__decorateClass([
|
|
3207
|
+
n4({ type: String })
|
|
3208
|
+
], LitShellTerminal.prototype, "container", 2);
|
|
3209
|
+
__decorateClass([
|
|
3210
|
+
n4({ type: String, attribute: "container-shell" })
|
|
3211
|
+
], LitShellTerminal.prototype, "containerShell", 2);
|
|
3212
|
+
__decorateClass([
|
|
3213
|
+
n4({ type: String, attribute: "container-user" })
|
|
3214
|
+
], LitShellTerminal.prototype, "containerUser", 2);
|
|
3215
|
+
__decorateClass([
|
|
3216
|
+
n4({ type: String, attribute: "container-cwd" })
|
|
3217
|
+
], LitShellTerminal.prototype, "containerCwd", 2);
|
|
3218
|
+
__decorateClass([
|
|
3219
|
+
n4({ type: Boolean, attribute: "show-connection-panel" })
|
|
3220
|
+
], LitShellTerminal.prototype, "showConnectionPanel", 2);
|
|
3221
|
+
__decorateClass([
|
|
3222
|
+
n4({ type: Boolean, attribute: "show-settings" })
|
|
3223
|
+
], LitShellTerminal.prototype, "showSettings", 2);
|
|
3224
|
+
__decorateClass([
|
|
3225
|
+
n4({ type: Boolean, attribute: "show-status-bar" })
|
|
3226
|
+
], LitShellTerminal.prototype, "showStatusBar", 2);
|
|
3227
|
+
__decorateClass([
|
|
3228
|
+
n4({ type: Boolean, attribute: "show-tabs" })
|
|
3229
|
+
], LitShellTerminal.prototype, "showTabs", 2);
|
|
1768
3230
|
__decorateClass([
|
|
1769
3231
|
n4({ type: Number, attribute: "font-size" })
|
|
1770
3232
|
], LitShellTerminal.prototype, "fontSize", 2);
|
|
@@ -1795,11 +3257,78 @@ __decorateClass([
|
|
|
1795
3257
|
__decorateClass([
|
|
1796
3258
|
r5()
|
|
1797
3259
|
], LitShellTerminal.prototype, "sessionInfo", 2);
|
|
3260
|
+
__decorateClass([
|
|
3261
|
+
r5()
|
|
3262
|
+
], LitShellTerminal.prototype, "containers", 2);
|
|
3263
|
+
__decorateClass([
|
|
3264
|
+
r5()
|
|
3265
|
+
], LitShellTerminal.prototype, "serverInfo", 2);
|
|
3266
|
+
__decorateClass([
|
|
3267
|
+
r5()
|
|
3268
|
+
], LitShellTerminal.prototype, "selectedContainer", 2);
|
|
3269
|
+
__decorateClass([
|
|
3270
|
+
r5()
|
|
3271
|
+
], LitShellTerminal.prototype, "selectedShell", 2);
|
|
3272
|
+
__decorateClass([
|
|
3273
|
+
r5()
|
|
3274
|
+
], LitShellTerminal.prototype, "connectionMode", 2);
|
|
3275
|
+
__decorateClass([
|
|
3276
|
+
r5()
|
|
3277
|
+
], LitShellTerminal.prototype, "orphanTimeout", 2);
|
|
3278
|
+
__decorateClass([
|
|
3279
|
+
r5()
|
|
3280
|
+
], LitShellTerminal.prototype, "useTmux", 2);
|
|
3281
|
+
__decorateClass([
|
|
3282
|
+
r5()
|
|
3283
|
+
], LitShellTerminal.prototype, "availableSessions", 2);
|
|
3284
|
+
__decorateClass([
|
|
3285
|
+
r5()
|
|
3286
|
+
], LitShellTerminal.prototype, "selectedSessionId", 2);
|
|
3287
|
+
__decorateClass([
|
|
3288
|
+
r5()
|
|
3289
|
+
], LitShellTerminal.prototype, "clientCount", 2);
|
|
3290
|
+
__decorateClass([
|
|
3291
|
+
r5()
|
|
3292
|
+
], LitShellTerminal.prototype, "settingsMenuOpen", 2);
|
|
3293
|
+
__decorateClass([
|
|
3294
|
+
r5()
|
|
3295
|
+
], LitShellTerminal.prototype, "showReconnectDialog", 2);
|
|
3296
|
+
__decorateClass([
|
|
3297
|
+
r5()
|
|
3298
|
+
], LitShellTerminal.prototype, "reconnectSessionId", 2);
|
|
3299
|
+
__decorateClass([
|
|
3300
|
+
r5()
|
|
3301
|
+
], LitShellTerminal.prototype, "isMobile", 2);
|
|
3302
|
+
__decorateClass([
|
|
3303
|
+
r5()
|
|
3304
|
+
], LitShellTerminal.prototype, "showTouchKeyboard", 2);
|
|
3305
|
+
__decorateClass([
|
|
3306
|
+
r5()
|
|
3307
|
+
], LitShellTerminal.prototype, "showExtraKeyRows", 2);
|
|
3308
|
+
__decorateClass([
|
|
3309
|
+
r5()
|
|
3310
|
+
], LitShellTerminal.prototype, "statusMessage", 2);
|
|
3311
|
+
__decorateClass([
|
|
3312
|
+
r5()
|
|
3313
|
+
], LitShellTerminal.prototype, "statusType", 2);
|
|
3314
|
+
__decorateClass([
|
|
3315
|
+
r5()
|
|
3316
|
+
], LitShellTerminal.prototype, "tabs", 2);
|
|
3317
|
+
__decorateClass([
|
|
3318
|
+
r5()
|
|
3319
|
+
], LitShellTerminal.prototype, "activeTabId", 2);
|
|
3320
|
+
__decorateClass([
|
|
3321
|
+
r5()
|
|
3322
|
+
], LitShellTerminal.prototype, "ctrlPressed", 2);
|
|
3323
|
+
__decorateClass([
|
|
3324
|
+
r5()
|
|
3325
|
+
], LitShellTerminal.prototype, "altPressed", 2);
|
|
1798
3326
|
LitShellTerminal = __decorateClass([
|
|
1799
3327
|
t3("lit-shell-terminal")
|
|
1800
3328
|
], LitShellTerminal);
|
|
1801
3329
|
export {
|
|
1802
3330
|
LitShellTerminal,
|
|
3331
|
+
VERSION,
|
|
1803
3332
|
buttonStyles,
|
|
1804
3333
|
sharedStyles,
|
|
1805
3334
|
themeStyles
|