vg-coder-cli 2.0.33 → 2.0.35
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/README.md +1 -0
- package/dist/vg-coder-bundle.js +3 -3
- package/package.json +2 -3
- package/src/server/api-server.js +111 -0
- package/src/server/views/css/commands-panel.css +75 -0
- package/src/server/views/css/editor.css +1 -1
- package/src/server/views/css/git-panel.css +361 -0
- package/src/server/views/css/git-view.css +1 -77
- package/src/server/views/css/project-panel.css +242 -0
- package/src/server/views/css/tool-window.css +267 -0
- package/src/server/views/dashboard.css +1 -1
- package/src/server/views/dashboard.html +59 -102
- package/src/server/views/js/api.js +13 -0
- package/src/server/views/js/event-protocol.js +1 -0
- package/src/server/views/js/features/bubble-features/copy-prompt-feature.js +14 -0
- package/src/server/views/js/features/bubble-features/index.js +6 -1
- package/src/server/views/js/features/commands-panel.js +63 -0
- package/src/server/views/js/features/git-panel.js +481 -0
- package/src/server/views/js/features/git-view.js +79 -307
- package/src/server/views/js/features/project-panel.js +452 -0
- package/src/server/views/js/features/resize.js +7 -10
- package/src/server/views/js/features/tool-window.js +148 -0
- package/src/server/views/js/handlers.js +11 -0
- package/src/server/views/js/main.js +13 -31
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import { getStructure, analyzeProject, copyToClipboard, saveTreeState as apiSaveTreeState, loadTreeState as apiLoadTreeState } from '../api.js';
|
|
2
|
+
import { showToast, formatNumber, getById, qsa } from '../utils.js';
|
|
3
|
+
import { API_BASE } from '../config.js';
|
|
4
|
+
import { switchProject } from './project-switcher.js';
|
|
5
|
+
|
|
6
|
+
let currentStructureData = null;
|
|
7
|
+
let excludedPaths = new Set();
|
|
8
|
+
let saveStateTimeout = null;
|
|
9
|
+
let isInitialized = false;
|
|
10
|
+
|
|
11
|
+
// Expose switchProject to window for selector onChange
|
|
12
|
+
window.switchProject = switchProject;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Initialize Project Panel
|
|
16
|
+
*/
|
|
17
|
+
export function initProjectPanel() {
|
|
18
|
+
// Listen for panel open events
|
|
19
|
+
document.addEventListener('tool-panel-opened', (event) => {
|
|
20
|
+
if (event.detail.panelId === 'project') {
|
|
21
|
+
if (!isInitialized) {
|
|
22
|
+
renderProjectPanel();
|
|
23
|
+
loadProjectTree();
|
|
24
|
+
isInitialized = true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Listen for project changes to update selector AND reload tree
|
|
30
|
+
window.addEventListener('project-switched', (e) => {
|
|
31
|
+
console.log('[ProjectPanel] 🔄 Project switched event detected:', e.detail);
|
|
32
|
+
|
|
33
|
+
// Update selector to reflect new project
|
|
34
|
+
loadProjectsIntoSelector();
|
|
35
|
+
|
|
36
|
+
// Reload project tree for new project
|
|
37
|
+
if (isInitialized) {
|
|
38
|
+
console.log('[ProjectPanel] Reloading project tree for new project...');
|
|
39
|
+
loadProjectTree();
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Poll for project updates every 5 seconds (since socket.io may not be available in shadow DOM)
|
|
44
|
+
setInterval(() => {
|
|
45
|
+
if (getById('project-panel-selector')) {
|
|
46
|
+
loadProjectsIntoSelector();
|
|
47
|
+
}
|
|
48
|
+
}, 5000);
|
|
49
|
+
|
|
50
|
+
console.log('[ProjectPanel] Initialized with polling (Socket.IO not available in shadow DOM)');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Render the Project Panel UI structure
|
|
55
|
+
*/
|
|
56
|
+
function renderProjectPanel() {
|
|
57
|
+
const container = getById('project-panel-content');
|
|
58
|
+
if (!container) return;
|
|
59
|
+
|
|
60
|
+
// Get current project from main selector (if exists)
|
|
61
|
+
const mainProjectSelector = getById('project-selector');
|
|
62
|
+
const currentProject = mainProjectSelector ? mainProjectSelector.value : '';
|
|
63
|
+
|
|
64
|
+
container.innerHTML = `
|
|
65
|
+
<div class="project-panel-selector-wrapper">
|
|
66
|
+
<select id="project-panel-selector" class="project-panel-selector" title="Switch Project">
|
|
67
|
+
<option value="">Loading projects...</option>
|
|
68
|
+
</select>
|
|
69
|
+
</div>
|
|
70
|
+
<div class="project-panel-actions-bar">
|
|
71
|
+
<button class="project-action-btn" id="project-refresh-btn" title="Refresh Project Tree">
|
|
72
|
+
<span>🔄</span>
|
|
73
|
+
<span>Refresh</span>
|
|
74
|
+
</button>
|
|
75
|
+
<button class="project-action-btn" id="project-copy-selected-btn" title="Copy Selected Files">
|
|
76
|
+
<span>📋</span>
|
|
77
|
+
<span>Copy</span>
|
|
78
|
+
</button>
|
|
79
|
+
</div>
|
|
80
|
+
<div class="project-token-summary" id="project-token-summary">
|
|
81
|
+
<span>Selected:</span>
|
|
82
|
+
<span class="project-token-count" id="project-token-count">0 tokens</span>
|
|
83
|
+
</div>
|
|
84
|
+
<div class="project-tree-wrapper" id="project-tree-wrapper">
|
|
85
|
+
<div class="project-loading">Loading project structure...</div>
|
|
86
|
+
</div>
|
|
87
|
+
`;
|
|
88
|
+
|
|
89
|
+
// Load projects into selector
|
|
90
|
+
loadProjectsIntoSelector();
|
|
91
|
+
|
|
92
|
+
// Attach event listeners
|
|
93
|
+
const refreshBtn = getById('project-refresh-btn');
|
|
94
|
+
const copyBtn = getById('project-copy-selected-btn');
|
|
95
|
+
const projectSelector = getById('project-panel-selector');
|
|
96
|
+
|
|
97
|
+
if (refreshBtn) {
|
|
98
|
+
refreshBtn.addEventListener('click', loadProjectTree);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (copyBtn) {
|
|
102
|
+
copyBtn.addEventListener('click', handleCopySelected);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (projectSelector) {
|
|
106
|
+
console.log('[ProjectPanel] Attaching change listener to project selector');
|
|
107
|
+
projectSelector.addEventListener('change', (e) => {
|
|
108
|
+
const projectId = e.target.value;
|
|
109
|
+
console.log('[ProjectPanel] Project selector changed to:', projectId);
|
|
110
|
+
// Sync with main project selector
|
|
111
|
+
if (window.switchProject) {
|
|
112
|
+
window.switchProject(projectId);
|
|
113
|
+
} else {
|
|
114
|
+
console.warn('[ProjectPanel] window.switchProject not available');
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Load projects into the panel selector
|
|
122
|
+
*/
|
|
123
|
+
async function loadProjectsIntoSelector() {
|
|
124
|
+
const selector = getById('project-panel-selector');
|
|
125
|
+
console.log('[ProjectPanel] loadProjectsIntoSelector called, selector exists:', !!selector);
|
|
126
|
+
|
|
127
|
+
if (!selector) {
|
|
128
|
+
console.warn('[ProjectPanel] Project selector not found in DOM');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Save current selection to preserve it
|
|
133
|
+
const currentValue = selector.value;
|
|
134
|
+
console.log('[ProjectPanel] Current selected project:', currentValue);
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
const apiBase = window.API_BASE || 'http://localhost:6868';
|
|
138
|
+
console.log('[ProjectPanel] Fetching projects from:', `${apiBase}/api/projects`);
|
|
139
|
+
|
|
140
|
+
const response = await fetch(`${apiBase}/api/projects`);
|
|
141
|
+
const data = await response.json();
|
|
142
|
+
|
|
143
|
+
console.log('[ProjectPanel] Received projects:', data);
|
|
144
|
+
|
|
145
|
+
if (data.projects && data.projects.length > 0) {
|
|
146
|
+
const options = data.projects.map(p =>
|
|
147
|
+
`<option value="${p.id}" ${p.isActive ? 'selected' : ''}>${p.name}</option>`
|
|
148
|
+
).join('');
|
|
149
|
+
|
|
150
|
+
console.log('[ProjectPanel] Updating selector with', data.projects.length, 'projects');
|
|
151
|
+
selector.innerHTML = options;
|
|
152
|
+
|
|
153
|
+
// IMPORTANT: Restore previous selection if it still exists
|
|
154
|
+
// This prevents auto-switching when new projects join
|
|
155
|
+
if (currentValue && data.projects.some(p => p.id === currentValue)) {
|
|
156
|
+
selector.value = currentValue;
|
|
157
|
+
console.log('[ProjectPanel] ✅ Restored previous selection:', currentValue);
|
|
158
|
+
} else {
|
|
159
|
+
console.log('[ProjectPanel] ℹ️ Using active project from API:', data.activeProjectId);
|
|
160
|
+
}
|
|
161
|
+
} else {
|
|
162
|
+
console.warn('[ProjectPanel] No projects returned from API');
|
|
163
|
+
selector.innerHTML = '<option value="">No projects</option>';
|
|
164
|
+
}
|
|
165
|
+
} catch (err) {
|
|
166
|
+
console.error('[ProjectPanel] Failed to load projects:', err);
|
|
167
|
+
selector.innerHTML = '<option value="">Error loading projects</option>';
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Load and render project tree
|
|
175
|
+
*/
|
|
176
|
+
async function loadProjectTree() {
|
|
177
|
+
const wrapper = getById('project-tree-wrapper');
|
|
178
|
+
if (!wrapper) return;
|
|
179
|
+
|
|
180
|
+
wrapper.innerHTML = '<div class="project-loading">Loading project structure...</div>';
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
// Get the current project path from the structure-path input (if exists) or use default
|
|
184
|
+
const pathInput = getById('structure-path');
|
|
185
|
+
const path = pathInput ? pathInput.value : '.';
|
|
186
|
+
|
|
187
|
+
const data = await getStructure(path);
|
|
188
|
+
currentStructureData = data.structure;
|
|
189
|
+
|
|
190
|
+
// Load saved state
|
|
191
|
+
await loadTreeState();
|
|
192
|
+
|
|
193
|
+
// Render tree
|
|
194
|
+
const treeHtml = generateProjectTree(currentStructureData);
|
|
195
|
+
wrapper.innerHTML = `<ul class="project-tree-ul">${treeHtml}</ul>`;
|
|
196
|
+
|
|
197
|
+
// IMPORTANT: Re-attach event listeners after rendering
|
|
198
|
+
attachProjectTreeListeners();
|
|
199
|
+
|
|
200
|
+
// Update token count
|
|
201
|
+
updateTokenCount();
|
|
202
|
+
|
|
203
|
+
showToast('Project tree loaded', 'success');
|
|
204
|
+
} catch (err) {
|
|
205
|
+
wrapper.innerHTML = `<div class="project-empty-state">Error loading project: ${err.message}</div>`;
|
|
206
|
+
showToast('Error loading project: ' + err.message, 'error');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Generate project tree HTML
|
|
212
|
+
*/
|
|
213
|
+
function generateProjectTree(node, depth = 0) {
|
|
214
|
+
if (!node) return '';
|
|
215
|
+
|
|
216
|
+
// Handle compact folders (merge single-child directories)
|
|
217
|
+
let currentNode = node;
|
|
218
|
+
let displayName = node.name;
|
|
219
|
+
|
|
220
|
+
if (currentNode.type === 'directory') {
|
|
221
|
+
while (
|
|
222
|
+
currentNode.children &&
|
|
223
|
+
currentNode.children.length === 1 &&
|
|
224
|
+
currentNode.children[0].type === 'directory'
|
|
225
|
+
) {
|
|
226
|
+
const child = currentNode.children[0];
|
|
227
|
+
displayName += '/' + child.name;
|
|
228
|
+
currentNode = child;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const isDir = currentNode.type === 'directory';
|
|
233
|
+
const hasChildren = isDir && currentNode.children && currentNode.children.length > 0;
|
|
234
|
+
const tokens = currentNode.tokens || 0;
|
|
235
|
+
|
|
236
|
+
let tokenClass = 'project-token-low';
|
|
237
|
+
if (tokens > 5000) tokenClass = 'project-token-high';
|
|
238
|
+
else if (tokens > 2000) tokenClass = 'project-token-med';
|
|
239
|
+
|
|
240
|
+
const icon = isDir ? '📁' : '📄';
|
|
241
|
+
const arrow = hasChildren ? '▼' : '';
|
|
242
|
+
const liClass = `project-tree-li ${hasChildren ? 'has-children' : ''}`;
|
|
243
|
+
|
|
244
|
+
const nodePath = currentNode.relativePath || currentNode.path;
|
|
245
|
+
const isExcluded = excludedPaths.has(nodePath);
|
|
246
|
+
|
|
247
|
+
let html = `<li class="${liClass}">`;
|
|
248
|
+
|
|
249
|
+
html += `
|
|
250
|
+
<div class="project-tree-row" data-path="${nodePath}" data-type="${currentNode.type}">
|
|
251
|
+
<span class="project-arrow">${arrow}</span>
|
|
252
|
+
<input type="checkbox" class="project-checkbox"
|
|
253
|
+
data-path="${nodePath}"
|
|
254
|
+
data-tokens="${tokens}"
|
|
255
|
+
data-type="${currentNode.type}"
|
|
256
|
+
${isExcluded ? '' : 'checked'}>
|
|
257
|
+
<span class="project-icon">${icon}</span>
|
|
258
|
+
<span class="project-name" title="${displayName}">${displayName}</span>
|
|
259
|
+
<span class="project-token-badge ${tokenClass}">${formatNumber(tokens)}</span>
|
|
260
|
+
</div>
|
|
261
|
+
`;
|
|
262
|
+
|
|
263
|
+
if (hasChildren) {
|
|
264
|
+
html += '<ul class="project-tree-ul">';
|
|
265
|
+
currentNode.children.forEach(child => {
|
|
266
|
+
html += generateProjectTree(child, depth + 1);
|
|
267
|
+
});
|
|
268
|
+
html += '</ul>';
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
html += '</li>';
|
|
272
|
+
return html;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Handle tree interactions after render
|
|
277
|
+
*/
|
|
278
|
+
export function attachProjectTreeListeners() {
|
|
279
|
+
const wrapper = getById('project-tree-wrapper');
|
|
280
|
+
if (!wrapper) return;
|
|
281
|
+
|
|
282
|
+
// Delegate events
|
|
283
|
+
wrapper.addEventListener('click', (e) => {
|
|
284
|
+
const row = e.target.closest('.project-tree-row');
|
|
285
|
+
if (!row) return;
|
|
286
|
+
|
|
287
|
+
// If clicking checkbox, don't toggle folder
|
|
288
|
+
if (e.target.classList.contains('project-checkbox')) {
|
|
289
|
+
handleCheckboxChange(e);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const type = row.dataset.type;
|
|
294
|
+
const path = row.dataset.path;
|
|
295
|
+
|
|
296
|
+
if (type === 'directory') {
|
|
297
|
+
console.log('[ProjectPanel] Toggling directory:', path);
|
|
298
|
+
// Toggle folder
|
|
299
|
+
const li = row.closest('.project-tree-li');
|
|
300
|
+
if (li && li.classList.contains('has-children')) {
|
|
301
|
+
li.classList.toggle('collapsed');
|
|
302
|
+
}
|
|
303
|
+
} else {
|
|
304
|
+
console.log('[ProjectPanel] 🔥 FILE CLICKED:', path);
|
|
305
|
+
// Open file in editor
|
|
306
|
+
const fileName = row.querySelector('.project-name').textContent;
|
|
307
|
+
console.log('[ProjectPanel] File name:', fileName);
|
|
308
|
+
console.log('[ProjectPanel] window.openFileTab exists:', typeof window.openFileTab !== 'undefined');
|
|
309
|
+
|
|
310
|
+
if (window.openFileTab) {
|
|
311
|
+
console.log('[ProjectPanel] Opening file tab:', path, fileName);
|
|
312
|
+
window.openFileTab(path, fileName);
|
|
313
|
+
} else {
|
|
314
|
+
console.error('[ProjectPanel] window.openFileTab is not defined!');
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Highlight selected row
|
|
318
|
+
qsa('.project-tree-row').forEach(r => r.classList.remove('selected'));
|
|
319
|
+
row.classList.add('selected');
|
|
320
|
+
console.log('[ProjectPanel] Row highlighted');
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// Checkbox changes
|
|
325
|
+
wrapper.addEventListener('change', (e) => {
|
|
326
|
+
if (e.target.classList.contains('project-checkbox')) {
|
|
327
|
+
handleCheckboxChange(e);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Handle checkbox state change
|
|
334
|
+
*/
|
|
335
|
+
function handleCheckboxChange(event) {
|
|
336
|
+
const checkbox = event.target;
|
|
337
|
+
const isChecked = checkbox.checked;
|
|
338
|
+
const path = checkbox.dataset.path;
|
|
339
|
+
|
|
340
|
+
if (isChecked) {
|
|
341
|
+
excludedPaths.delete(path);
|
|
342
|
+
} else {
|
|
343
|
+
excludedPaths.add(path);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Update children
|
|
347
|
+
const li = checkbox.closest('.project-tree-li');
|
|
348
|
+
if (li) {
|
|
349
|
+
const childCheckboxes = li.querySelectorAll('.project-checkbox');
|
|
350
|
+
childCheckboxes.forEach(child => {
|
|
351
|
+
child.checked = isChecked;
|
|
352
|
+
const childPath = child.dataset.path;
|
|
353
|
+
if (isChecked) {
|
|
354
|
+
excludedPaths.delete(childPath);
|
|
355
|
+
} else {
|
|
356
|
+
excludedPaths.add(childPath);
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
updateTokenCount();
|
|
362
|
+
debouncedSaveState();
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Update token count summary
|
|
367
|
+
*/
|
|
368
|
+
function updateTokenCount() {
|
|
369
|
+
const checkedFiles = qsa('.project-checkbox[data-type="file"]:checked');
|
|
370
|
+
let total = 0;
|
|
371
|
+
checkedFiles.forEach(box => {
|
|
372
|
+
const tokens = parseInt(box.dataset.tokens || '0');
|
|
373
|
+
total += tokens;
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
const countEl = getById('project-token-count');
|
|
377
|
+
if (countEl) {
|
|
378
|
+
countEl.textContent = `${formatNumber(total)} tokens`;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Handle copy selected files
|
|
384
|
+
*/
|
|
385
|
+
async function handleCopySelected() {
|
|
386
|
+
const checkedBoxes = qsa('.project-checkbox[data-type="file"]:checked');
|
|
387
|
+
const checkedPaths = [];
|
|
388
|
+
|
|
389
|
+
checkedBoxes.forEach(box => {
|
|
390
|
+
if (box.dataset.path) checkedPaths.push(box.dataset.path);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
if (checkedPaths.length === 0) {
|
|
394
|
+
showToast('No files selected', 'error');
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const btn = getById('project-copy-selected-btn');
|
|
399
|
+
if (btn) btn.disabled = true;
|
|
400
|
+
|
|
401
|
+
try {
|
|
402
|
+
const pathInput = getById('structure-path');
|
|
403
|
+
const path = pathInput ? pathInput.value : '.';
|
|
404
|
+
|
|
405
|
+
const content = await analyzeProject(path, checkedPaths);
|
|
406
|
+
await copyToClipboard(content);
|
|
407
|
+
|
|
408
|
+
showToast(`Copied ${checkedPaths.length} files to clipboard!`, 'success');
|
|
409
|
+
} catch (err) {
|
|
410
|
+
showToast('Copy error: ' + err.message, 'error');
|
|
411
|
+
} finally {
|
|
412
|
+
if (btn) btn.disabled = false;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Load saved tree state
|
|
418
|
+
*/
|
|
419
|
+
async function loadTreeState() {
|
|
420
|
+
try {
|
|
421
|
+
const data = await apiLoadTreeState();
|
|
422
|
+
excludedPaths = new Set(data.excludedPaths || []);
|
|
423
|
+
} catch (err) {
|
|
424
|
+
console.error('[ProjectPanel] Failed to load tree state:', err);
|
|
425
|
+
excludedPaths = new Set();
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Debounced save state
|
|
431
|
+
*/
|
|
432
|
+
function debouncedSaveState() {
|
|
433
|
+
if (saveStateTimeout) clearTimeout(saveStateTimeout);
|
|
434
|
+
saveStateTimeout = setTimeout(() => {
|
|
435
|
+
saveTreeState();
|
|
436
|
+
}, 500);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Save tree state
|
|
441
|
+
*/
|
|
442
|
+
async function saveTreeState() {
|
|
443
|
+
try {
|
|
444
|
+
const excludedArray = Array.from(excludedPaths);
|
|
445
|
+
await apiSaveTreeState(excludedArray);
|
|
446
|
+
} catch (err) {
|
|
447
|
+
console.error('[ProjectPanel] Failed to save tree state:', err);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Export loadProjectTree wrapped with listener attachment
|
|
452
|
+
export { loadProjectTree };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { qs, getById } from '../utils.js';
|
|
2
2
|
|
|
3
3
|
export function initResizeHandler() {
|
|
4
|
-
const
|
|
4
|
+
const toolPanelContainer = getById('tool-panel-container');
|
|
5
5
|
const handle = getById('resize-handler');
|
|
6
6
|
|
|
7
|
-
if (!
|
|
7
|
+
if (!toolPanelContainer || !handle) {
|
|
8
8
|
return;
|
|
9
9
|
}
|
|
10
10
|
|
|
@@ -15,7 +15,7 @@ export function initResizeHandler() {
|
|
|
15
15
|
handle.addEventListener('mousedown', (e) => {
|
|
16
16
|
isResizing = true;
|
|
17
17
|
startX = e.clientX;
|
|
18
|
-
startWidth =
|
|
18
|
+
startWidth = toolPanelContainer.getBoundingClientRect().width;
|
|
19
19
|
|
|
20
20
|
document.body.classList.add('resizing');
|
|
21
21
|
e.preventDefault();
|
|
@@ -27,13 +27,10 @@ export function initResizeHandler() {
|
|
|
27
27
|
requestAnimationFrame(() => {
|
|
28
28
|
const currentX = e.clientX;
|
|
29
29
|
const diffX = currentX - startX;
|
|
30
|
-
const newWidth = Math.max(250, startWidth + diffX);
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
leftPanel.style.flex = `0 0 ${newWidth}px`;
|
|
35
|
-
leftPanel.style.width = `${newWidth}px`;
|
|
36
|
-
}
|
|
30
|
+
const newWidth = Math.max(250, Math.min(600, startWidth + diffX)); // Min 250px, Max 600px
|
|
31
|
+
|
|
32
|
+
// Update width of tool-panel-container
|
|
33
|
+
toolPanelContainer.style.width = `${newWidth}px`;
|
|
37
34
|
});
|
|
38
35
|
});
|
|
39
36
|
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { getById, qsa } from '../utils.js';
|
|
2
|
+
|
|
3
|
+
let activePanel = null;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Initialize Tool Window system
|
|
7
|
+
*/
|
|
8
|
+
export function initToolWindow() {
|
|
9
|
+
const toolWindowBar = getById('tool-window-bar');
|
|
10
|
+
if (!toolWindowBar) {
|
|
11
|
+
console.warn('[ToolWindow] Tool window bar not found');
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Attach event listeners to all tool window icons
|
|
16
|
+
const icons = qsa('.tool-window-icon');
|
|
17
|
+
icons.forEach(icon => {
|
|
18
|
+
icon.addEventListener('click', () => {
|
|
19
|
+
const panelId = icon.dataset.panel;
|
|
20
|
+
if (panelId) {
|
|
21
|
+
togglePanel(panelId);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
console.log('[ToolWindow] Initialized');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Toggle a specific panel
|
|
31
|
+
* @param {string} panelId - Panel ID to toggle (e.g., 'project', 'git')
|
|
32
|
+
*/
|
|
33
|
+
export function togglePanel(panelId) {
|
|
34
|
+
const panel = getById(`tool-panel-${panelId}`);
|
|
35
|
+
const container = getById('tool-panel-container');
|
|
36
|
+
const icon = qsa(`.tool-window-icon[data-panel="${panelId}"]`)[0];
|
|
37
|
+
|
|
38
|
+
if (!panel || !container) {
|
|
39
|
+
console.error('[ToolWindow] Panel or container not found:', panelId);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// If clicking the same active panel, close it
|
|
44
|
+
if (activePanel === panelId) {
|
|
45
|
+
closeAllPanels();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Close all panels first
|
|
50
|
+
closeAllPanels();
|
|
51
|
+
|
|
52
|
+
// Open the new panel
|
|
53
|
+
setActivePanel(panelId);
|
|
54
|
+
container.classList.add('expanded');
|
|
55
|
+
panel.classList.add('active');
|
|
56
|
+
if (icon) icon.classList.add('active');
|
|
57
|
+
|
|
58
|
+
activePanel = panelId;
|
|
59
|
+
|
|
60
|
+
// Trigger panel-specific initialization if needed
|
|
61
|
+
triggerPanelInit(panelId);
|
|
62
|
+
|
|
63
|
+
console.log('[ToolWindow] Opened panel:', panelId);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Close all open panels
|
|
68
|
+
*/
|
|
69
|
+
export function closeAllPanels() {
|
|
70
|
+
const container = getById('tool-panel-container');
|
|
71
|
+
const panels = qsa('.tool-panel');
|
|
72
|
+
const icons = qsa('.tool-window-icon');
|
|
73
|
+
|
|
74
|
+
if (container) {
|
|
75
|
+
container.classList.remove('expanded');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
panels.forEach(panel => {
|
|
79
|
+
panel.classList.remove('active');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
icons.forEach(icon => {
|
|
83
|
+
icon.classList.remove('active');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
activePanel = null;
|
|
87
|
+
|
|
88
|
+
console.log('[ToolWindow] Closed all panels');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Set a panel as active (internal use)
|
|
93
|
+
* @param {string} panelId - Panel ID
|
|
94
|
+
*/
|
|
95
|
+
function setActivePanel(panelId) {
|
|
96
|
+
const panels = qsa('.tool-panel');
|
|
97
|
+
panels.forEach(panel => {
|
|
98
|
+
if (panel.id === `tool-panel-${panelId}`) {
|
|
99
|
+
panel.classList.add('active');
|
|
100
|
+
} else {
|
|
101
|
+
panel.classList.remove('active');
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Trigger initialization for panel-specific features
|
|
108
|
+
* @param {string} panelId - Panel ID
|
|
109
|
+
*/
|
|
110
|
+
function triggerPanelInit(panelId) {
|
|
111
|
+
// Dispatch custom event that panel modules can listen to
|
|
112
|
+
const event = new CustomEvent('tool-panel-opened', {
|
|
113
|
+
detail: { panelId }
|
|
114
|
+
});
|
|
115
|
+
document.dispatchEvent(event);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Get the currently active panel ID
|
|
120
|
+
* @returns {string|null} Active panel ID or null
|
|
121
|
+
*/
|
|
122
|
+
export function getActivePanel() {
|
|
123
|
+
return activePanel;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Register a callback for when a panel is closed
|
|
128
|
+
* @param {Function} callback - Callback function
|
|
129
|
+
*/
|
|
130
|
+
export function onPanelClose(callback) {
|
|
131
|
+
const container = getById('tool-panel-container');
|
|
132
|
+
if (container) {
|
|
133
|
+
const observer = new MutationObserver((mutations) => {
|
|
134
|
+
mutations.forEach((mutation) => {
|
|
135
|
+
if (mutation.attributeName === 'class') {
|
|
136
|
+
if (!container.classList.contains('expanded')) {
|
|
137
|
+
callback();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
observer.observe(container, { attributes: true });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Expose to window for HTML onclick if needed
|
|
147
|
+
window.toggleToolPanel = togglePanel;
|
|
148
|
+
window.closeToolPanels = closeAllPanels;
|
|
@@ -36,6 +36,17 @@ export function initEventHandlers() {
|
|
|
36
36
|
}
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
+
// Copy System Prompt
|
|
40
|
+
globalDispatcher.on(EVENT_TYPES.COPY_PROMPT, async (event) => {
|
|
41
|
+
console.log('[Handlers] Copy Prompt event received:', event);
|
|
42
|
+
try {
|
|
43
|
+
await navigator.clipboard.writeText(SYSTEM_PROMPT);
|
|
44
|
+
showToast('📋 Copied System Prompt', 'success');
|
|
45
|
+
} catch (err) {
|
|
46
|
+
showToast('Failed to copy: ' + err.message, 'error');
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
39
50
|
console.log('[Handlers] Event handlers initialized');
|
|
40
51
|
}
|
|
41
52
|
|