treesap 0.1.6 → 0.1.8
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/components/Terminal.d.ts +2 -2
- package/dist/components/Terminal.d.ts.map +1 -1
- package/dist/components/Terminal.js +10 -4
- package/dist/components/Terminal.js.map +1 -1
- package/dist/pages/Code.d.ts.map +1 -1
- package/dist/pages/Code.js +1 -1
- package/dist/pages/Code.js.map +1 -1
- package/dist/pages/Welcome.d.ts.map +1 -1
- package/dist/pages/Welcome.js +1 -1
- package/dist/pages/Welcome.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +23 -1
- package/dist/server.js.map +1 -1
- package/dist/services/terminal.d.ts.map +1 -1
- package/dist/services/terminal.js +2 -0
- package/dist/services/terminal.js.map +1 -1
- package/dist/static/components/Terminal.js +89 -42
- package/dist/static/components/TerminalTabs.js +383 -0
- package/dist/static/signals/TerminalSignal.js +138 -0
- package/dist/static/styles/main.css +127 -65
- package/package.json +1 -1
- package/src/components/Terminal.tsx +13 -7
- package/src/pages/Code.tsx +83 -16
- package/src/pages/Welcome.tsx +44 -19
- package/src/server.tsx +26 -1
- package/src/services/terminal.ts +2 -0
- package/src/static/components/Terminal.js +89 -42
- package/src/static/components/TerminalTabs.js +383 -0
- package/src/static/signals/TerminalSignal.js +138 -0
- package/src/static/styles/main.css +127 -65
|
@@ -1113,12 +1113,20 @@ video {
|
|
|
1113
1113
|
z-index: 50;
|
|
1114
1114
|
}
|
|
1115
1115
|
|
|
1116
|
-
.mb-
|
|
1117
|
-
margin-bottom:
|
|
1116
|
+
.mb-12 {
|
|
1117
|
+
margin-bottom: 3rem;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
.mb-16 {
|
|
1121
|
+
margin-bottom: 4rem;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
.mb-3 {
|
|
1125
|
+
margin-bottom: 0.75rem;
|
|
1118
1126
|
}
|
|
1119
1127
|
|
|
1120
|
-
.mb-
|
|
1121
|
-
margin-bottom:
|
|
1128
|
+
.mb-4 {
|
|
1129
|
+
margin-bottom: 1rem;
|
|
1122
1130
|
}
|
|
1123
1131
|
|
|
1124
1132
|
.mb-8 {
|
|
@@ -1133,22 +1141,30 @@ video {
|
|
|
1133
1141
|
margin-left: 0.5rem;
|
|
1134
1142
|
}
|
|
1135
1143
|
|
|
1144
|
+
.mr-1 {
|
|
1145
|
+
margin-right: 0.25rem;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1136
1148
|
.mr-2 {
|
|
1137
1149
|
margin-right: 0.5rem;
|
|
1138
1150
|
}
|
|
1139
1151
|
|
|
1140
|
-
.
|
|
1141
|
-
|
|
1152
|
+
.mr-4 {
|
|
1153
|
+
margin-right: 1rem;
|
|
1142
1154
|
}
|
|
1143
1155
|
|
|
1144
|
-
.
|
|
1145
|
-
display:
|
|
1156
|
+
.block {
|
|
1157
|
+
display: block;
|
|
1146
1158
|
}
|
|
1147
1159
|
|
|
1148
1160
|
.flex {
|
|
1149
1161
|
display: flex;
|
|
1150
1162
|
}
|
|
1151
1163
|
|
|
1164
|
+
.inline-flex {
|
|
1165
|
+
display: inline-flex;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1152
1168
|
.hidden {
|
|
1153
1169
|
display: none;
|
|
1154
1170
|
}
|
|
@@ -1157,6 +1173,14 @@ video {
|
|
|
1157
1173
|
height: 2.5rem;
|
|
1158
1174
|
}
|
|
1159
1175
|
|
|
1176
|
+
.h-12 {
|
|
1177
|
+
height: 3rem;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
.h-8 {
|
|
1181
|
+
height: 2rem;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1160
1184
|
.h-full {
|
|
1161
1185
|
height: 100%;
|
|
1162
1186
|
}
|
|
@@ -1173,12 +1197,20 @@ video {
|
|
|
1173
1197
|
width: 2.5rem;
|
|
1174
1198
|
}
|
|
1175
1199
|
|
|
1200
|
+
.w-12 {
|
|
1201
|
+
width: 3rem;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
.w-8 {
|
|
1205
|
+
width: 2rem;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1176
1208
|
.w-full {
|
|
1177
1209
|
width: 100%;
|
|
1178
1210
|
}
|
|
1179
1211
|
|
|
1180
|
-
.
|
|
1181
|
-
|
|
1212
|
+
.min-w-\[140px\] {
|
|
1213
|
+
min-width: 140px;
|
|
1182
1214
|
}
|
|
1183
1215
|
|
|
1184
1216
|
.flex-1 {
|
|
@@ -1219,14 +1251,16 @@ video {
|
|
|
1219
1251
|
justify-content: center;
|
|
1220
1252
|
}
|
|
1221
1253
|
|
|
1254
|
+
.gap-1 {
|
|
1255
|
+
gap: 0.25rem;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1222
1258
|
.gap-2 {
|
|
1223
1259
|
gap: 0.5rem;
|
|
1224
1260
|
}
|
|
1225
1261
|
|
|
1226
|
-
.
|
|
1227
|
-
|
|
1228
|
-
margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
|
|
1229
|
-
margin-bottom: calc(1rem * var(--tw-space-y-reverse));
|
|
1262
|
+
.gap-8 {
|
|
1263
|
+
gap: 2rem;
|
|
1230
1264
|
}
|
|
1231
1265
|
|
|
1232
1266
|
.overflow-hidden {
|
|
@@ -1249,6 +1283,11 @@ video {
|
|
|
1249
1283
|
border-radius: 0.375rem;
|
|
1250
1284
|
}
|
|
1251
1285
|
|
|
1286
|
+
.rounded-t-sm {
|
|
1287
|
+
border-top-left-radius: 0.125rem;
|
|
1288
|
+
border-top-right-radius: 0.125rem;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1252
1291
|
.border {
|
|
1253
1292
|
border-width: 1px;
|
|
1254
1293
|
}
|
|
@@ -1265,6 +1304,15 @@ video {
|
|
|
1265
1304
|
border-right-width: 1px;
|
|
1266
1305
|
}
|
|
1267
1306
|
|
|
1307
|
+
.border-t-2 {
|
|
1308
|
+
border-top-width: 2px;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
.border-\[\#0e639c\] {
|
|
1312
|
+
--tw-border-opacity: 1;
|
|
1313
|
+
border-color: rgb(14 99 156 / var(--tw-border-opacity, 1));
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1268
1316
|
.border-\[\#3c3c3c\] {
|
|
1269
1317
|
--tw-border-opacity: 1;
|
|
1270
1318
|
border-color: rgb(60 60 60 / var(--tw-border-opacity, 1));
|
|
@@ -1280,11 +1328,6 @@ video {
|
|
|
1280
1328
|
border-color: rgb(156 163 175 / var(--tw-border-opacity, 1));
|
|
1281
1329
|
}
|
|
1282
1330
|
|
|
1283
|
-
.border-green-600 {
|
|
1284
|
-
--tw-border-opacity: 1;
|
|
1285
|
-
border-color: rgb(22 163 74 / var(--tw-border-opacity, 1));
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
1331
|
.bg-\[\#1e1e1e\] {
|
|
1289
1332
|
--tw-bg-opacity: 1;
|
|
1290
1333
|
background-color: rgb(30 30 30 / var(--tw-bg-opacity, 1));
|
|
@@ -1295,6 +1338,11 @@ video {
|
|
|
1295
1338
|
background-color: rgb(37 37 38 / var(--tw-bg-opacity, 1));
|
|
1296
1339
|
}
|
|
1297
1340
|
|
|
1341
|
+
.bg-\[\#2a2a2a\] {
|
|
1342
|
+
--tw-bg-opacity: 1;
|
|
1343
|
+
background-color: rgb(42 42 42 / var(--tw-bg-opacity, 1));
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1298
1346
|
.bg-\[\#2d2d30\] {
|
|
1299
1347
|
--tw-bg-opacity: 1;
|
|
1300
1348
|
background-color: rgb(45 45 48 / var(--tw-bg-opacity, 1));
|
|
@@ -1305,26 +1353,11 @@ video {
|
|
|
1305
1353
|
background-color: rgb(0 0 0 / var(--tw-bg-opacity, 1));
|
|
1306
1354
|
}
|
|
1307
1355
|
|
|
1308
|
-
.bg-gray-200 {
|
|
1309
|
-
--tw-bg-opacity: 1;
|
|
1310
|
-
background-color: rgb(229 231 235 / var(--tw-bg-opacity, 1));
|
|
1311
|
-
}
|
|
1312
|
-
|
|
1313
|
-
.bg-gray-50 {
|
|
1314
|
-
--tw-bg-opacity: 1;
|
|
1315
|
-
background-color: rgb(249 250 251 / var(--tw-bg-opacity, 1));
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
1356
|
.bg-gray-900 {
|
|
1319
1357
|
--tw-bg-opacity: 1;
|
|
1320
1358
|
background-color: rgb(17 24 39 / var(--tw-bg-opacity, 1));
|
|
1321
1359
|
}
|
|
1322
1360
|
|
|
1323
|
-
.bg-green-600 {
|
|
1324
|
-
--tw-bg-opacity: 1;
|
|
1325
|
-
background-color: rgb(22 163 74 / var(--tw-bg-opacity, 1));
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
1361
|
.bg-transparent {
|
|
1329
1362
|
background-color: transparent;
|
|
1330
1363
|
}
|
|
@@ -1334,6 +1367,24 @@ video {
|
|
|
1334
1367
|
background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
|
|
1335
1368
|
}
|
|
1336
1369
|
|
|
1370
|
+
.bg-gradient-to-br {
|
|
1371
|
+
background-image: linear-gradient(to bottom right, var(--tw-gradient-stops));
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
.from-green-400 {
|
|
1375
|
+
--tw-gradient-from: #4ade80 var(--tw-gradient-from-position);
|
|
1376
|
+
--tw-gradient-to: rgb(74 222 128 / 0) var(--tw-gradient-to-position);
|
|
1377
|
+
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
.to-green-600 {
|
|
1381
|
+
--tw-gradient-to: #16a34a var(--tw-gradient-to-position);
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
.p-0\.5 {
|
|
1385
|
+
padding: 0.125rem;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1337
1388
|
.p-1 {
|
|
1338
1389
|
padding: 0.25rem;
|
|
1339
1390
|
}
|
|
@@ -1350,6 +1401,10 @@ video {
|
|
|
1350
1401
|
padding: 1rem;
|
|
1351
1402
|
}
|
|
1352
1403
|
|
|
1404
|
+
.p-6 {
|
|
1405
|
+
padding: 1.5rem;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1353
1408
|
.p-8 {
|
|
1354
1409
|
padding: 2rem;
|
|
1355
1410
|
}
|
|
@@ -1364,11 +1419,6 @@ video {
|
|
|
1364
1419
|
padding-right: 0.75rem;
|
|
1365
1420
|
}
|
|
1366
1421
|
|
|
1367
|
-
.px-6 {
|
|
1368
|
-
padding-left: 1.5rem;
|
|
1369
|
-
padding-right: 1.5rem;
|
|
1370
|
-
}
|
|
1371
|
-
|
|
1372
1422
|
.py-1 {
|
|
1373
1423
|
padding-top: 0.25rem;
|
|
1374
1424
|
padding-bottom: 0.25rem;
|
|
@@ -1379,9 +1429,8 @@ video {
|
|
|
1379
1429
|
padding-bottom: 0.5rem;
|
|
1380
1430
|
}
|
|
1381
1431
|
|
|
1382
|
-
.
|
|
1383
|
-
|
|
1384
|
-
padding-bottom: 0.75rem;
|
|
1432
|
+
.text-left {
|
|
1433
|
+
text-align: left;
|
|
1385
1434
|
}
|
|
1386
1435
|
|
|
1387
1436
|
.text-center {
|
|
@@ -1392,26 +1441,26 @@ video {
|
|
|
1392
1441
|
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
1393
1442
|
}
|
|
1394
1443
|
|
|
1444
|
+
.text-2xl {
|
|
1445
|
+
font-size: 1.5rem;
|
|
1446
|
+
line-height: 2rem;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
.text-3xl {
|
|
1450
|
+
font-size: 1.875rem;
|
|
1451
|
+
line-height: 2.25rem;
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1395
1454
|
.text-4xl {
|
|
1396
1455
|
font-size: 2.25rem;
|
|
1397
1456
|
line-height: 2.5rem;
|
|
1398
1457
|
}
|
|
1399
1458
|
|
|
1400
|
-
.text-6xl {
|
|
1401
|
-
font-size: 3.75rem;
|
|
1402
|
-
line-height: 1;
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
1459
|
.text-sm {
|
|
1406
1460
|
font-size: 0.875rem;
|
|
1407
1461
|
line-height: 1.25rem;
|
|
1408
1462
|
}
|
|
1409
1463
|
|
|
1410
|
-
.text-xl {
|
|
1411
|
-
font-size: 1.25rem;
|
|
1412
|
-
line-height: 1.75rem;
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
1464
|
.font-bold {
|
|
1416
1465
|
font-weight: 700;
|
|
1417
1466
|
}
|
|
@@ -1438,14 +1487,19 @@ video {
|
|
|
1438
1487
|
color: rgb(0 0 0 / var(--tw-text-opacity, 1));
|
|
1439
1488
|
}
|
|
1440
1489
|
|
|
1441
|
-
.text-gray-
|
|
1490
|
+
.text-gray-300 {
|
|
1442
1491
|
--tw-text-opacity: 1;
|
|
1443
|
-
color: rgb(
|
|
1492
|
+
color: rgb(209 213 219 / var(--tw-text-opacity, 1));
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
.text-gray-400 {
|
|
1496
|
+
--tw-text-opacity: 1;
|
|
1497
|
+
color: rgb(156 163 175 / var(--tw-text-opacity, 1));
|
|
1444
1498
|
}
|
|
1445
1499
|
|
|
1446
|
-
.text-gray-
|
|
1500
|
+
.text-gray-500 {
|
|
1447
1501
|
--tw-text-opacity: 1;
|
|
1448
|
-
color: rgb(
|
|
1502
|
+
color: rgb(107 114 128 / var(--tw-text-opacity, 1));
|
|
1449
1503
|
}
|
|
1450
1504
|
|
|
1451
1505
|
.text-gray-800 {
|
|
@@ -1458,10 +1512,8 @@ video {
|
|
|
1458
1512
|
color: rgb(255 255 255 / var(--tw-text-opacity, 1));
|
|
1459
1513
|
}
|
|
1460
1514
|
|
|
1461
|
-
.
|
|
1462
|
-
|
|
1463
|
-
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
|
|
1464
|
-
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
|
1515
|
+
.opacity-50 {
|
|
1516
|
+
opacity: 0.5;
|
|
1465
1517
|
}
|
|
1466
1518
|
|
|
1467
1519
|
.shadow-xl {
|
|
@@ -1500,6 +1552,16 @@ video {
|
|
|
1500
1552
|
border-color: rgb(14 99 156 / var(--tw-border-opacity, 1));
|
|
1501
1553
|
}
|
|
1502
1554
|
|
|
1555
|
+
.hover\:bg-\[\#2d2d30\]:hover {
|
|
1556
|
+
--tw-bg-opacity: 1;
|
|
1557
|
+
background-color: rgb(45 45 48 / var(--tw-bg-opacity, 1));
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
.hover\:bg-\[\#3a3a3a\]:hover {
|
|
1561
|
+
--tw-bg-opacity: 1;
|
|
1562
|
+
background-color: rgb(58 58 58 / var(--tw-bg-opacity, 1));
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1503
1565
|
.hover\:bg-\[\#3c3c3c\]:hover {
|
|
1504
1566
|
--tw-bg-opacity: 1;
|
|
1505
1567
|
background-color: rgb(60 60 60 / var(--tw-bg-opacity, 1));
|
|
@@ -1515,11 +1577,6 @@ video {
|
|
|
1515
1577
|
background-color: rgb(31 41 55 / var(--tw-bg-opacity, 1));
|
|
1516
1578
|
}
|
|
1517
1579
|
|
|
1518
|
-
.hover\:bg-green-700:hover {
|
|
1519
|
-
--tw-bg-opacity: 1;
|
|
1520
|
-
background-color: rgb(21 128 61 / var(--tw-bg-opacity, 1));
|
|
1521
|
-
}
|
|
1522
|
-
|
|
1523
1580
|
.hover\:text-white:hover {
|
|
1524
1581
|
--tw-text-opacity: 1;
|
|
1525
1582
|
color: rgb(255 255 255 / var(--tw-text-opacity, 1));
|
|
@@ -1539,3 +1596,8 @@ video {
|
|
|
1539
1596
|
.disabled\:opacity-50:disabled {
|
|
1540
1597
|
opacity: 0.5;
|
|
1541
1598
|
}
|
|
1599
|
+
|
|
1600
|
+
.group:hover .group-hover\:text-white {
|
|
1601
|
+
--tw-text-opacity: 1;
|
|
1602
|
+
color: rgb(255 255 255 / var(--tw-text-opacity, 1));
|
|
1603
|
+
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
interface TerminalProps {
|
|
2
2
|
id?: string;
|
|
3
|
-
|
|
3
|
+
index?: number;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
export function Terminal({ id =
|
|
6
|
+
export function Terminal({ id, index = 1 }: TerminalProps) {
|
|
7
|
+
const terminalId = id || `terminal-${index}`;
|
|
8
|
+
const sessionId = `terminal-${index}`;
|
|
7
9
|
return (
|
|
8
10
|
<sapling-island loading="visible">
|
|
9
11
|
<template>
|
|
@@ -11,23 +13,27 @@ export function Terminal({ id = "terminal", sessionId }: TerminalProps) {
|
|
|
11
13
|
<script type="module" src="/components/Terminal.js"></script>
|
|
12
14
|
</template>
|
|
13
15
|
|
|
14
|
-
<div id={
|
|
16
|
+
<div id={terminalId} class="h-full bg-gray-900 flex font-sans overflow-hidden">
|
|
15
17
|
{/* Terminal Interface */}
|
|
16
18
|
<div class="w-full flex flex-col bg-gray-900 relative">
|
|
17
19
|
{/* Terminal header */}
|
|
18
|
-
<div id={`${
|
|
20
|
+
<div id={`${terminalId}-container`} class="h-full flex flex-col">
|
|
19
21
|
|
|
20
22
|
{/* Xterm.js terminal container */}
|
|
21
23
|
<div class="flex-1 overflow-hidden">
|
|
22
|
-
<div id={`${
|
|
24
|
+
<div id={`${terminalId}-xterm`} class="h-full w-full"></div>
|
|
23
25
|
</div>
|
|
24
26
|
</div>
|
|
25
27
|
</div>
|
|
26
28
|
</div>
|
|
27
29
|
|
|
28
30
|
<script dangerouslySetInnerHTML={{__html: `
|
|
29
|
-
// Pass
|
|
30
|
-
window.
|
|
31
|
+
// Pass terminal data to JavaScript
|
|
32
|
+
window.terminalData_${terminalId.replace(/-/g, '_')} = {
|
|
33
|
+
terminalId: '${terminalId}',
|
|
34
|
+
sessionId: '${sessionId}',
|
|
35
|
+
index: ${index}
|
|
36
|
+
};
|
|
31
37
|
`}}></script>
|
|
32
38
|
</sapling-island>
|
|
33
39
|
);
|
package/src/pages/Code.tsx
CHANGED
|
@@ -13,21 +13,16 @@ export function Code({ previewPort = 1234, workingDirectory }: TerminalProps) {
|
|
|
13
13
|
<div id="code-container" class="h-screen flex bg-[#1e1e1e]">
|
|
14
14
|
{/* Left Pane - Tabbed Sidebar */}
|
|
15
15
|
<div id="sidebar-pane" class="w-2/5-plus border-r border-[#3c3c3c] transition-all duration-300 flex flex-col bg-[#252526]">
|
|
16
|
-
{/* Back to Home */}
|
|
17
|
-
<div class="p-3 border-b border-[#3c3c3c]">
|
|
18
|
-
<a href="/" class="text-[#cccccc] hover:text-white text-sm">← Back to Home</a>
|
|
19
|
-
</div>
|
|
20
|
-
|
|
21
16
|
{/* Preview Controls */}
|
|
22
17
|
<div class="p-3 border-b border-[#3c3c3c] bg-[#2d2d30]">
|
|
23
18
|
<div class="flex items-center gap-2">
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
class="p-2 hover:bg-[#3c3c3c] rounded-md transition-colors flex
|
|
27
|
-
title="
|
|
19
|
+
<a
|
|
20
|
+
href="/"
|
|
21
|
+
class="p-2 hover:bg-[#3c3c3c] rounded-md transition-colors flex-shrink-0 text-[#cccccc] hover:text-white"
|
|
22
|
+
title="Back to Home"
|
|
28
23
|
>
|
|
29
|
-
<iconify-icon
|
|
30
|
-
</
|
|
24
|
+
<iconify-icon icon="tabler:arrow-left" width="16" height="16"></iconify-icon>
|
|
25
|
+
</a>
|
|
31
26
|
<button
|
|
32
27
|
type="button"
|
|
33
28
|
id="live-preview-refresh-btn"
|
|
@@ -77,11 +72,80 @@ export function Code({ previewPort = 1234, workingDirectory }: TerminalProps) {
|
|
|
77
72
|
{/* Tab Content */}
|
|
78
73
|
<div class="flex-1 overflow-hidden bg-[#1e1e1e]">
|
|
79
74
|
{/* Terminal Tab Content (Active) */}
|
|
80
|
-
<div class="h-full
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
75
|
+
<div class="h-full flex flex-col">
|
|
76
|
+
|
|
77
|
+
{/* Terminal Tabs Bar */}
|
|
78
|
+
<div class="border-b border-[#3c3c3c] bg-[#252526] px-3 py-1">
|
|
79
|
+
<div class="flex items-center gap-1">
|
|
80
|
+
{/* Terminal Tab 1 */}
|
|
81
|
+
<button
|
|
82
|
+
id="terminal-tab-1"
|
|
83
|
+
class="terminal-tab flex items-center px-3 py-1 text-sm text-white bg-[#1e1e1e] border-t-2 border-[#0e639c] rounded-t-sm hover:bg-[#2d2d30] transition-colors"
|
|
84
|
+
data-terminal-index="1"
|
|
85
|
+
>
|
|
86
|
+
<iconify-icon icon="tabler:terminal-2" width="14" height="14" class="mr-1"></iconify-icon>
|
|
87
|
+
Terminal 1
|
|
88
|
+
<button class="ml-2 hover:bg-[#3c3c3c] rounded p-0.5 text-[#cccccc] hover:text-white terminal-close-btn" data-terminal-index="1" style="display: none;">
|
|
89
|
+
<iconify-icon icon="tabler:x" width="12" height="12"></iconify-icon>
|
|
90
|
+
</button>
|
|
91
|
+
</button>
|
|
92
|
+
|
|
93
|
+
{/* Terminal Tab 2 */}
|
|
94
|
+
<button
|
|
95
|
+
id="terminal-tab-2"
|
|
96
|
+
class="terminal-tab flex items-center px-3 py-1 text-sm text-[#cccccc] hover:text-white hover:bg-[#2d2d30] transition-colors rounded-t-sm"
|
|
97
|
+
data-terminal-index="2"
|
|
98
|
+
style="display: none;"
|
|
99
|
+
>
|
|
100
|
+
<iconify-icon icon="tabler:terminal-2" width="14" height="14" class="mr-1"></iconify-icon>
|
|
101
|
+
Terminal 2
|
|
102
|
+
<button class="ml-2 hover:bg-[#3c3c3c] rounded p-0.5 text-[#cccccc] hover:text-white terminal-close-btn" data-terminal-index="2">
|
|
103
|
+
<iconify-icon icon="tabler:x" width="12" height="12"></iconify-icon>
|
|
104
|
+
</button>
|
|
105
|
+
</button>
|
|
106
|
+
|
|
107
|
+
{/* Terminal Tab 3 */}
|
|
108
|
+
<button
|
|
109
|
+
id="terminal-tab-3"
|
|
110
|
+
class="terminal-tab flex items-center px-3 py-1 text-sm text-[#cccccc] hover:text-white hover:bg-[#2d2d30] transition-colors rounded-t-sm"
|
|
111
|
+
data-terminal-index="3"
|
|
112
|
+
style="display: none;"
|
|
113
|
+
>
|
|
114
|
+
<iconify-icon icon="tabler:terminal-2" width="14" height="14" class="mr-1"></iconify-icon>
|
|
115
|
+
Terminal 3
|
|
116
|
+
<button class="ml-2 hover:bg-[#3c3c3c] rounded p-0.5 text-[#cccccc] hover:text-white terminal-close-btn" data-terminal-index="3">
|
|
117
|
+
<iconify-icon icon="tabler:x" width="12" height="12"></iconify-icon>
|
|
118
|
+
</button>
|
|
119
|
+
</button>
|
|
120
|
+
|
|
121
|
+
{/* Add Terminal Button */}
|
|
122
|
+
<button
|
|
123
|
+
id="add-terminal-btn"
|
|
124
|
+
class="flex items-center px-2 py-1 text-sm text-[#cccccc] hover:text-white hover:bg-[#2d2d30] transition-colors rounded"
|
|
125
|
+
title="New Terminal"
|
|
126
|
+
>
|
|
127
|
+
<iconify-icon icon="tabler:plus" width="14" height="14"></iconify-icon>
|
|
128
|
+
</button>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
{/* Terminal Content Area */}
|
|
133
|
+
<div class="flex-1 overflow-hidden relative">
|
|
134
|
+
{/* Terminal 1 Container */}
|
|
135
|
+
<div id="terminal-container-1" class="h-full p-4 terminal-container" data-terminal-index="1">
|
|
136
|
+
<TerminalComponent index={1} />
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
{/* Terminal 2 Container */}
|
|
140
|
+
<div id="terminal-container-2" class="h-full p-4 terminal-container" data-terminal-index="2" style="display: none;">
|
|
141
|
+
<TerminalComponent index={2} />
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
{/* Terminal 3 Container */}
|
|
145
|
+
<div id="terminal-container-3" class="h-full p-4 terminal-container" data-terminal-index="3" style="display: none;">
|
|
146
|
+
<TerminalComponent index={3} />
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
85
149
|
</div>
|
|
86
150
|
</div>
|
|
87
151
|
</div>
|
|
@@ -89,6 +153,9 @@ export function Code({ previewPort = 1234, workingDirectory }: TerminalProps) {
|
|
|
89
153
|
{/* Right Pane - Live Preview */}
|
|
90
154
|
<SimpleLivePreview id="live-preview" previewPort={previewPort} />
|
|
91
155
|
</div>
|
|
156
|
+
|
|
157
|
+
{/* Terminal Tabs Management Script */}
|
|
158
|
+
<script type="module" src="/components/TerminalTabs.js"></script>
|
|
92
159
|
</Layout>
|
|
93
160
|
);
|
|
94
161
|
}
|
package/src/pages/Welcome.tsx
CHANGED
|
@@ -3,28 +3,53 @@ import Layout from "../layouts/Layout.js";
|
|
|
3
3
|
export function Welcome() {
|
|
4
4
|
return (
|
|
5
5
|
<Layout title="Welcome to Treesap">
|
|
6
|
-
<div class="h-screen flex flex-col items-center justify-center bg-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
href="/code"
|
|
17
|
-
class="inline-block bg-green-600 hover:bg-green-700 text-white font-semibold py-3 px-6 rounded-lg transition-colors shadow-lg border-2 border-green-600"
|
|
18
|
-
style="background-color: #16a34a !important; color: white !important; text-decoration: none !important;"
|
|
19
|
-
>
|
|
20
|
-
Start Coding →
|
|
21
|
-
</a>
|
|
22
|
-
|
|
23
|
-
<div class="text-gray-500 text-sm">
|
|
24
|
-
or navigate to <code class="bg-gray-200 px-2 py-1 rounded">/code</code> to begin
|
|
6
|
+
<div class="h-screen flex flex-col items-center justify-center bg-[#1e1e1e] text-white p-8">
|
|
7
|
+
{/* Header with logo and title */}
|
|
8
|
+
<div class="text-center mb-12">
|
|
9
|
+
<div class="flex items-center justify-center mb-4">
|
|
10
|
+
<div class="w-12 h-12 bg-gradient-to-br from-green-400 to-green-600 rounded-lg flex items-center justify-center mr-4">
|
|
11
|
+
<span class="text-2xl">🌳</span>
|
|
12
|
+
</div>
|
|
13
|
+
<div class="text-left">
|
|
14
|
+
<h1 class="text-3xl font-semibold text-white">Treesap</h1>
|
|
15
|
+
<p class="text-gray-400 text-sm">Free • Development Environment</p>
|
|
25
16
|
</div>
|
|
26
17
|
</div>
|
|
27
18
|
</div>
|
|
19
|
+
|
|
20
|
+
{/* Main action buttons */}
|
|
21
|
+
<div class="flex gap-8 mb-16">
|
|
22
|
+
<a
|
|
23
|
+
href="/code"
|
|
24
|
+
class="flex flex-col items-center p-6 bg-[#2a2a2a] hover:bg-[#3a3a3a] rounded-lg transition-colors group min-w-[140px]"
|
|
25
|
+
>
|
|
26
|
+
<div class="w-8 h-8 mb-3 text-gray-300 group-hover:text-white transition-colors">
|
|
27
|
+
<iconify-icon icon="tabler:folder-open" width="32" height="32"></iconify-icon>
|
|
28
|
+
</div>
|
|
29
|
+
<span class="text-gray-300 group-hover:text-white transition-colors">Open project</span>
|
|
30
|
+
</a>
|
|
31
|
+
|
|
32
|
+
<button
|
|
33
|
+
class="flex flex-col items-center p-6 bg-[#2a2a2a] hover:bg-[#3a3a3a] rounded-lg transition-colors group min-w-[140px] opacity-50 cursor-not-allowed"
|
|
34
|
+
disabled
|
|
35
|
+
>
|
|
36
|
+
<div class="w-8 h-8 mb-3 text-gray-500">
|
|
37
|
+
<iconify-icon icon="tabler:git-branch" width="32" height="32"></iconify-icon>
|
|
38
|
+
</div>
|
|
39
|
+
<span class="text-gray-500">Clone repo</span>
|
|
40
|
+
</button>
|
|
41
|
+
|
|
42
|
+
<button
|
|
43
|
+
class="flex flex-col items-center p-6 bg-[#2a2a2a] hover:bg-[#3a3a3a] rounded-lg transition-colors group min-w-[140px] opacity-50 cursor-not-allowed"
|
|
44
|
+
disabled
|
|
45
|
+
>
|
|
46
|
+
<div class="w-8 h-8 mb-3 text-gray-500">
|
|
47
|
+
<iconify-icon icon="tabler:terminal" width="32" height="32"></iconify-icon>
|
|
48
|
+
</div>
|
|
49
|
+
<span class="text-gray-500">Connect via SSH</span>
|
|
50
|
+
</button>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
28
53
|
</div>
|
|
29
54
|
</Layout>
|
|
30
55
|
);
|
package/src/server.tsx
CHANGED
|
@@ -235,6 +235,8 @@ export async function startServer(config: TreesapConfig & { autoStartDev?: boole
|
|
|
235
235
|
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`));
|
|
236
236
|
} catch (error) {
|
|
237
237
|
console.error('Error sending terminal output:', error);
|
|
238
|
+
// Remove listener on error to prevent memory leaks
|
|
239
|
+
session!.eventEmitter.removeListener('output', handleOutput);
|
|
238
240
|
}
|
|
239
241
|
};
|
|
240
242
|
|
|
@@ -242,7 +244,11 @@ export async function startServer(config: TreesapConfig & { autoStartDev?: boole
|
|
|
242
244
|
|
|
243
245
|
// Handle client disconnect
|
|
244
246
|
const handleDisconnect = () => {
|
|
245
|
-
|
|
247
|
+
try {
|
|
248
|
+
session!.eventEmitter.removeListener('output', handleOutput);
|
|
249
|
+
} catch (error) {
|
|
250
|
+
console.error('Error removing output listener:', error);
|
|
251
|
+
}
|
|
246
252
|
};
|
|
247
253
|
|
|
248
254
|
// Clean up on stream close
|
|
@@ -262,6 +268,25 @@ export async function startServer(config: TreesapConfig & { autoStartDev?: boole
|
|
|
262
268
|
});
|
|
263
269
|
});
|
|
264
270
|
|
|
271
|
+
// Delete terminal session
|
|
272
|
+
app.delete("/terminal/session/:sessionId", async (c: Context) => {
|
|
273
|
+
const sessionId = c.req.param('sessionId');
|
|
274
|
+
|
|
275
|
+
if (!sessionId) {
|
|
276
|
+
return c.json({ error: "Session ID is required" }, 400);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const success = TerminalService.destroySession(sessionId);
|
|
280
|
+
|
|
281
|
+
if (success) {
|
|
282
|
+
|
|
283
|
+
return c.json({ message: `Terminal session ${sessionId} destroyed successfully` });
|
|
284
|
+
} else {
|
|
285
|
+
console.log(`Terminal session not found: ${sessionId}`);
|
|
286
|
+
return c.json({ error: `Terminal session ${sessionId} not found` }, 404);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
|
|
265
290
|
// Claude Code subprocess management
|
|
266
291
|
let claudeCodeManager: DevServerManager | null = null;
|
|
267
292
|
|
package/src/services/terminal.ts
CHANGED
|
@@ -19,6 +19,8 @@ export class TerminalService {
|
|
|
19
19
|
this.destroySession(sessionId);
|
|
20
20
|
|
|
21
21
|
const eventEmitter = new EventEmitter();
|
|
22
|
+
// Increase max listeners to handle multiple terminal tabs and connections
|
|
23
|
+
eventEmitter.setMaxListeners(20);
|
|
22
24
|
|
|
23
25
|
// Create a PTY process for proper terminal behavior
|
|
24
26
|
const ptyProcess = pty.spawn(process.platform === 'win32' ? 'cmd.exe' : process.env.SHELL || '/bin/bash', [], {
|