treesap 0.1.8 → 0.1.10
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/Sidebar.d.ts +8 -0
- package/dist/components/Sidebar.d.ts.map +1 -0
- package/dist/components/Sidebar.js +6 -0
- package/dist/components/Sidebar.js.map +1 -0
- package/dist/components/SimpleLivePreview.js +1 -1
- package/dist/components/SimpleLivePreview.js.map +1 -1
- package/dist/layouts/Layout.js +1 -1
- package/dist/layouts/Layout.js.map +1 -1
- package/dist/pages/Code.d.ts.map +1 -1
- package/dist/pages/Code.js +2 -2
- package/dist/pages/Code.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +81 -11
- package/dist/server.js.map +1 -1
- package/dist/services/terminal.d.ts +25 -1
- package/dist/services/terminal.d.ts.map +1 -1
- package/dist/services/terminal.js +135 -6
- package/dist/services/terminal.js.map +1 -1
- package/dist/services/websocket.d.ts +45 -0
- package/dist/services/websocket.d.ts.map +1 -0
- package/dist/services/websocket.js +306 -0
- package/dist/services/websocket.js.map +1 -0
- package/dist/static/components/Sidebar.js +225 -0
- package/dist/static/components/SimpleLivePreview.js +73 -53
- package/dist/static/components/Terminal.js +141 -61
- package/dist/static/signals/SidebarSignal.js +123 -0
- package/dist/static/signals/TerminalSignal.js +137 -2
- package/dist/static/styles/main.css +111 -25
- package/package.json +6 -2
- package/src/components/Sidebar.tsx +92 -0
- package/src/components/SimpleLivePreview.tsx +4 -4
- package/src/layouts/Layout.tsx +1 -1
- package/src/pages/Code.tsx +18 -145
- package/src/server.tsx +97 -12
- package/src/services/terminal.ts +164 -6
- package/src/services/websocket.ts +374 -0
- package/src/static/components/Sidebar.js +225 -0
- package/src/static/components/SimpleLivePreview.js +73 -53
- package/src/static/components/Terminal.js +141 -61
- package/src/static/signals/SidebarSignal.js +123 -0
- package/src/static/signals/TerminalSignal.js +137 -2
- package/src/static/styles/main.css +111 -25
- package/tailwind.config.ts +10 -0
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
import { signal, computed } from 'https://esm.sh/@preact/signals@1.2.2';
|
|
2
2
|
|
|
3
|
-
// Terminal state management for multiple terminals
|
|
3
|
+
// Terminal state management for multiple terminals with cross-tab sync
|
|
4
4
|
class TerminalStore {
|
|
5
5
|
constructor() {
|
|
6
6
|
// Core state signals
|
|
7
|
-
this.terminals = signal([]); // Array of terminal objects: {id, sessionId, status, index}
|
|
7
|
+
this.terminals = signal([]); // Array of terminal objects: {id, sessionId, status, index, clientCount}
|
|
8
8
|
this.activeTerminalId = signal('terminal-1'); // Currently active terminal
|
|
9
9
|
this.nextTerminalIndex = signal(1); // For generating new terminal indices
|
|
10
10
|
|
|
11
|
+
// Cross-tab sync signals
|
|
12
|
+
this.sessionClients = signal(new Map()); // sessionId -> client count
|
|
13
|
+
this.globalStatus = signal('initializing'); // Overall connection status
|
|
14
|
+
|
|
11
15
|
// Computed values
|
|
12
16
|
this.activeTerminal = computed(() =>
|
|
13
17
|
this.terminals.value.find(t => t.id === this.activeTerminalId.value)
|
|
14
18
|
);
|
|
15
19
|
this.terminalCount = computed(() => this.terminals.value.length);
|
|
20
|
+
this.hasConnectedTerminals = computed(() =>
|
|
21
|
+
this.terminals.value.some(t => t.status === 'connected')
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
// Initialize cross-tab communication
|
|
25
|
+
this.initializeCrossTabSync();
|
|
16
26
|
}
|
|
17
27
|
|
|
18
28
|
// Actions for managing terminals
|
|
@@ -118,6 +128,131 @@ class TerminalStore {
|
|
|
118
128
|
console.log('Terminal Store State:', this.getState());
|
|
119
129
|
}
|
|
120
130
|
|
|
131
|
+
// Initialize cross-tab communication
|
|
132
|
+
initializeCrossTabSync() {
|
|
133
|
+
// Listen for WebSocket events from terminal managers
|
|
134
|
+
this.setupWebSocketEventListeners();
|
|
135
|
+
|
|
136
|
+
// Listen for browser storage events for cross-tab sync
|
|
137
|
+
this.setupStorageEventListeners();
|
|
138
|
+
|
|
139
|
+
// Initialize from any existing state in localStorage
|
|
140
|
+
this.loadStateFromStorage();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
setupWebSocketEventListeners() {
|
|
144
|
+
// Custom events dispatched by TerminalManager
|
|
145
|
+
document.addEventListener('terminal:clients_count', (event) => {
|
|
146
|
+
this.updateSessionClientCount(event.detail.sessionId, event.detail.count);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
document.addEventListener('terminal:session_closed', (event) => {
|
|
150
|
+
this.handleSessionClosed(event.detail.sessionId);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
document.addEventListener('terminal:global_status', (event) => {
|
|
154
|
+
this.globalStatus.value = event.detail.status;
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
setupStorageEventListeners() {
|
|
159
|
+
// Listen for changes from other tabs
|
|
160
|
+
window.addEventListener('storage', (event) => {
|
|
161
|
+
if (event.key === 'treesap_terminal_state') {
|
|
162
|
+
this.syncFromStorage(event.newValue);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Save state to storage when terminals change
|
|
167
|
+
this.terminals.subscribe(() => {
|
|
168
|
+
this.saveStateToStorage();
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
loadStateFromStorage() {
|
|
173
|
+
try {
|
|
174
|
+
const savedState = localStorage.getItem('treesap_terminal_state');
|
|
175
|
+
if (savedState) {
|
|
176
|
+
const state = JSON.parse(savedState);
|
|
177
|
+
// Only sync certain properties, not connection status which is per-tab
|
|
178
|
+
if (state.activeTerminalId) {
|
|
179
|
+
this.activeTerminalId.value = state.activeTerminalId;
|
|
180
|
+
}
|
|
181
|
+
if (state.nextTerminalIndex) {
|
|
182
|
+
this.nextTerminalIndex.value = state.nextTerminalIndex;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
} catch (error) {
|
|
186
|
+
console.error('Error loading terminal state from storage:', error);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
saveStateToStorage() {
|
|
191
|
+
try {
|
|
192
|
+
const state = {
|
|
193
|
+
activeTerminalId: this.activeTerminalId.value,
|
|
194
|
+
nextTerminalIndex: this.nextTerminalIndex.value,
|
|
195
|
+
timestamp: Date.now()
|
|
196
|
+
};
|
|
197
|
+
localStorage.setItem('treesap_terminal_state', JSON.stringify(state));
|
|
198
|
+
} catch (error) {
|
|
199
|
+
console.error('Error saving terminal state to storage:', error);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
syncFromStorage(newValueStr) {
|
|
204
|
+
if (!newValueStr) return;
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
const newState = JSON.parse(newValueStr);
|
|
208
|
+
// Only sync if the change is from another tab (newer timestamp)
|
|
209
|
+
const currentTimestamp = this.lastSaveTimestamp || 0;
|
|
210
|
+
if (newState.timestamp && newState.timestamp > currentTimestamp) {
|
|
211
|
+
if (newState.activeTerminalId) {
|
|
212
|
+
this.activeTerminalId.value = newState.activeTerminalId;
|
|
213
|
+
}
|
|
214
|
+
if (newState.nextTerminalIndex) {
|
|
215
|
+
this.nextTerminalIndex.value = newState.nextTerminalIndex;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error('Error syncing terminal state from storage:', error);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
updateSessionClientCount(sessionId, count) {
|
|
224
|
+
const currentMap = new Map(this.sessionClients.value);
|
|
225
|
+
currentMap.set(sessionId, count);
|
|
226
|
+
this.sessionClients.value = currentMap;
|
|
227
|
+
|
|
228
|
+
// Update terminal objects with client count
|
|
229
|
+
this.terminals.value = this.terminals.value.map(terminal => {
|
|
230
|
+
if (terminal.sessionId === sessionId) {
|
|
231
|
+
return { ...terminal, clientCount: count, lastUpdated: new Date() };
|
|
232
|
+
}
|
|
233
|
+
return terminal;
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
console.log(`Session ${sessionId} now has ${count} clients`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
handleSessionClosed(sessionId) {
|
|
240
|
+
console.log(`Session ${sessionId} was closed`);
|
|
241
|
+
|
|
242
|
+
// Update terminals for this session
|
|
243
|
+
this.terminals.value = this.terminals.value.map(terminal => {
|
|
244
|
+
if (terminal.sessionId === sessionId) {
|
|
245
|
+
return { ...terminal, status: 'closed', lastUpdated: new Date() };
|
|
246
|
+
}
|
|
247
|
+
return terminal;
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Remove from session clients
|
|
251
|
+
const currentMap = new Map(this.sessionClients.value);
|
|
252
|
+
currentMap.delete(sessionId);
|
|
253
|
+
this.sessionClients.value = currentMap;
|
|
254
|
+
}
|
|
255
|
+
|
|
121
256
|
// Initialize default terminal
|
|
122
257
|
initializeDefaultTerminal() {
|
|
123
258
|
if (this.terminals.value.length === 0) {
|
|
@@ -1093,6 +1093,10 @@ video {
|
|
|
1093
1093
|
margin-bottom: 0;
|
|
1094
1094
|
}
|
|
1095
1095
|
|
|
1096
|
+
.pointer-events-none {
|
|
1097
|
+
pointer-events: none;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1096
1100
|
.visible {
|
|
1097
1101
|
visibility: visible;
|
|
1098
1102
|
}
|
|
@@ -1101,6 +1105,10 @@ video {
|
|
|
1101
1105
|
position: static;
|
|
1102
1106
|
}
|
|
1103
1107
|
|
|
1108
|
+
.fixed {
|
|
1109
|
+
position: fixed;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1104
1112
|
.absolute {
|
|
1105
1113
|
position: absolute;
|
|
1106
1114
|
}
|
|
@@ -1109,10 +1117,38 @@ video {
|
|
|
1109
1117
|
position: relative;
|
|
1110
1118
|
}
|
|
1111
1119
|
|
|
1120
|
+
.inset-0 {
|
|
1121
|
+
inset: 0px;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
.left-0 {
|
|
1125
|
+
left: 0px;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
.left-4 {
|
|
1129
|
+
left: 1rem;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
.top-0 {
|
|
1133
|
+
top: 0px;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
.top-4 {
|
|
1137
|
+
top: 1rem;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
.z-40 {
|
|
1141
|
+
z-index: 40;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1112
1144
|
.z-50 {
|
|
1113
1145
|
z-index: 50;
|
|
1114
1146
|
}
|
|
1115
1147
|
|
|
1148
|
+
.z-60 {
|
|
1149
|
+
z-index: 60;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1116
1152
|
.mb-12 {
|
|
1117
1153
|
margin-bottom: 3rem;
|
|
1118
1154
|
}
|
|
@@ -1141,10 +1177,6 @@ video {
|
|
|
1141
1177
|
margin-left: 0.5rem;
|
|
1142
1178
|
}
|
|
1143
1179
|
|
|
1144
|
-
.mr-1 {
|
|
1145
|
-
margin-right: 0.25rem;
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
1180
|
.mr-2 {
|
|
1149
1181
|
margin-right: 0.5rem;
|
|
1150
1182
|
}
|
|
@@ -1221,6 +1253,20 @@ video {
|
|
|
1221
1253
|
flex-shrink: 0;
|
|
1222
1254
|
}
|
|
1223
1255
|
|
|
1256
|
+
.-translate-x-full {
|
|
1257
|
+
--tw-translate-x: -100%;
|
|
1258
|
+
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
.translate-x-0 {
|
|
1262
|
+
--tw-translate-x: 0px;
|
|
1263
|
+
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
.transform {
|
|
1267
|
+
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1224
1270
|
@keyframes spin {
|
|
1225
1271
|
to {
|
|
1226
1272
|
transform: rotate(360deg);
|
|
@@ -1251,10 +1297,6 @@ video {
|
|
|
1251
1297
|
justify-content: center;
|
|
1252
1298
|
}
|
|
1253
1299
|
|
|
1254
|
-
.gap-1 {
|
|
1255
|
-
gap: 0.25rem;
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
1300
|
.gap-2 {
|
|
1259
1301
|
gap: 0.5rem;
|
|
1260
1302
|
}
|
|
@@ -1367,6 +1409,10 @@ video {
|
|
|
1367
1409
|
background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
|
|
1368
1410
|
}
|
|
1369
1411
|
|
|
1412
|
+
.bg-opacity-50 {
|
|
1413
|
+
--tw-bg-opacity: 0.5;
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1370
1416
|
.bg-gradient-to-br {
|
|
1371
1417
|
background-image: linear-gradient(to bottom right, var(--tw-gradient-stops));
|
|
1372
1418
|
}
|
|
@@ -1381,10 +1427,6 @@ video {
|
|
|
1381
1427
|
--tw-gradient-to: #16a34a var(--tw-gradient-to-position);
|
|
1382
1428
|
}
|
|
1383
1429
|
|
|
1384
|
-
.p-0\.5 {
|
|
1385
|
-
padding: 0.125rem;
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
1430
|
.p-1 {
|
|
1389
1431
|
padding: 0.25rem;
|
|
1390
1432
|
}
|
|
@@ -1397,10 +1439,6 @@ video {
|
|
|
1397
1439
|
padding: 0.75rem;
|
|
1398
1440
|
}
|
|
1399
1441
|
|
|
1400
|
-
.p-4 {
|
|
1401
|
-
padding: 1rem;
|
|
1402
|
-
}
|
|
1403
|
-
|
|
1404
1442
|
.p-6 {
|
|
1405
1443
|
padding: 1.5rem;
|
|
1406
1444
|
}
|
|
@@ -1409,11 +1447,6 @@ video {
|
|
|
1409
1447
|
padding: 2rem;
|
|
1410
1448
|
}
|
|
1411
1449
|
|
|
1412
|
-
.px-2 {
|
|
1413
|
-
padding-left: 0.5rem;
|
|
1414
|
-
padding-right: 0.5rem;
|
|
1415
|
-
}
|
|
1416
|
-
|
|
1417
1450
|
.px-3 {
|
|
1418
1451
|
padding-left: 0.75rem;
|
|
1419
1452
|
padding-right: 0.75rem;
|
|
@@ -1512,6 +1545,14 @@ video {
|
|
|
1512
1545
|
color: rgb(255 255 255 / var(--tw-text-opacity, 1));
|
|
1513
1546
|
}
|
|
1514
1547
|
|
|
1548
|
+
.opacity-0 {
|
|
1549
|
+
opacity: 0;
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
.opacity-100 {
|
|
1553
|
+
opacity: 1;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1515
1556
|
.opacity-50 {
|
|
1516
1557
|
opacity: 0.5;
|
|
1517
1558
|
}
|
|
@@ -1526,6 +1567,12 @@ video {
|
|
|
1526
1567
|
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
|
1527
1568
|
}
|
|
1528
1569
|
|
|
1570
|
+
.backdrop-blur-sm {
|
|
1571
|
+
--tw-backdrop-blur: blur(4px);
|
|
1572
|
+
-webkit-backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
|
|
1573
|
+
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1529
1576
|
.transition-all {
|
|
1530
1577
|
transition-property: all;
|
|
1531
1578
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
@@ -1538,10 +1585,26 @@ video {
|
|
|
1538
1585
|
transition-duration: 150ms;
|
|
1539
1586
|
}
|
|
1540
1587
|
|
|
1588
|
+
.transition-opacity {
|
|
1589
|
+
transition-property: opacity;
|
|
1590
|
+
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
1591
|
+
transition-duration: 150ms;
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
.transition-transform {
|
|
1595
|
+
transition-property: transform;
|
|
1596
|
+
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
1597
|
+
transition-duration: 150ms;
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1541
1600
|
.duration-300 {
|
|
1542
1601
|
transition-duration: 300ms;
|
|
1543
1602
|
}
|
|
1544
1603
|
|
|
1604
|
+
.ease-in-out {
|
|
1605
|
+
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1545
1608
|
.focus-within\:border-\[\#0e639c\]:focus-within {
|
|
1546
1609
|
--tw-border-opacity: 1;
|
|
1547
1610
|
border-color: rgb(14 99 156 / var(--tw-border-opacity, 1));
|
|
@@ -1593,11 +1656,34 @@ video {
|
|
|
1593
1656
|
outline-offset: 2px;
|
|
1594
1657
|
}
|
|
1595
1658
|
|
|
1596
|
-
.disabled\:opacity-50:disabled {
|
|
1597
|
-
opacity: 0.5;
|
|
1598
|
-
}
|
|
1599
|
-
|
|
1600
1659
|
.group:hover .group-hover\:text-white {
|
|
1601
1660
|
--tw-text-opacity: 1;
|
|
1602
1661
|
color: rgb(255 255 255 / var(--tw-text-opacity, 1));
|
|
1603
1662
|
}
|
|
1663
|
+
|
|
1664
|
+
@media (min-width: 768px) {
|
|
1665
|
+
.md\:relative {
|
|
1666
|
+
position: relative;
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
.md\:z-auto {
|
|
1670
|
+
z-index: auto;
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
.md\:hidden {
|
|
1674
|
+
display: none;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
.md\:w-2\/5 {
|
|
1678
|
+
width: 40%;
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
.md\:flex-1 {
|
|
1682
|
+
flex: 1 1 0%;
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
.md\:translate-x-0 {
|
|
1686
|
+
--tw-translate-x: 0px;
|
|
1687
|
+
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
1688
|
+
}
|
|
1689
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "treesap",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "AI Agent Framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -37,10 +37,14 @@
|
|
|
37
37
|
"commander": "^12.1.0",
|
|
38
38
|
"dotenv": "^16.4.7",
|
|
39
39
|
"hono": "^4.9.1",
|
|
40
|
-
"node-pty": "^1.1.0-beta34"
|
|
40
|
+
"node-pty": "^1.1.0-beta34",
|
|
41
|
+
"uuid": "^11.1.0",
|
|
42
|
+
"ws": "^8.18.3"
|
|
41
43
|
},
|
|
42
44
|
"devDependencies": {
|
|
43
45
|
"@types/node": "^22.15.15",
|
|
46
|
+
"@types/uuid": "^10.0.0",
|
|
47
|
+
"@types/ws": "^8.18.1",
|
|
44
48
|
"tailwindcss": "^3.4.17",
|
|
45
49
|
"tsx": "^4.19.2",
|
|
46
50
|
"typescript": "^5.7.3"
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Terminal as TerminalComponent } from "./Terminal.js";
|
|
2
|
+
|
|
3
|
+
interface SidebarProps {
|
|
4
|
+
id?: string;
|
|
5
|
+
previewPort?: number;
|
|
6
|
+
workingDirectory?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function Sidebar({ id = "sidebar", previewPort = 1234, workingDirectory }: SidebarProps) {
|
|
10
|
+
return (
|
|
11
|
+
<sapling-island loading="visible">
|
|
12
|
+
<template>
|
|
13
|
+
<script type="module" src="https://code.iconify.design/iconify-icon/2.0.0/iconify-icon.min.js"></script>
|
|
14
|
+
<script type="module" src="/components/Sidebar.js"></script>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
{/* Mobile backdrop */}
|
|
18
|
+
<div
|
|
19
|
+
id={`${id}-backdrop`}
|
|
20
|
+
class="fixed inset-0 bg-black bg-opacity-50 backdrop-blur-sm z-40 transition-opacity duration-300 opacity-0 pointer-events-none md:hidden"
|
|
21
|
+
></div>
|
|
22
|
+
|
|
23
|
+
{/* Sidebar container */}
|
|
24
|
+
<div
|
|
25
|
+
id={`${id}-pane`}
|
|
26
|
+
class="fixed left-0 top-0 h-full w-full z-50 transform -translate-x-full transition-transform duration-300 ease-in-out md:relative md:translate-x-0 md:w-2/5 md:z-auto border-r border-[#3c3c3c] flex flex-col bg-[#252526]"
|
|
27
|
+
>
|
|
28
|
+
{/* Preview Controls */}
|
|
29
|
+
<div class="p-3 border-b border-[#3c3c3c] bg-[#2d2d30] flex-shrink-0">
|
|
30
|
+
<div class="flex items-center gap-2">
|
|
31
|
+
{/* Mobile close button */}
|
|
32
|
+
<button
|
|
33
|
+
type="button"
|
|
34
|
+
id={`${id}-close-btn`}
|
|
35
|
+
class="p-2 hover:bg-[#3c3c3c] rounded-md transition-colors flex-shrink-0 text-[#cccccc] hover:text-white md:hidden"
|
|
36
|
+
title="Close Sidebar"
|
|
37
|
+
>
|
|
38
|
+
<iconify-icon icon="tabler:x" width="16" height="16"></iconify-icon>
|
|
39
|
+
</button>
|
|
40
|
+
|
|
41
|
+
{/* Back to Home */}
|
|
42
|
+
<a
|
|
43
|
+
href="/"
|
|
44
|
+
class="p-2 hover:bg-[#3c3c3c] rounded-md transition-colors flex-shrink-0 text-[#cccccc] hover:text-white"
|
|
45
|
+
title="Back to Home"
|
|
46
|
+
>
|
|
47
|
+
<iconify-icon icon="tabler:arrow-left" width="16" height="16"></iconify-icon>
|
|
48
|
+
</a>
|
|
49
|
+
|
|
50
|
+
{/* Refresh button */}
|
|
51
|
+
<button
|
|
52
|
+
type="button"
|
|
53
|
+
id="live-preview-refresh-btn"
|
|
54
|
+
class="p-2 hover:bg-[#3c3c3c] rounded-md transition-colors flex-shrink-0 text-[#cccccc] hover:text-white"
|
|
55
|
+
title="Reload"
|
|
56
|
+
>
|
|
57
|
+
<iconify-icon icon="tabler:refresh" width="16" height="16"></iconify-icon>
|
|
58
|
+
</button>
|
|
59
|
+
|
|
60
|
+
{/* URL input */}
|
|
61
|
+
<div class="flex-1 flex items-center bg-[#1e1e1e] border border-[#3c3c3c] rounded px-3 py-2 hover:border-[#0e639c] focus-within:border-[#0e639c] transition-all">
|
|
62
|
+
<iconify-icon icon="tabler:world" width="16" height="16" class="text-[#cccccc] mr-2"></iconify-icon>
|
|
63
|
+
<span class="text-[#cccccc] text-sm">localhost:{previewPort}/</span>
|
|
64
|
+
<input
|
|
65
|
+
id="live-preview-url-input"
|
|
66
|
+
type="text"
|
|
67
|
+
placeholder="path"
|
|
68
|
+
defaultValue=""
|
|
69
|
+
class="flex-1 bg-transparent text-sm focus:outline-none text-[#cccccc] ml-1"
|
|
70
|
+
/>
|
|
71
|
+
<button
|
|
72
|
+
type="button"
|
|
73
|
+
id="live-preview-load-btn"
|
|
74
|
+
class="ml-2 p-1 hover:bg-[#3c3c3c] rounded transition-colors flex-shrink-0 text-[#cccccc] hover:text-white"
|
|
75
|
+
title="Go"
|
|
76
|
+
>
|
|
77
|
+
<iconify-icon icon="tabler:chevron-right" width="16" height="16"></iconify-icon>
|
|
78
|
+
</button>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
{/* Terminal Content */}
|
|
84
|
+
<div class="flex-1 overflow-hidden bg-[#1e1e1e]">
|
|
85
|
+
<div class="h-full">
|
|
86
|
+
<TerminalComponent index={1} />
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
</sapling-island>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
@@ -25,14 +25,14 @@ export function SimpleLivePreview({ id = "simple-preview", previewPort = 5173 }:
|
|
|
25
25
|
data-preview-port={previewPort}
|
|
26
26
|
></iframe>
|
|
27
27
|
|
|
28
|
-
{/* Floating Sidebar Toggle */}
|
|
28
|
+
{/* Floating Sidebar Toggle - Only show when sidebar is closed */}
|
|
29
29
|
<button
|
|
30
30
|
id={`${id}-floating-hide-sidebar-btn`}
|
|
31
|
-
class="absolute p-3 bg-white border-2 border-gray-400 rounded-lg shadow-xl hover:bg-gray-50 hover:shadow-2xl transition-all
|
|
32
|
-
title="
|
|
31
|
+
class="absolute p-3 bg-white border-2 border-gray-400 rounded-lg shadow-xl hover:bg-gray-50 hover:shadow-2xl transition-all items-center justify-center z-50 hidden"
|
|
32
|
+
title="Show Sidebar"
|
|
33
33
|
style="position: absolute !important; z-index: 9999 !important; bottom: 16px; left: 16px;"
|
|
34
34
|
>
|
|
35
|
-
<iconify-icon id={`${id}-floating-hide-sidebar-icon`} icon="ph:sidebar-simple" width="20" height="20" class="text-gray-800"></iconify-icon>
|
|
35
|
+
<iconify-icon id={`${id}-floating-hide-sidebar-icon`} icon="ph:sidebar-simple-fill" width="20" height="20" class="text-gray-800"></iconify-icon>
|
|
36
36
|
</button>
|
|
37
37
|
</div>
|
|
38
38
|
</sapling-island>
|
package/src/layouts/Layout.tsx
CHANGED
|
@@ -14,7 +14,7 @@ export default function BaseLayout(props: BaseLayoutProps) {
|
|
|
14
14
|
<title>{props.title || "Treesap"}</title>
|
|
15
15
|
<meta name="description" content={props.description || "A modern web application"} />
|
|
16
16
|
<meta charset="UTF-8" />
|
|
17
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
17
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover, interactive-widget=resizes-content" />
|
|
18
18
|
<link rel="stylesheet" href="/styles/main.css" />
|
|
19
19
|
{/* Sapling Islands */}
|
|
20
20
|
<script type="module" src="https://sapling-is.land"></script>
|