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.
Files changed (43) hide show
  1. package/dist/components/Sidebar.d.ts +8 -0
  2. package/dist/components/Sidebar.d.ts.map +1 -0
  3. package/dist/components/Sidebar.js +6 -0
  4. package/dist/components/Sidebar.js.map +1 -0
  5. package/dist/components/SimpleLivePreview.js +1 -1
  6. package/dist/components/SimpleLivePreview.js.map +1 -1
  7. package/dist/layouts/Layout.js +1 -1
  8. package/dist/layouts/Layout.js.map +1 -1
  9. package/dist/pages/Code.d.ts.map +1 -1
  10. package/dist/pages/Code.js +2 -2
  11. package/dist/pages/Code.js.map +1 -1
  12. package/dist/server.d.ts.map +1 -1
  13. package/dist/server.js +81 -11
  14. package/dist/server.js.map +1 -1
  15. package/dist/services/terminal.d.ts +25 -1
  16. package/dist/services/terminal.d.ts.map +1 -1
  17. package/dist/services/terminal.js +135 -6
  18. package/dist/services/terminal.js.map +1 -1
  19. package/dist/services/websocket.d.ts +45 -0
  20. package/dist/services/websocket.d.ts.map +1 -0
  21. package/dist/services/websocket.js +306 -0
  22. package/dist/services/websocket.js.map +1 -0
  23. package/dist/static/components/Sidebar.js +225 -0
  24. package/dist/static/components/SimpleLivePreview.js +73 -53
  25. package/dist/static/components/Terminal.js +141 -61
  26. package/dist/static/signals/SidebarSignal.js +123 -0
  27. package/dist/static/signals/TerminalSignal.js +137 -2
  28. package/dist/static/styles/main.css +111 -25
  29. package/package.json +6 -2
  30. package/src/components/Sidebar.tsx +92 -0
  31. package/src/components/SimpleLivePreview.tsx +4 -4
  32. package/src/layouts/Layout.tsx +1 -1
  33. package/src/pages/Code.tsx +18 -145
  34. package/src/server.tsx +97 -12
  35. package/src/services/terminal.ts +164 -6
  36. package/src/services/websocket.ts +374 -0
  37. package/src/static/components/Sidebar.js +225 -0
  38. package/src/static/components/SimpleLivePreview.js +73 -53
  39. package/src/static/components/Terminal.js +141 -61
  40. package/src/static/signals/SidebarSignal.js +123 -0
  41. package/src/static/signals/TerminalSignal.js +137 -2
  42. package/src/static/styles/main.css +111 -25
  43. package/tailwind.config.ts +10 -0
