@mooncompany/uplink-chat 0.5.0
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.
Potentially problematic release.
This version of @mooncompany/uplink-chat might be problematic. Click here for more details.
- package/LICENSE +21 -0
- package/README.md +185 -0
- package/bin/uplink.js +279 -0
- package/middleware/error-handler.js +69 -0
- package/package.json +93 -0
- package/public/css/agents.36b98c0f.css +1469 -0
- package/public/css/agents.css +1469 -0
- package/public/css/app.a6a7f8f5.css +2731 -0
- package/public/css/app.css +2731 -0
- package/public/css/artifacts.css +444 -0
- package/public/css/commands.css +55 -0
- package/public/css/connection.css +131 -0
- package/public/css/dashboard.css +233 -0
- package/public/css/developer.css +328 -0
- package/public/css/files.css +123 -0
- package/public/css/markdown.css +156 -0
- package/public/css/message-actions.css +278 -0
- package/public/css/mobile.css +614 -0
- package/public/css/panels-unified.css +483 -0
- package/public/css/premium.css +415 -0
- package/public/css/realtime.css +189 -0
- package/public/css/satellites.css +401 -0
- package/public/css/shortcuts.css +185 -0
- package/public/css/split-view.4def0262.css +673 -0
- package/public/css/split-view.css +673 -0
- package/public/css/theme-generator.css +391 -0
- package/public/css/themes.css +387 -0
- package/public/css/timestamps.css +54 -0
- package/public/css/variables.css +78 -0
- package/public/dist/bundle.b55050c4.js +15757 -0
- package/public/favicon.svg +24 -0
- package/public/img/agents/ada.png +0 -0
- package/public/img/agents/clarice.png +0 -0
- package/public/img/agents/dennis-nedry.png +0 -0
- package/public/img/agents/elliot-alderson.png +0 -0
- package/public/img/agents/main.png +0 -0
- package/public/img/agents/scotty.png +0 -0
- package/public/img/agents/top-flight-security.png +0 -0
- package/public/index.html +1083 -0
- package/public/js/agents-data.js +234 -0
- package/public/js/agents-ui.js +72 -0
- package/public/js/agents.js +1525 -0
- package/public/js/app.js +79 -0
- package/public/js/appearance-settings.js +111 -0
- package/public/js/artifacts.js +432 -0
- package/public/js/audio-queue.js +168 -0
- package/public/js/bootstrap.js +54 -0
- package/public/js/chat.js +1211 -0
- package/public/js/commands.js +581 -0
- package/public/js/connection-api.js +121 -0
- package/public/js/connection.js +1231 -0
- package/public/js/context-tracker.js +271 -0
- package/public/js/core.js +172 -0
- package/public/js/dashboard.js +452 -0
- package/public/js/developer.js +432 -0
- package/public/js/encryption.js +124 -0
- package/public/js/errors.js +122 -0
- package/public/js/event-bus.js +77 -0
- package/public/js/fetch-utils.js +171 -0
- package/public/js/file-handler.js +229 -0
- package/public/js/files.js +352 -0
- package/public/js/gateway-chat.js +538 -0
- package/public/js/logger.js +112 -0
- package/public/js/markdown.js +190 -0
- package/public/js/message-actions.js +431 -0
- package/public/js/message-renderer.js +288 -0
- package/public/js/missed-messages.js +235 -0
- package/public/js/mobile-debug.js +95 -0
- package/public/js/notifications.js +367 -0
- package/public/js/offline-queue.js +178 -0
- package/public/js/onboarding.js +543 -0
- package/public/js/panels.js +156 -0
- package/public/js/premium.js +412 -0
- package/public/js/realtime-voice.js +844 -0
- package/public/js/satellite-sync.js +256 -0
- package/public/js/satellite-ui.js +175 -0
- package/public/js/satellites.js +1516 -0
- package/public/js/settings.js +1087 -0
- package/public/js/shortcuts.js +381 -0
- package/public/js/split-chat.js +1234 -0
- package/public/js/split-resize.js +211 -0
- package/public/js/splitview.js +340 -0
- package/public/js/storage.js +408 -0
- package/public/js/streaming-handler.js +324 -0
- package/public/js/stt-settings.js +316 -0
- package/public/js/theme-generator.js +661 -0
- package/public/js/themes.js +164 -0
- package/public/js/timestamps.js +198 -0
- package/public/js/tts-settings.js +575 -0
- package/public/js/ui.js +267 -0
- package/public/js/update-notifier.js +143 -0
- package/public/js/utils/constants.js +165 -0
- package/public/js/utils/sanitize.js +93 -0
- package/public/js/utils/sse-parser.js +195 -0
- package/public/js/voice.js +883 -0
- package/public/manifest.json +58 -0
- package/public/moon_texture.jpg +0 -0
- package/public/sw.js +221 -0
- package/public/three.min.js +6 -0
- package/server/channel.js +529 -0
- package/server/chat.js +270 -0
- package/server/config-store.js +362 -0
- package/server/config.js +159 -0
- package/server/context.js +131 -0
- package/server/gateway-commands.js +211 -0
- package/server/gateway-proxy.js +318 -0
- package/server/index.js +22 -0
- package/server/logger.js +89 -0
- package/server/middleware/auth.js +188 -0
- package/server/middleware.js +218 -0
- package/server/openclaw-discover.js +308 -0
- package/server/premium/index.js +156 -0
- package/server/premium/license.js +140 -0
- package/server/realtime/bridge.js +837 -0
- package/server/realtime/index.js +349 -0
- package/server/realtime/tts-stream.js +446 -0
- package/server/routes/agents.js +564 -0
- package/server/routes/artifacts.js +174 -0
- package/server/routes/chat.js +311 -0
- package/server/routes/config-settings.js +345 -0
- package/server/routes/config.js +603 -0
- package/server/routes/files.js +307 -0
- package/server/routes/index.js +18 -0
- package/server/routes/media.js +451 -0
- package/server/routes/missed-messages.js +107 -0
- package/server/routes/premium.js +75 -0
- package/server/routes/push.js +156 -0
- package/server/routes/satellite.js +406 -0
- package/server/routes/status.js +251 -0
- package/server/routes/stt.js +35 -0
- package/server/routes/voice.js +260 -0
- package/server/routes/webhooks.js +203 -0
- package/server/routes.js +206 -0
- package/server/runtime-config.js +336 -0
- package/server/share.js +305 -0
- package/server/stt/faster-whisper.js +72 -0
- package/server/stt/groq.js +51 -0
- package/server/stt/index.js +196 -0
- package/server/stt/openai.js +49 -0
- package/server/sync.js +244 -0
- package/server/tailscale-https.js +175 -0
- package/server/tts.js +646 -0
- package/server/update-checker.js +172 -0
- package/server/utils/filename.js +129 -0
- package/server/utils.js +147 -0
- package/server/watchdog.js +318 -0
- package/server/websocket/broadcast.js +359 -0
- package/server/websocket/connections.js +339 -0
- package/server/websocket/index.js +215 -0
- package/server/websocket/routing.js +277 -0
- package/server/websocket/sync.js +102 -0
- package/server.js +404 -0
- package/utils/detect-tool-usage.js +93 -0
- package/utils/errors.js +158 -0
- package/utils/html-escape.js +84 -0
- package/utils/id-sanitize.js +94 -0
- package/utils/response.js +130 -0
- package/utils/with-retry.js +105 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Split View Drag Resize Handler
|
|
3
|
+
* Manages the drag handle for resizing split-view panes
|
|
4
|
+
* Targets .split-chat-container (the flex container inside .app)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { UplinkCore } from './core.js';
|
|
8
|
+
|
|
9
|
+
const STORAGE_KEY = 'uplink-split-ratio';
|
|
10
|
+
const MIN_RATIO = 0.3;
|
|
11
|
+
const MAX_RATIO = 0.7;
|
|
12
|
+
|
|
13
|
+
let isDragging = false;
|
|
14
|
+
let container = null;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Initialize drag resize functionality
|
|
18
|
+
*/
|
|
19
|
+
function init() {
|
|
20
|
+
const handle = document.getElementById('splitDragHandle');
|
|
21
|
+
container = document.getElementById('splitChatContainer');
|
|
22
|
+
|
|
23
|
+
if (!handle || !container) {
|
|
24
|
+
console.warn('[split-resize] Required elements not found');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Restore saved ratio
|
|
29
|
+
restoreRatio();
|
|
30
|
+
|
|
31
|
+
// Mouse events
|
|
32
|
+
handle.addEventListener('mousedown', handleMouseDown);
|
|
33
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
34
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
35
|
+
|
|
36
|
+
// Touch events for tablets
|
|
37
|
+
handle.addEventListener('touchstart', handleTouchStart, { passive: false });
|
|
38
|
+
document.addEventListener('touchmove', handleTouchMove, { passive: false });
|
|
39
|
+
document.addEventListener('touchend', handleTouchEnd);
|
|
40
|
+
|
|
41
|
+
// Keyboard accessibility (arrow keys to resize)
|
|
42
|
+
handle.addEventListener('keydown', handleKeyDown);
|
|
43
|
+
handle.setAttribute('tabindex', '0');
|
|
44
|
+
handle.setAttribute('role', 'separator');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Restore split ratio from localStorage
|
|
49
|
+
*/
|
|
50
|
+
function restoreRatio() {
|
|
51
|
+
try {
|
|
52
|
+
const savedRatio = localStorage.getItem(STORAGE_KEY);
|
|
53
|
+
if (savedRatio !== null) {
|
|
54
|
+
const ratio = parseFloat(savedRatio);
|
|
55
|
+
if (!isNaN(ratio) && ratio >= MIN_RATIO && ratio <= MAX_RATIO) {
|
|
56
|
+
setSplitRatio(ratio);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
} catch (err) {
|
|
60
|
+
console.warn('[split-resize] Failed to restore ratio:', err);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Save split ratio to localStorage
|
|
66
|
+
*/
|
|
67
|
+
function saveRatio(ratio) {
|
|
68
|
+
try {
|
|
69
|
+
localStorage.setItem(STORAGE_KEY, ratio.toString());
|
|
70
|
+
} catch (err) {
|
|
71
|
+
console.warn('[split-resize] Failed to save ratio:', err);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Set the split ratio CSS custom property on the container
|
|
77
|
+
*/
|
|
78
|
+
function setSplitRatio(ratio) {
|
|
79
|
+
const clamped = Math.max(MIN_RATIO, Math.min(MAX_RATIO, ratio));
|
|
80
|
+
if (container) {
|
|
81
|
+
container.style.setProperty('--split-ratio', clamped.toString());
|
|
82
|
+
}
|
|
83
|
+
// Also set on :root for any global references
|
|
84
|
+
document.documentElement.style.setProperty('--split-ratio', clamped.toString());
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Calculate ratio from mouse/touch X position relative to the container
|
|
89
|
+
*/
|
|
90
|
+
function calculateRatioFromX(clientX) {
|
|
91
|
+
if (!container) return 0.5;
|
|
92
|
+
const rect = container.getBoundingClientRect();
|
|
93
|
+
const x = clientX - rect.left;
|
|
94
|
+
return x / rect.width;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Mouse down handler
|
|
99
|
+
*/
|
|
100
|
+
function handleMouseDown(e) {
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
isDragging = true;
|
|
103
|
+
|
|
104
|
+
e.currentTarget.classList.add('dragging');
|
|
105
|
+
document.body.classList.add('split-dragging');
|
|
106
|
+
document.body.style.cursor = 'col-resize';
|
|
107
|
+
document.body.style.userSelect = 'none';
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Mouse move handler
|
|
112
|
+
*/
|
|
113
|
+
function handleMouseMove(e) {
|
|
114
|
+
if (!isDragging) return;
|
|
115
|
+
const ratio = calculateRatioFromX(e.clientX);
|
|
116
|
+
setSplitRatio(ratio);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Mouse up handler
|
|
121
|
+
*/
|
|
122
|
+
function handleMouseUp() {
|
|
123
|
+
if (!isDragging) return;
|
|
124
|
+
isDragging = false;
|
|
125
|
+
|
|
126
|
+
const handle = document.getElementById('splitDragHandle');
|
|
127
|
+
if (handle) handle.classList.remove('dragging');
|
|
128
|
+
document.body.classList.remove('split-dragging');
|
|
129
|
+
|
|
130
|
+
document.body.style.cursor = '';
|
|
131
|
+
document.body.style.userSelect = '';
|
|
132
|
+
|
|
133
|
+
// Save final ratio
|
|
134
|
+
const finalRatio = parseFloat(
|
|
135
|
+
getComputedStyle(container || document.documentElement).getPropertyValue('--split-ratio') || '0.5'
|
|
136
|
+
);
|
|
137
|
+
saveRatio(finalRatio);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Touch start handler
|
|
142
|
+
*/
|
|
143
|
+
function handleTouchStart(e) {
|
|
144
|
+
if (e.touches.length !== 1) return;
|
|
145
|
+
e.preventDefault();
|
|
146
|
+
isDragging = true;
|
|
147
|
+
e.currentTarget.classList.add('dragging');
|
|
148
|
+
document.body.classList.add('split-dragging');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Touch move handler
|
|
153
|
+
*/
|
|
154
|
+
function handleTouchMove(e) {
|
|
155
|
+
if (!isDragging || e.touches.length !== 1) return;
|
|
156
|
+
e.preventDefault();
|
|
157
|
+
const ratio = calculateRatioFromX(e.touches[0].clientX);
|
|
158
|
+
setSplitRatio(ratio);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Touch end handler
|
|
163
|
+
*/
|
|
164
|
+
function handleTouchEnd() {
|
|
165
|
+
if (!isDragging) return;
|
|
166
|
+
isDragging = false;
|
|
167
|
+
|
|
168
|
+
const handle = document.getElementById('splitDragHandle');
|
|
169
|
+
if (handle) handle.classList.remove('dragging');
|
|
170
|
+
document.body.classList.remove('split-dragging');
|
|
171
|
+
|
|
172
|
+
const finalRatio = parseFloat(
|
|
173
|
+
getComputedStyle(container || document.documentElement).getPropertyValue('--split-ratio') || '0.5'
|
|
174
|
+
);
|
|
175
|
+
saveRatio(finalRatio);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Keyboard handler (arrow keys to resize)
|
|
180
|
+
*/
|
|
181
|
+
function handleKeyDown(e) {
|
|
182
|
+
const currentRatio = parseFloat(
|
|
183
|
+
getComputedStyle(container || document.documentElement).getPropertyValue('--split-ratio') || '0.5'
|
|
184
|
+
);
|
|
185
|
+
let newRatio = currentRatio;
|
|
186
|
+
let handled = false;
|
|
187
|
+
|
|
188
|
+
switch(e.key) {
|
|
189
|
+
case 'ArrowLeft': newRatio -= 0.05; handled = true; break;
|
|
190
|
+
case 'ArrowRight': newRatio += 0.05; handled = true; break;
|
|
191
|
+
case 'Home': newRatio = MIN_RATIO; handled = true; break;
|
|
192
|
+
case 'End': newRatio = MAX_RATIO; handled = true; break;
|
|
193
|
+
case 'Enter':
|
|
194
|
+
case ' ': newRatio = 0.5; handled = true; break;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (handled) {
|
|
198
|
+
e.preventDefault();
|
|
199
|
+
setSplitRatio(newRatio);
|
|
200
|
+
saveRatio(newRatio);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Register with core for coordinated initialization
|
|
205
|
+
UplinkCore.registerModule('split-resize', init);
|
|
206
|
+
|
|
207
|
+
// Export for debugging
|
|
208
|
+
export const SplitResize = { setSplitRatio, saveRatio, restoreRatio };
|
|
209
|
+
|
|
210
|
+
// Backward compat: assign to window
|
|
211
|
+
window.SplitResize = SplitResize;
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// SPLIT VIEW - Desktop panel management
|
|
3
|
+
// ============================================
|
|
4
|
+
|
|
5
|
+
import { UplinkCore } from './core.js';
|
|
6
|
+
|
|
7
|
+
const DESKTOP_BREAKPOINT = 1024;
|
|
8
|
+
const MAX_INIT_RETRIES = 5;
|
|
9
|
+
const INIT_RETRY_DELAY_MS = 100;
|
|
10
|
+
let initRetryCount = 0;
|
|
11
|
+
|
|
12
|
+
let currentPanel = null;
|
|
13
|
+
let layoutWrapper = null;
|
|
14
|
+
let sidePanel = null;
|
|
15
|
+
let sidePanelTitle = null;
|
|
16
|
+
let sidePanelContent = null;
|
|
17
|
+
let sidePanelClose = null;
|
|
18
|
+
|
|
19
|
+
// Panel configurations - titles match Activity panel style (uppercase, no emoji)
|
|
20
|
+
const panels = {
|
|
21
|
+
satellites: {
|
|
22
|
+
title: 'SATELLITES',
|
|
23
|
+
getContent: () => document.querySelector('.satellite-navigator')
|
|
24
|
+
},
|
|
25
|
+
activity: {
|
|
26
|
+
title: 'ACTIVITY',
|
|
27
|
+
getContent: () => document.querySelector('.dev-panel')
|
|
28
|
+
},
|
|
29
|
+
settings: {
|
|
30
|
+
title: 'SETTINGS',
|
|
31
|
+
getContent: () => document.querySelector('.settings-panel')
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function isDesktop() {
|
|
36
|
+
return window.innerWidth >= DESKTOP_BREAKPOINT;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function init() {
|
|
40
|
+
layoutWrapper = document.querySelector('.layout-wrapper');
|
|
41
|
+
sidePanel = document.getElementById('sidePanel');
|
|
42
|
+
sidePanelTitle = document.getElementById('sidePanelTitle');
|
|
43
|
+
sidePanelContent = document.getElementById('sidePanelContent');
|
|
44
|
+
sidePanelClose = document.getElementById('sidePanelClose');
|
|
45
|
+
|
|
46
|
+
if (!layoutWrapper || !sidePanel) {
|
|
47
|
+
if (initRetryCount < MAX_INIT_RETRIES) {
|
|
48
|
+
initRetryCount++;
|
|
49
|
+
logger.warn(`Split view: Required elements not found, retrying (${initRetryCount}/${MAX_INIT_RETRIES})...`);
|
|
50
|
+
setTimeout(init, INIT_RETRY_DELAY_MS * initRetryCount);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
logger.warn('Split view: Required elements not found after max retries');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Close button handler
|
|
58
|
+
sidePanelClose?.addEventListener('click', closePanel);
|
|
59
|
+
|
|
60
|
+
// Escape key to close
|
|
61
|
+
document.addEventListener('keydown', (e) => {
|
|
62
|
+
if (e.key === 'Escape' && currentPanel) {
|
|
63
|
+
closePanel();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Handle resize - close panel if going to mobile
|
|
68
|
+
// Debounced to avoid excessive layout checks during resize
|
|
69
|
+
window.addEventListener('resize', UplinkCore.debounce(() => {
|
|
70
|
+
if (!isDesktop() && currentPanel) {
|
|
71
|
+
closePanel();
|
|
72
|
+
}
|
|
73
|
+
}, 150));
|
|
74
|
+
|
|
75
|
+
// Intercept panel button clicks
|
|
76
|
+
setupPanelInterceptors();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function setupPanelInterceptors() {
|
|
80
|
+
// Satellites button
|
|
81
|
+
const satellitesBtn = document.getElementById('satellitesBtn');
|
|
82
|
+
if (satellitesBtn) {
|
|
83
|
+
satellitesBtn.addEventListener('click', (e) => {
|
|
84
|
+
if (isDesktop()) {
|
|
85
|
+
e.stopPropagation();
|
|
86
|
+
e.stopImmediatePropagation();
|
|
87
|
+
togglePanel('satellites');
|
|
88
|
+
}
|
|
89
|
+
}, true); // Capture phase to intercept before other handlers
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Activity button
|
|
93
|
+
const activityBtn = document.getElementById('activityBtn');
|
|
94
|
+
if (activityBtn) {
|
|
95
|
+
activityBtn.addEventListener('click', (e) => {
|
|
96
|
+
if (isDesktop()) {
|
|
97
|
+
e.stopPropagation();
|
|
98
|
+
e.stopImmediatePropagation();
|
|
99
|
+
togglePanel('activity');
|
|
100
|
+
}
|
|
101
|
+
}, true);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Settings button
|
|
105
|
+
const settingsBtn = document.getElementById('settingsBtn');
|
|
106
|
+
if (settingsBtn) {
|
|
107
|
+
settingsBtn.addEventListener('click', (e) => {
|
|
108
|
+
if (isDesktop()) {
|
|
109
|
+
e.stopPropagation();
|
|
110
|
+
e.stopImmediatePropagation();
|
|
111
|
+
togglePanel('settings');
|
|
112
|
+
}
|
|
113
|
+
}, true);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function togglePanel(panelName) {
|
|
118
|
+
console.log('[SplitView] togglePanel called, panelName:', panelName, 'currentPanel:', currentPanel);
|
|
119
|
+
if (currentPanel === panelName) {
|
|
120
|
+
closePanel();
|
|
121
|
+
} else {
|
|
122
|
+
openPanel(panelName);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function openPanel(panelName) {
|
|
127
|
+
const config = panels[panelName];
|
|
128
|
+
if (!config) return;
|
|
129
|
+
|
|
130
|
+
// Close any existing panel first
|
|
131
|
+
if (currentPanel && currentPanel !== panelName) {
|
|
132
|
+
restorePanelContent(currentPanel);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
currentPanel = panelName;
|
|
136
|
+
|
|
137
|
+
// Update title
|
|
138
|
+
sidePanelTitle.textContent = config.title;
|
|
139
|
+
|
|
140
|
+
// Get the panel content element
|
|
141
|
+
// For dynamically created panels, we may need to wait or trigger their creation
|
|
142
|
+
let contentEl = config.getContent();
|
|
143
|
+
|
|
144
|
+
if (contentEl) {
|
|
145
|
+
// Move content into side panel (CSS handles the styling override)
|
|
146
|
+
sidePanelContent.innerHTML = '';
|
|
147
|
+
sidePanelContent.appendChild(contentEl);
|
|
148
|
+
// Ensure it's visible
|
|
149
|
+
contentEl.classList.add('visible');
|
|
150
|
+
} else {
|
|
151
|
+
// Panel not created yet - trigger its creation
|
|
152
|
+
triggerPanelCreation(panelName);
|
|
153
|
+
// Try again after a short delay
|
|
154
|
+
setTimeout(() => {
|
|
155
|
+
contentEl = config.getContent();
|
|
156
|
+
if (contentEl) {
|
|
157
|
+
sidePanelContent.innerHTML = '';
|
|
158
|
+
sidePanelContent.appendChild(contentEl);
|
|
159
|
+
contentEl.classList.add('visible');
|
|
160
|
+
}
|
|
161
|
+
}, 50);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Add class to activate split layout (body for CSS 70/30 split)
|
|
165
|
+
document.body.classList.add('panel-open');
|
|
166
|
+
layoutWrapper.classList.add('panel-open');
|
|
167
|
+
sidePanel.classList.add('visible');
|
|
168
|
+
|
|
169
|
+
// Update button states
|
|
170
|
+
updateButtonStates(panelName);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function closePanel() {
|
|
174
|
+
console.log('[SplitView] closePanel called, currentPanel:', currentPanel);
|
|
175
|
+
console.trace('[SplitView] closePanel stack trace');
|
|
176
|
+
if (!currentPanel) return;
|
|
177
|
+
|
|
178
|
+
restorePanelContent(currentPanel);
|
|
179
|
+
|
|
180
|
+
document.body.classList.remove('panel-open');
|
|
181
|
+
layoutWrapper.classList.remove('panel-open');
|
|
182
|
+
sidePanel.classList.remove('visible');
|
|
183
|
+
|
|
184
|
+
updateButtonStates(null);
|
|
185
|
+
currentPanel = null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function restorePanelContent(panelName) {
|
|
189
|
+
const config = panels[panelName];
|
|
190
|
+
if (!config) return;
|
|
191
|
+
|
|
192
|
+
const contentEl = config.getContent();
|
|
193
|
+
if (contentEl && sidePanelContent.contains(contentEl)) {
|
|
194
|
+
// Move back to body and hide
|
|
195
|
+
document.body.appendChild(contentEl);
|
|
196
|
+
contentEl.classList.remove('visible');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function triggerPanelCreation(panelName) {
|
|
201
|
+
// Dispatch a custom event that the panel modules can listen to
|
|
202
|
+
// or directly call their init functions if available
|
|
203
|
+
if (panelName === 'satellites' && window.UplinkSatellites) {
|
|
204
|
+
window.UplinkSatellites.toggleNavigator();
|
|
205
|
+
} else if (panelName === 'activity' && window.UplinkDeveloper) {
|
|
206
|
+
window.UplinkDeveloper.show();
|
|
207
|
+
} else if (panelName === 'settings') {
|
|
208
|
+
// Settings panel is static in HTML, just show it
|
|
209
|
+
const settingsPanel = document.getElementById('settingsPanel');
|
|
210
|
+
if (settingsPanel) {
|
|
211
|
+
settingsPanel.classList.add('visible');
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function updateButtonStates(activePanel) {
|
|
217
|
+
const buttons = {
|
|
218
|
+
satellites: document.getElementById('satellitesBtn'),
|
|
219
|
+
activity: document.getElementById('activityBtn'),
|
|
220
|
+
settings: document.getElementById('settingsBtn')
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
Object.entries(buttons).forEach(([name, btn]) => {
|
|
224
|
+
if (btn) {
|
|
225
|
+
btn.classList.toggle('active', name === activePanel);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ============================================
|
|
231
|
+
// DUAL CHAT (SPLIT VIEW)
|
|
232
|
+
// ============================================
|
|
233
|
+
|
|
234
|
+
let splitActive = false;
|
|
235
|
+
|
|
236
|
+
function setupSplitViewButton() {
|
|
237
|
+
const splitBtn = document.getElementById('splitViewBtn');
|
|
238
|
+
if (!splitBtn) return;
|
|
239
|
+
|
|
240
|
+
splitBtn.addEventListener('click', () => {
|
|
241
|
+
if (!isDesktop()) return;
|
|
242
|
+
toggleSplitView();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Close button in secondary pane
|
|
246
|
+
const closeBtn = document.getElementById('splitSecondaryClose');
|
|
247
|
+
if (closeBtn) {
|
|
248
|
+
closeBtn.addEventListener('click', () => closeSplitView());
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Switch session button listener is handled by split-chat.js
|
|
252
|
+
// (removed duplicate listener to prevent double-firing)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function toggleSplitView() {
|
|
256
|
+
if (splitActive) {
|
|
257
|
+
closeSplitView();
|
|
258
|
+
} else {
|
|
259
|
+
openSplitView();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function openSplitView() {
|
|
264
|
+
if (!isDesktop()) return;
|
|
265
|
+
|
|
266
|
+
splitActive = true;
|
|
267
|
+
document.body.classList.add('split-view-active');
|
|
268
|
+
|
|
269
|
+
// Show the secondary pane and drag handle
|
|
270
|
+
const secondary = document.getElementById('splitSecondary');
|
|
271
|
+
const dragHandle = document.getElementById('splitDragHandle');
|
|
272
|
+
if (secondary) secondary.style.display = 'flex';
|
|
273
|
+
if (dragHandle) dragHandle.style.display = 'block';
|
|
274
|
+
|
|
275
|
+
// Update button state
|
|
276
|
+
const splitBtn = document.getElementById('splitViewBtn');
|
|
277
|
+
if (splitBtn) splitBtn.classList.add('active');
|
|
278
|
+
|
|
279
|
+
// Show the session picker if no session is active
|
|
280
|
+
if (window.UplinkSplitChat && !window.UplinkSplitChat.isActive()) {
|
|
281
|
+
window.UplinkSplitChat.showPicker();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function closeSplitView() {
|
|
286
|
+
splitActive = false;
|
|
287
|
+
document.body.classList.remove('split-view-active');
|
|
288
|
+
|
|
289
|
+
// Hide the secondary pane and drag handle
|
|
290
|
+
const secondary = document.getElementById('splitSecondary');
|
|
291
|
+
const dragHandle = document.getElementById('splitDragHandle');
|
|
292
|
+
if (secondary) secondary.style.display = 'none';
|
|
293
|
+
if (dragHandle) dragHandle.style.display = 'none';
|
|
294
|
+
|
|
295
|
+
// Update button state
|
|
296
|
+
const splitBtn = document.getElementById('splitViewBtn');
|
|
297
|
+
if (splitBtn) splitBtn.classList.remove('active');
|
|
298
|
+
|
|
299
|
+
// Close the secondary session
|
|
300
|
+
if (window.UplinkSplitChat) {
|
|
301
|
+
window.UplinkSplitChat.closeSession();
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function isSplitActive() {
|
|
306
|
+
return splitActive;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Combined init: core init + split view button setup
|
|
310
|
+
function initWithSplitView() {
|
|
311
|
+
init();
|
|
312
|
+
setupSplitViewButton();
|
|
313
|
+
|
|
314
|
+
window.addEventListener('resize', UplinkCore.debounce(() => {
|
|
315
|
+
if (!isDesktop() && splitActive) {
|
|
316
|
+
closeSplitView();
|
|
317
|
+
}
|
|
318
|
+
}, 150));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Expose for debugging
|
|
322
|
+
export const UplinkSplitView = {
|
|
323
|
+
openPanel,
|
|
324
|
+
closePanel,
|
|
325
|
+
togglePanel,
|
|
326
|
+
isDesktop,
|
|
327
|
+
getCurrentPanel: () => currentPanel,
|
|
328
|
+
// Dual chat
|
|
329
|
+
openSplitView,
|
|
330
|
+
closeSplitView,
|
|
331
|
+
toggleSplitView,
|
|
332
|
+
isSplitActive
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
// Backward compat: assign to window
|
|
336
|
+
window.UplinkSplitView = UplinkSplitView;
|
|
337
|
+
|
|
338
|
+
// Register with core for coordinated initialization
|
|
339
|
+
UplinkCore.registerModule('splitview', initWithSplitView);
|
|
340
|
+
|