treesap 0.1.9 → 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.
@@ -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
  }
@@ -1217,6 +1253,20 @@ video {
1217
1253
  flex-shrink: 0;
1218
1254
  }
1219
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
+
1220
1270
  @keyframes spin {
1221
1271
  to {
1222
1272
  transform: rotate(360deg);
@@ -1359,6 +1409,10 @@ video {
1359
1409
  background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1));
1360
1410
  }
1361
1411
 
1412
+ .bg-opacity-50 {
1413
+ --tw-bg-opacity: 0.5;
1414
+ }
1415
+
1362
1416
  .bg-gradient-to-br {
1363
1417
  background-image: linear-gradient(to bottom right, var(--tw-gradient-stops));
1364
1418
  }
@@ -1491,6 +1545,14 @@ video {
1491
1545
  color: rgb(255 255 255 / var(--tw-text-opacity, 1));
1492
1546
  }
1493
1547
 
1548
+ .opacity-0 {
1549
+ opacity: 0;
1550
+ }
1551
+
1552
+ .opacity-100 {
1553
+ opacity: 1;
1554
+ }
1555
+
1494
1556
  .opacity-50 {
1495
1557
  opacity: 0.5;
1496
1558
  }
@@ -1505,6 +1567,12 @@ video {
1505
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);
1506
1568
  }
1507
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
+
1508
1576
  .transition-all {
1509
1577
  transition-property: all;
1510
1578
  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
@@ -1517,10 +1585,26 @@ video {
1517
1585
  transition-duration: 150ms;
1518
1586
  }
1519
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
+
1520
1600
  .duration-300 {
1521
1601
  transition-duration: 300ms;
1522
1602
  }
1523
1603
 
1604
+ .ease-in-out {
1605
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1606
+ }
1607
+
1524
1608
  .focus-within\:border-\[\#0e639c\]:focus-within {
1525
1609
  --tw-border-opacity: 1;
1526
1610
  border-color: rgb(14 99 156 / var(--tw-border-opacity, 1));
@@ -1576,3 +1660,30 @@ video {
1576
1660
  --tw-text-opacity: 1;
1577
1661
  color: rgb(255 255 255 / var(--tw-text-opacity, 1));
1578
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
+ }
@@ -6,6 +6,16 @@ const config: Config = {
6
6
  "./src/**/*.{js,ts,jsx,tsx}",
7
7
  "./static/**/*.{js,ts,jsx,tsx}",
8
8
  ],
9
+ theme: {
10
+ extend: {
11
+ width: {
12
+ '2/5': '40%',
13
+ },
14
+ zIndex: {
15
+ '60': '60',
16
+ }
17
+ }
18
+ },
9
19
  plugins: [typography()],
10
20
  };
11
21