@@ -0,0 +1,45 @@
1
+ import { WebSocket } from 'ws';
2
+ import type { Server } from 'http';
3
+ export interface WebSocketClient {
4
+ id: string;
5
+ ws: WebSocket;
6
+ sessionId?: string;
7
+ terminalId?: string;
8
+ lastPing: Date;
9
+ }
10
+ export interface WebSocketMessage {
11
+ type: 'join' | 'leave' | 'input' | 'ping' | 'pong';
12
+ sessionId?: string;
13
+ terminalId?: string;
14
+ data?: string;
15
+ timestamp?: number;
16
+ }
17
+ export declare class WebSocketTerminalService {
18
+ private static wss;
19
+ private static clients;
20
+ private static sessionClients;
21
+ static initialize(server: Server): void;
22
+ private static setupPingPong;
23
+ private static handleMessage;
24
+ private static handleJoin;
25
+ private static handleLeave;
26
+ private static handleInput;
27
+ private static handleDisconnect;
28
+ private static addClientToSession;
29
+ private static removeClientFromSession;
30
+ private static setupSessionOutputListener;
31
+ private static cleanupSessionOutputListener;
32
+ private static broadcastToSession;
33
+ private static broadcastClientCount;
34
+ private static sendToClient;
35
+ static getSessionClients(sessionId: string): string[];
36
+ static sendCommandToSession(sessionId: string, command: string): boolean;
37
+ static getActiveSessions(): Array<{
38
+ sessionId: string;
39
+ clientCount: number;
40
+ }>;
41
+ static getConnectedClients(): number;
42
+ static closeSession(sessionId: string): void;
43
+ static cleanup(): void;
44
+ }
45
+ //# sourceMappingURL=websocket.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../../src/services/websocket.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,SAAS,EAAE,MAAM,IAAI,CAAC;AAKhD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,SAAS,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,IAAI,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,wBAAwB;IACnC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAgC;IAClD,OAAO,CAAC,MAAM,CAAC,OAAO,CAAsC;IAC5D,OAAO,CAAC,MAAM,CAAC,cAAc,CAAkC;IAE/D,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM;IA8ChC,OAAO,CAAC,MAAM,CAAC,aAAa;IAsB5B,OAAO,CAAC,MAAM,CAAC,aAAa;IAkC5B,OAAO,CAAC,MAAM,CAAC,UAAU;IAuCzB,OAAO,CAAC,MAAM,CAAC,WAAW;IAe1B,OAAO,CAAC,MAAM,CAAC,WAAW;IA8B1B,OAAO,CAAC,MAAM,CAAC,gBAAgB;IAY/B,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAOjC,OAAO,CAAC,MAAM,CAAC,uBAAuB;IAYtC,OAAO,CAAC,MAAM,CAAC,0BAA0B;IAiBzC,OAAO,CAAC,MAAM,CAAC,4BAA4B;IAQ3C,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAcjC,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAenC,OAAO,CAAC,MAAM,CAAC,YAAY;IAe3B,MAAM,CAAC,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE;IAKrD,MAAM,CAAC,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO;IAaxE,MAAM,CAAC,iBAAiB,IAAI,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAO7E,MAAM,CAAC,mBAAmB,IAAI,MAAM;IAIpC,MAAM,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM;IAoBrC,MAAM,CAAC,OAAO;CAUf"}
@@ -0,0 +1,306 @@
1
+ import { WebSocketServer, WebSocket } from 'ws';
2
+ import { v4 as uuidv4 } from 'uuid';
3
+ import { TerminalService } from './terminal.js';
4
+ export class WebSocketTerminalService {
5
+ static wss = null;
6
+ static clients = new Map();
7
+ static sessionClients = new Map(); // sessionId -> Set<clientId>
8
+ static initialize(server) {
9
+ this.wss = new WebSocketServer({
10
+ server,
11
+ path: '/terminal/ws'
12
+ });
13
+ this.wss.on('connection', (ws, request) => {
14
+ const clientId = uuidv4();
15
+ const client = {
16
+ id: clientId,
17
+ ws,
18
+ lastPing: new Date()
19
+ };
20
+ this.clients.set(clientId, client);
21
+ console.log(`WebSocket client connected: ${clientId}`);
22
+ // Set up message handler
23
+ ws.on('message', (data) => {
24
+ this.handleMessage(clientId, data);
25
+ });
26
+ // Set up disconnect handler
27
+ ws.on('close', () => {
28
+ this.handleDisconnect(clientId);
29
+ });
30
+ // Set up error handler
31
+ ws.on('error', (error) => {
32
+ console.error(`WebSocket error for client ${clientId}:`, error);
33
+ this.handleDisconnect(clientId);
34
+ });
35
+ // Send initial connection success
36
+ this.sendToClient(clientId, {
37
+ type: 'pong',
38
+ timestamp: Date.now()
39
+ });
40
+ // Set up ping/pong for connection health
41
+ this.setupPingPong(clientId);
42
+ });
43
+ console.log('WebSocket server initialized for terminal connections');
44
+ }
45
+ static setupPingPong(clientId) {
46
+ const client = this.clients.get(clientId);
47
+ if (!client)
48
+ return;
49
+ const pingInterval = setInterval(() => {
50
+ if (client.ws.readyState === WebSocket.OPEN) {
51
+ client.ws.ping();
52
+ client.lastPing = new Date();
53
+ }
54
+ else {
55
+ clearInterval(pingInterval);
56
+ }
57
+ }, 30000); // Ping every 30 seconds
58
+ client.ws.on('pong', () => {
59
+ client.lastPing = new Date();
60
+ });
61
+ client.ws.on('close', () => {
62
+ clearInterval(pingInterval);
63
+ });
64
+ }
65
+ static handleMessage(clientId, data) {
66
+ try {
67
+ const message = JSON.parse(data.toString());
68
+ const client = this.clients.get(clientId);
69
+ if (!client) {
70
+ console.error(`Client ${clientId} not found`);
71
+ return;
72
+ }
73
+ switch (message.type) {
74
+ case 'join':
75
+ this.handleJoin(clientId, message);
76
+ break;
77
+ case 'leave':
78
+ this.handleLeave(clientId, message);
79
+ break;
80
+ case 'input':
81
+ this.handleInput(clientId, message);
82
+ break;
83
+ case 'ping':
84
+ this.sendToClient(clientId, {
85
+ type: 'pong',
86
+ timestamp: Date.now()
87
+ });
88
+ break;
89
+ default:
90
+ console.warn(`Unknown message type: ${message.type}`);
91
+ }
92
+ }
93
+ catch (error) {
94
+ console.error(`Error parsing WebSocket message from ${clientId}:`, error);
95
+ }
96
+ }
97
+ static handleJoin(clientId, message) {
98
+ const client = this.clients.get(clientId);
99
+ if (!client || !message.sessionId)
100
+ return;
101
+ console.log(`Client ${clientId} joining session ${message.sessionId}`);
102
+ // Remove client from any existing session
103
+ if (client.sessionId) {
104
+ this.removeClientFromSession(clientId, client.sessionId);
105
+ }
106
+ // Get or create terminal session
107
+ let session = TerminalService.getSession(message.sessionId);
108
+ if (!session) {
109
+ console.log(`Creating new terminal session: ${message.sessionId}`);
110
+ session = TerminalService.createSession(message.sessionId);
111
+ }
112
+ // Update client info
113
+ client.sessionId = message.sessionId;
114
+ client.terminalId = message.terminalId;
115
+ // Add client to session tracking
116
+ this.addClientToSession(clientId, message.sessionId);
117
+ // Set up output listener for this session (if not already set up)
118
+ this.setupSessionOutputListener(message.sessionId);
119
+ // Send connection confirmation
120
+ this.sendToClient(clientId, {
121
+ type: 'connected',
122
+ sessionId: message.sessionId,
123
+ timestamp: Date.now()
124
+ });
125
+ // Notify all clients in this session about client count
126
+ this.broadcastClientCount(message.sessionId);
127
+ }
128
+ static handleLeave(clientId, message) {
129
+ const client = this.clients.get(clientId);
130
+ if (!client)
131
+ return;
132
+ console.log(`Client ${clientId} leaving session ${client.sessionId}`);
133
+ if (client.sessionId) {
134
+ this.removeClientFromSession(clientId, client.sessionId);
135
+ this.broadcastClientCount(client.sessionId);
136
+ }
137
+ client.sessionId = undefined;
138
+ client.terminalId = undefined;
139
+ }
140
+ static handleInput(clientId, message) {
141
+ const client = this.clients.get(clientId);
142
+ if (!client || !message.sessionId || message.data === undefined)
143
+ return;
144
+ ;
145
+ // Get the terminal session
146
+ const session = TerminalService.getSession(message.sessionId);
147
+ if (!session) {
148
+ console.error(`Session ${message.sessionId} not found`);
149
+ this.sendToClient(clientId, {
150
+ type: 'error',
151
+ data: 'Terminal session not found',
152
+ timestamp: Date.now()
153
+ });
154
+ return;
155
+ }
156
+ // Send input directly to PTY (raw input like key presses)
157
+ try {
158
+ session.lastActivity = new Date();
159
+ session.process.write(message.data);
160
+ }
161
+ catch (error) {
162
+ console.error(`Failed to send input to session ${message.sessionId}:`, error);
163
+ this.sendToClient(clientId, {
164
+ type: 'error',
165
+ data: 'Failed to send input to terminal',
166
+ timestamp: Date.now()
167
+ });
168
+ }
169
+ }
170
+ static handleDisconnect(clientId) {
171
+ const client = this.clients.get(clientId);
172
+ console.log(`WebSocket client disconnected: ${clientId}`);
173
+ if (client?.sessionId) {
174
+ this.removeClientFromSession(clientId, client.sessionId);
175
+ this.broadcastClientCount(client.sessionId);
176
+ }
177
+ this.clients.delete(clientId);
178
+ }
179
+ static addClientToSession(clientId, sessionId) {
180
+ if (!this.sessionClients.has(sessionId)) {
181
+ this.sessionClients.set(sessionId, new Set());
182
+ }
183
+ this.sessionClients.get(sessionId).add(clientId);
184
+ }
185
+ static removeClientFromSession(clientId, sessionId) {
186
+ const clientSet = this.sessionClients.get(sessionId);
187
+ if (clientSet) {
188
+ clientSet.delete(clientId);
189
+ if (clientSet.size === 0) {
190
+ this.sessionClients.delete(sessionId);
191
+ // Clean up output listener if no clients are connected
192
+ this.cleanupSessionOutputListener(sessionId);
193
+ }
194
+ }
195
+ }
196
+ static setupSessionOutputListener(sessionId) {
197
+ const session = TerminalService.getSession(sessionId);
198
+ if (!session)
199
+ return;
200
+ // Check if listener already exists
201
+ if (session.eventEmitter.listenerCount('output') > 0) {
202
+ return; // Listener already set up
203
+ }
204
+ const handleOutput = (data) => {
205
+ this.broadcastToSession(sessionId, data);
206
+ };
207
+ session.eventEmitter.on('output', handleOutput);
208
+ console.log(`Set up output listener for session ${sessionId}`);
209
+ }
210
+ static cleanupSessionOutputListener(sessionId) {
211
+ const session = TerminalService.getSession(sessionId);
212
+ if (!session)
213
+ return;
214
+ session.eventEmitter.removeAllListeners('output');
215
+ console.log(`Cleaned up output listener for session ${sessionId}`);
216
+ }
217
+ static broadcastToSession(sessionId, data) {
218
+ const clientIds = this.sessionClients.get(sessionId);
219
+ if (!clientIds)
220
+ return;
221
+ const message = {
222
+ ...data,
223
+ timestamp: Date.now()
224
+ };
225
+ for (const clientId of clientIds) {
226
+ this.sendToClient(clientId, message);
227
+ }
228
+ }
229
+ static broadcastClientCount(sessionId) {
230
+ const clientIds = this.sessionClients.get(sessionId);
231
+ const count = clientIds ? clientIds.size : 0;
232
+ if (clientIds) {
233
+ for (const clientId of clientIds) {
234
+ this.sendToClient(clientId, {
235
+ type: 'clients_count',
236
+ count,
237
+ timestamp: Date.now()
238
+ });
239
+ }
240
+ }
241
+ }
242
+ static sendToClient(clientId, message) {
243
+ const client = this.clients.get(clientId);
244
+ if (!client || client.ws.readyState !== WebSocket.OPEN) {
245
+ return;
246
+ }
247
+ try {
248
+ client.ws.send(JSON.stringify(message));
249
+ }
250
+ catch (error) {
251
+ console.error(`Error sending message to client ${clientId}:`, error);
252
+ this.handleDisconnect(clientId);
253
+ }
254
+ }
255
+ // API methods for external control
256
+ static getSessionClients(sessionId) {
257
+ const clientSet = this.sessionClients.get(sessionId);
258
+ return clientSet ? Array.from(clientSet) : [];
259
+ }
260
+ static sendCommandToSession(sessionId, command) {
261
+ // Send command to terminal
262
+ const success = TerminalService.executeCommand(sessionId, command);
263
+ if (success) {
264
+ // The output will be automatically broadcast to all connected clients
265
+ // via the output listener
266
+ console.log(`Command sent to session ${sessionId}: ${command.substring(0, 50)}...`);
267
+ }
268
+ return success;
269
+ }
270
+ static getActiveSessions() {
271
+ return Array.from(this.sessionClients.entries()).map(([sessionId, clients]) => ({
272
+ sessionId,
273
+ clientCount: clients.size
274
+ }));
275
+ }
276
+ static getConnectedClients() {
277
+ return this.clients.size;
278
+ }
279
+ static closeSession(sessionId) {
280
+ const clientIds = this.sessionClients.get(sessionId);
281
+ if (clientIds) {
282
+ // Notify all clients that the session is closing
283
+ for (const clientId of clientIds) {
284
+ this.sendToClient(clientId, {
285
+ type: 'session_closed',
286
+ sessionId,
287
+ timestamp: Date.now()
288
+ });
289
+ }
290
+ // Clean up client tracking
291
+ this.sessionClients.delete(sessionId);
292
+ }
293
+ // Clean up the terminal session
294
+ TerminalService.destroySession(sessionId);
295
+ }
296
+ static cleanup() {
297
+ if (this.wss) {
298
+ console.log('Closing WebSocket server...');
299
+ this.wss.close();
300
+ this.wss = null;
301
+ }
302
+ this.clients.clear();
303
+ this.sessionClients.clear();
304
+ }
305
+ }
306
+ //# sourceMappingURL=websocket.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket.js","sourceRoot":"","sources":["../../src/services/websocket.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,225 @@
1
+ // Sidebar component JavaScript for responsive behavior
2
+ import { sidebarStore } from '/signals/SidebarSignal.js';
3
+
4
+ class SidebarManager {
5
+ constructor(id = 'sidebar') {
6
+ this.id = id;
7
+
8
+ // DOM elements
9
+ this.backdrop = document.getElementById(`${id}-backdrop`);
10
+ this.pane = document.getElementById(`${id}-pane`);
11
+ this.closeBtn = document.getElementById(`${id}-close-btn`);
12
+ this.refreshBtn = document.getElementById('live-preview-refresh-btn');
13
+ this.urlInput = document.getElementById('live-preview-url-input');
14
+ this.loadBtn = document.getElementById('live-preview-load-btn');
15
+
16
+ // Reference to the sidebar store
17
+ this.store = sidebarStore;
18
+
19
+ this.init();
20
+ }
21
+
22
+ init() {
23
+ console.log('Initializing Sidebar:', this.id);
24
+ console.log('Elements found:', {
25
+ backdrop: !!this.backdrop,
26
+ pane: !!this.pane,
27
+ closeBtn: !!this.closeBtn,
28
+ refreshBtn: !!this.refreshBtn,
29
+ urlInput: !!this.urlInput,
30
+ loadBtn: !!this.loadBtn
31
+ });
32
+
33
+ // Set up event listeners
34
+ this.setupEventListeners();
35
+
36
+ // Subscribe to store state changes
37
+ this.subscribeToStore();
38
+
39
+ // Initial state update
40
+ this.updateSidebarState();
41
+ this.updateMobileToggle();
42
+ }
43
+
44
+ setupEventListeners() {
45
+ // Mobile close button
46
+ this.closeBtn?.addEventListener('click', () => this.store.close());
47
+
48
+ // Backdrop click to close
49
+ this.backdrop?.addEventListener('click', () => this.store.close());
50
+
51
+ // Mobile toggle button (in main layout)
52
+ const mobileToggle = document.getElementById('mobile-sidebar-toggle');
53
+ mobileToggle?.addEventListener('click', () => this.store.toggle());
54
+
55
+ // Refresh button
56
+ this.refreshBtn?.addEventListener('click', () => this.refreshPreview());
57
+
58
+ // URL navigation
59
+ this.loadBtn?.addEventListener('click', (e) => {
60
+ e.preventDefault();
61
+ this.loadUrl();
62
+ });
63
+
64
+ this.urlInput?.addEventListener('keypress', (e) => {
65
+ if (e.key === 'Enter') {
66
+ e.preventDefault();
67
+ this.loadUrl();
68
+ }
69
+ });
70
+
71
+ // Keyboard shortcuts
72
+ document.addEventListener('keydown', (e) => {
73
+ // Escape key to close sidebar on mobile
74
+ if (e.key === 'Escape' && this.store.isMobile.value && this.store.isOpen.value) {
75
+ e.preventDefault();
76
+ this.store.close();
77
+ }
78
+
79
+ // Cmd/Ctrl + B to toggle sidebar
80
+ if ((e.metaKey || e.ctrlKey) && e.key === 'b') {
81
+ e.preventDefault();
82
+ this.store.toggle();
83
+ }
84
+ });
85
+
86
+ // Listen for custom events to maintain backward compatibility
87
+ document.addEventListener('sidebar:toggle', () => this.store.toggle());
88
+ document.addEventListener('sidebar:open', () => this.store.open());
89
+ document.addEventListener('sidebar:close', () => this.store.close());
90
+ }
91
+
92
+ subscribeToStore() {
93
+ // Subscribe to store changes and update UI accordingly
94
+ this.store.isOpen.subscribe(() => this.updateSidebarState());
95
+ this.store.isMobile.subscribe(() => this.updateSidebarState());
96
+ this.store.shouldShowBackdrop.subscribe(() => this.updateSidebarState());
97
+ this.store.shouldShowMobileToggle.subscribe(() => this.updateMobileToggle());
98
+ }
99
+
100
+ updateSidebarState() {
101
+ if (!this.pane || !this.backdrop) return;
102
+
103
+ const isOpen = this.store.isOpen.value;
104
+ const isMobile = this.store.isMobile.value;
105
+ const shouldShowBackdrop = this.store.shouldShowBackdrop.value;
106
+
107
+ if (isMobile) {
108
+ // Mobile behavior: overlay
109
+ if (shouldShowBackdrop) {
110
+ // Show backdrop
111
+ this.backdrop.classList.remove('opacity-0', 'pointer-events-none');
112
+ this.backdrop.classList.add('opacity-100');
113
+
114
+ // Prevent body scroll
115
+ document.body.style.overflow = 'hidden';
116
+ } else {
117
+ // Hide backdrop
118
+ this.backdrop.classList.remove('opacity-100');
119
+ this.backdrop.classList.add('opacity-0', 'pointer-events-none');
120
+
121
+ // Restore body scroll
122
+ document.body.style.overflow = '';
123
+ }
124
+
125
+ if (isOpen) {
126
+ // Show sidebar
127
+ this.pane.classList.remove('-translate-x-full');
128
+ this.pane.classList.add('translate-x-0');
129
+ } else {
130
+ // Hide sidebar
131
+ this.pane.classList.remove('translate-x-0');
132
+ this.pane.classList.add('-translate-x-full');
133
+ }
134
+ } else {
135
+ // Desktop behavior: side panel
136
+ // Hide backdrop (not needed on desktop)
137
+ this.backdrop.classList.add('opacity-0', 'pointer-events-none');
138
+
139
+ // Restore body scroll
140
+ document.body.style.overflow = '';
141
+
142
+ if (isOpen) {
143
+ // Show sidebar
144
+ this.pane.classList.remove('-translate-x-full');
145
+ this.pane.classList.add('translate-x-0');
146
+ this.pane.style.display = '';
147
+ } else {
148
+ // Hide sidebar completely on desktop
149
+ this.pane.style.display = 'none';
150
+ }
151
+ }
152
+ }
153
+
154
+ updateMobileToggle() {
155
+ const mobileToggle = document.getElementById('mobile-sidebar-toggle');
156
+ const shouldShow = this.store.shouldShowMobileToggle.value;
157
+
158
+ if (mobileToggle) {
159
+ mobileToggle.style.display = shouldShow ? 'flex' : 'none';
160
+ }
161
+ }
162
+
163
+ refreshPreview() {
164
+ // Dispatch event for SimpleLivePreview to handle
165
+ document.dispatchEvent(new CustomEvent('preview:refresh'));
166
+ }
167
+
168
+ loadUrl() {
169
+ if (this.urlInput) {
170
+ const path = this.urlInput.value.trim();
171
+ // Dispatch event for SimpleLivePreview to handle
172
+ document.dispatchEvent(new CustomEvent('preview:loadUrl', {
173
+ detail: { path }
174
+ }));
175
+ }
176
+ }
177
+
178
+ // Public API methods
179
+ getState() {
180
+ return this.store.getState();
181
+ }
182
+
183
+ destroy() {
184
+ // Restore body scroll
185
+ document.body.style.overflow = '';
186
+
187
+ // Clean up is handled by the signal store
188
+ }
189
+ }
190
+
191
+ // Auto-initialize when script loads
192
+ console.log('Sidebar.js loaded, looking for sidebar containers...');
193
+
194
+ function initializeSidebar() {
195
+ // Look for sapling-islands containing sidebar content
196
+ const saplingIslands = document.querySelectorAll('sapling-island');
197
+
198
+ for (const island of saplingIslands) {
199
+ // Look for sidebar pane div
200
+ const sidebarPane = island.querySelector('div[id$="-pane"]');
201
+ if (sidebarPane && sidebarPane.id.includes('sidebar')) {
202
+ const sidebarId = sidebarPane.id.replace('-pane', '');
203
+ console.log('Found Sidebar component with ID:', sidebarId);
204
+
205
+ // Create and store manager
206
+ const manager = new SidebarManager(sidebarId);
207
+ window.sidebarManager = manager; // Make globally available
208
+
209
+ break; // Only one sidebar per page
210
+ }
211
+ }
212
+ }
213
+
214
+ // Initialize immediately since Sapling islands are ready
215
+ initializeSidebar();
216
+
217
+ // Make available globally
218
+ window.SidebarManager = SidebarManager;
219
+
220
+ // Cleanup on page unload
221
+ window.addEventListener('beforeunload', () => {
222
+ if (window.sidebarManager) {
223
+ window.sidebarManager.destroy();
224
+ }
225
+ });