vg-coder-cli 2.0.20 → 2.0.23
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/.vg/tree-state.json +9 -0
- package/package.json +5 -2
- package/scripts/build.js +49 -6
- package/src/index.js +4 -4
- package/src/server/api-server.js +83 -5
- package/src/server/views/css/structure.css +4 -1
- package/src/server/views/dashboard.css +51 -6
- package/src/server/views/dashboard.html +11 -3
- package/src/server/views/js/api.js +24 -0
- package/src/server/views/js/features/iframe-manager.js +2 -1
- package/src/server/views/js/features/resize.js +57 -0
- package/src/server/views/js/features/structure.js +109 -16
- package/src/server/views/js/main.js +36 -4
- package/src/server/views/vg-coder/background.js +48201 -2
- package/src/server/views/vg-coder/controller.js +496 -1
- package/src/server/views/vg-coder/manifest.json +13 -5
- package/src/server/views/vg-coder/{options.css → sidepanel.css} +34 -32
- package/src/server/views/vg-coder/{options.html → sidepanel.html} +2 -2
- package/src/server/views/vg-coder/sidepanel.js +347 -0
- package/vetgo-auto/README.md +3 -0
- package/vetgo-auto/chrome/CSP_IMPROVEMENTS.md +147 -0
- package/vetgo-auto/chrome/MANIFEST_V3_MIGRATION.md +123 -0
- package/vetgo-auto/chrome/assets/icon128.png +0 -0
- package/vetgo-auto/chrome/assets/icon16.png +0 -0
- package/vetgo-auto/chrome/assets/icon48.png +0 -0
- package/vetgo-auto/chrome/environments/environment.ts +13 -0
- package/vetgo-auto/chrome/manifest.json +66 -0
- package/vetgo-auto/chrome/rules.json +23 -0
- package/vetgo-auto/chrome/src/background.ts +200 -0
- package/vetgo-auto/chrome/src/controller.ts +98 -0
- package/vetgo-auto/chrome/src/controllers/common.firebase.ts +31 -0
- package/vetgo-auto/chrome/src/controllers/firebase-crud.ts +147 -0
- package/vetgo-auto/chrome/src/controllers/load-common-fuc.controller.ts +24 -0
- package/vetgo-auto/chrome/src/controllers/load-script.controller.ts +23 -0
- package/vetgo-auto/chrome/src/script-injector.ts +305 -0
- package/vetgo-auto/chrome/src/sidepanel.css +166 -0
- package/vetgo-auto/chrome/src/sidepanel.html +48 -0
- package/vetgo-auto/chrome/src/sidepanel.ts +127 -0
- package/vetgo-auto/chrome/src/utils/db-utils.ts +2 -0
- package/vetgo-auto/chrome/src/utils/environment-storage.service.ts +85 -0
- package/vetgo-auto/chrome/webpack.config.js +53 -0
- package/vetgo-auto/chrome/webpack.config.prod.js +54 -0
- package/vetgo-auto/package.json +30 -0
- package/vetgo-auto/tsconfig.json +27 -0
- package/vg-coder-cli-2.0.23.tgz +0 -0
- package/src/server/views/vg-coder/background.js.LICENSE.txt +0 -118
- package/src/server/views/vg-coder/options.js +0 -1
- package/vg-coder-cli-2.0.20.tgz +0 -0
- package/vg-coder-cli-2.0.21.tgz +0 -0
- package/vg-coder.zip +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vg-coder-cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.23",
|
|
4
4
|
"description": "🚀 CLI tool to analyze projects, concatenate source files, count tokens, and export HTML with syntax highlighting and copy functionality",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
"test": "jest",
|
|
14
14
|
"test:watch": "jest --watch",
|
|
15
15
|
"dev": "nodemon src/index.js",
|
|
16
|
+
"build:extension": "cd vetgo-auto && npm run build",
|
|
17
|
+
"build:copy": "node scripts/build.js",
|
|
16
18
|
"build": "node scripts/build.js",
|
|
17
19
|
"push": "npm run build && npm pack && npm publish"
|
|
18
20
|
},
|
|
@@ -44,6 +46,7 @@
|
|
|
44
46
|
"ora": "^5.4.1",
|
|
45
47
|
"path": "^0.12.7",
|
|
46
48
|
"socket.io": "^4.7.2",
|
|
49
|
+
"socket.io-client": "^4.7.2",
|
|
47
50
|
"tiktoken": "^1.0.10",
|
|
48
51
|
"vg-coder-cli": "^2.0.15"
|
|
49
52
|
},
|
|
@@ -56,4 +59,4 @@
|
|
|
56
59
|
"engines": {
|
|
57
60
|
"node": ">=16.0.0"
|
|
58
61
|
}
|
|
59
|
-
}
|
|
62
|
+
}
|
package/scripts/build.js
CHANGED
|
@@ -1,29 +1,72 @@
|
|
|
1
1
|
const AdmZip = require('adm-zip');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const fs = require('fs-extra');
|
|
4
|
+
const { execSync } = require('child_process');
|
|
4
5
|
|
|
5
6
|
async function build() {
|
|
6
7
|
try {
|
|
7
8
|
const rootDir = path.resolve(__dirname, '..');
|
|
9
|
+
const extensionSourceDir = path.join(rootDir, 'vetgo-auto', 'chrome', 'dist');
|
|
10
|
+
const extensionWorkspaceDir = path.join(rootDir, 'vetgo-auto');
|
|
8
11
|
const zipPath = path.join(rootDir, 'vg-coder.zip');
|
|
9
12
|
const targetDir = path.join(rootDir, 'src', 'server', 'views', 'vg-coder');
|
|
10
13
|
|
|
11
14
|
console.log('🏗️ Starting build process...');
|
|
12
15
|
|
|
13
|
-
// 1
|
|
16
|
+
// Strategy 1: Try to build and copy from vetgo-auto/chrome/dist
|
|
17
|
+
if (fs.existsSync(extensionWorkspaceDir)) {
|
|
18
|
+
console.log('📦 Found vetgo-auto workspace. Building extension...');
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
// Build the extension
|
|
22
|
+
console.log('🔨 Running: cd vetgo-auto && npm run build');
|
|
23
|
+
execSync('npm run build', {
|
|
24
|
+
cwd: extensionWorkspaceDir,
|
|
25
|
+
stdio: 'inherit'
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Check if dist directory exists
|
|
29
|
+
if (fs.existsSync(extensionSourceDir)) {
|
|
30
|
+
console.log('✅ Extension built successfully at:', extensionSourceDir);
|
|
31
|
+
|
|
32
|
+
// Clean old directory
|
|
33
|
+
if (fs.existsSync(targetDir)) {
|
|
34
|
+
console.log('🧹 Cleaning old extension directory...');
|
|
35
|
+
fs.removeSync(targetDir);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Copy built extension
|
|
39
|
+
console.log('📋 Copying extension to:', targetDir);
|
|
40
|
+
fs.copySync(extensionSourceDir, targetDir);
|
|
41
|
+
|
|
42
|
+
console.log('✅ Extension copied successfully!');
|
|
43
|
+
console.log('🚀 Build completed successfully!');
|
|
44
|
+
return;
|
|
45
|
+
} else {
|
|
46
|
+
console.warn('⚠️ Extension dist directory not found. Falling back to zip extraction...');
|
|
47
|
+
}
|
|
48
|
+
} catch (buildError) {
|
|
49
|
+
console.warn('⚠️ Extension build failed:', buildError.message);
|
|
50
|
+
console.warn('⚠️ Falling back to zip extraction...');
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
console.log('ℹ️ vetgo-auto workspace not found. Using zip file...');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Strategy 2: Fallback to zip extraction (for npm package users)
|
|
14
57
|
if (!fs.existsSync(zipPath)) {
|
|
15
|
-
console.error('
|
|
16
|
-
|
|
17
|
-
|
|
58
|
+
console.error('❌ vg-coder.zip not found and extension build failed.');
|
|
59
|
+
console.error('❌ Cannot proceed with build.');
|
|
60
|
+
process.exit(1);
|
|
18
61
|
}
|
|
19
62
|
|
|
20
|
-
//
|
|
63
|
+
// Clean old directory
|
|
21
64
|
if (fs.existsSync(targetDir)) {
|
|
22
65
|
console.log('🧹 Cleaning old extension directory...');
|
|
23
66
|
fs.removeSync(targetDir);
|
|
24
67
|
}
|
|
25
68
|
|
|
26
|
-
//
|
|
69
|
+
// Unzip
|
|
27
70
|
console.log('📦 Unzipping vg-coder.zip...');
|
|
28
71
|
const zip = new AdmZip(zipPath);
|
|
29
72
|
zip.extractAllTo(targetDir, true);
|
package/src/index.js
CHANGED
|
@@ -372,13 +372,13 @@ class VGCoderCLI {
|
|
|
372
372
|
*/
|
|
373
373
|
async handleStart(options) {
|
|
374
374
|
try {
|
|
375
|
-
const
|
|
376
|
-
const server = new ApiServer(
|
|
375
|
+
const initialPort = parseInt(options.port);
|
|
376
|
+
const server = new ApiServer(initialPort);
|
|
377
377
|
|
|
378
378
|
await server.start();
|
|
379
379
|
|
|
380
|
-
// Auto-open browser to dashboard
|
|
381
|
-
const dashboardUrl = `http://localhost:${port}`;
|
|
380
|
+
// Auto-open browser to dashboard using actual port from server
|
|
381
|
+
const dashboardUrl = `http://localhost:${server.port}`;
|
|
382
382
|
const { exec } = require('child_process');
|
|
383
383
|
const platform = process.platform;
|
|
384
384
|
|
package/src/server/api-server.js
CHANGED
|
@@ -274,6 +274,52 @@ class ApiServer {
|
|
|
274
274
|
}
|
|
275
275
|
});
|
|
276
276
|
|
|
277
|
+
// --- TREE STATE API ---
|
|
278
|
+
|
|
279
|
+
// Save tree state (excluded paths)
|
|
280
|
+
this.app.post('/api/tree-state/save', async (req, res) => {
|
|
281
|
+
try {
|
|
282
|
+
const { excludedPaths } = req.body;
|
|
283
|
+
if (!Array.isArray(excludedPaths)) {
|
|
284
|
+
return res.status(400).json({ error: 'excludedPaths must be an array' });
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Create .vg directory if it doesn't exist
|
|
288
|
+
const vgDir = path.join(this.workingDir, '.vg');
|
|
289
|
+
await fs.ensureDir(vgDir);
|
|
290
|
+
|
|
291
|
+
// Save state to .vg/tree-state.json
|
|
292
|
+
const stateFile = path.join(vgDir, 'tree-state.json');
|
|
293
|
+
await fs.writeJson(stateFile, { excludedPaths }, { spaces: 2 });
|
|
294
|
+
|
|
295
|
+
console.log(chalk.green(`✓ Saved tree state: ${excludedPaths.length} excluded items`));
|
|
296
|
+
res.json({ success: true, count: excludedPaths.length });
|
|
297
|
+
} catch (error) {
|
|
298
|
+
console.error(chalk.red('❌ [TREE STATE SAVE] Error:'), error.message);
|
|
299
|
+
res.status(500).json({ error: error.message });
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// Load tree state
|
|
304
|
+
this.app.get('/api/tree-state/load', async (req, res) => {
|
|
305
|
+
try {
|
|
306
|
+
const stateFile = path.join(this.workingDir, '.vg', 'tree-state.json');
|
|
307
|
+
|
|
308
|
+
// Check if state file exists
|
|
309
|
+
if (!await fs.pathExists(stateFile)) {
|
|
310
|
+
return res.json({ excludedPaths: [] });
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Read and return state
|
|
314
|
+
const state = await fs.readJson(stateFile);
|
|
315
|
+
res.json({ excludedPaths: state.excludedPaths || [] });
|
|
316
|
+
} catch (error) {
|
|
317
|
+
console.error(chalk.red('❌ [TREE STATE LOAD] Error:'), error.message);
|
|
318
|
+
// Return empty state on error instead of failing
|
|
319
|
+
res.json({ excludedPaths: [] });
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
|
|
277
323
|
// --- GENERAL API ---
|
|
278
324
|
|
|
279
325
|
this.app.post('/api/analyze', async (req, res) => {
|
|
@@ -327,11 +373,43 @@ class ApiServer {
|
|
|
327
373
|
}
|
|
328
374
|
|
|
329
375
|
async start() {
|
|
330
|
-
return new Promise((resolve) => {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
376
|
+
return new Promise((resolve, reject) => {
|
|
377
|
+
const tryPort = (port) => {
|
|
378
|
+
const onError = (e) => {
|
|
379
|
+
if (e.code === 'EADDRINUSE') {
|
|
380
|
+
console.log(chalk.yellow(`⚠️ Port ${port} is busy, trying ${port + 1}...`));
|
|
381
|
+
this.httpServer.close();
|
|
382
|
+
tryPort(port + 1);
|
|
383
|
+
} else {
|
|
384
|
+
this.httpServer.removeListener('error', onError);
|
|
385
|
+
reject(e);
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
this.httpServer.once('error', onError);
|
|
390
|
+
|
|
391
|
+
this.server = this.httpServer.listen(port, () => {
|
|
392
|
+
this.httpServer.removeListener('error', onError);
|
|
393
|
+
|
|
394
|
+
// Update actual port
|
|
395
|
+
this.port = this.server.address().port;
|
|
396
|
+
|
|
397
|
+
const projectName = path.basename(this.workingDir);
|
|
398
|
+
const startTime = new Date().toLocaleString();
|
|
399
|
+
|
|
400
|
+
console.log(chalk.green('\n──────────────────────────────────────────────────'));
|
|
401
|
+
console.log(`🚀 ${chalk.bold('VG Coder Server')} ${chalk.green('● Online')}`);
|
|
402
|
+
console.log(chalk.gray('──────────────────────────────────────────────────'));
|
|
403
|
+
console.log(`📁 Project: ${chalk.cyan(projectName)}`);
|
|
404
|
+
console.log(`⏰ Started: ${chalk.yellow(startTime)}`);
|
|
405
|
+
console.log(`📡 URL: ${chalk.blue(`http://localhost:${this.port}`)}`);
|
|
406
|
+
console.log(chalk.green('──────────────────────────────────────────────────\n'));
|
|
407
|
+
|
|
408
|
+
resolve();
|
|
409
|
+
});
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
tryPort(this.port);
|
|
335
413
|
});
|
|
336
414
|
}
|
|
337
415
|
|
|
@@ -25,8 +25,9 @@
|
|
|
25
25
|
font-size: 11px;
|
|
26
26
|
/* Smaller tree font */
|
|
27
27
|
overflow-x: auto;
|
|
28
|
-
max-height:
|
|
28
|
+
max-height: 600px;
|
|
29
29
|
overflow-y: auto;
|
|
30
|
+
width: 100%; /* Take full width */
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
.tree-ul {
|
|
@@ -58,6 +59,7 @@
|
|
|
58
59
|
cursor: pointer;
|
|
59
60
|
transition: background 0.1s;
|
|
60
61
|
line-height: 1.2;
|
|
62
|
+
width: 100%; /* Ensure row takes full width */
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
.tree-item-row:hover {
|
|
@@ -99,6 +101,7 @@
|
|
|
99
101
|
font-weight: 600;
|
|
100
102
|
min-width: 30px;
|
|
101
103
|
text-align: center;
|
|
104
|
+
flex-shrink: 0; /* Always keep badge visible */
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
.token-low {
|
|
@@ -61,32 +61,77 @@ body {
|
|
|
61
61
|
height: 100%;
|
|
62
62
|
overflow-y: auto;
|
|
63
63
|
background: var(--ios-bg);
|
|
64
|
-
border-right: 1px solid var(--ios-separator);
|
|
65
64
|
position: relative;
|
|
66
65
|
z-index: 10;
|
|
66
|
+
/* allow resize override */
|
|
67
|
+
flex: 0 0 450px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* Resize Handler */
|
|
71
|
+
.resize-handler {
|
|
72
|
+
width: 16px;
|
|
73
|
+
cursor: col-resize;
|
|
74
|
+
background: transparent;
|
|
75
|
+
z-index: 100;
|
|
76
|
+
margin-left: -3px; /* Overlap border */
|
|
77
|
+
position: relative;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.resize-handler:hover,
|
|
81
|
+
body.resizing .resize-handler {
|
|
82
|
+
background: var(--ios-blue);
|
|
83
|
+
opacity: 0.5;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
body.resizing {
|
|
87
|
+
cursor: col-resize;
|
|
88
|
+
user-select: none;
|
|
67
89
|
}
|
|
68
90
|
|
|
69
91
|
.container {
|
|
70
92
|
padding: 15px;
|
|
71
93
|
padding-top: 15px;
|
|
94
|
+
padding-right: 0px;
|
|
72
95
|
}
|
|
73
96
|
|
|
74
97
|
/* --- COMPONENT STYLES --- */
|
|
75
98
|
.header {
|
|
76
99
|
display: flex;
|
|
77
100
|
justify-content: space-between;
|
|
101
|
+
align-items: center; /* Align items vertically center */
|
|
78
102
|
margin-bottom: 15px;
|
|
79
103
|
}
|
|
80
104
|
|
|
81
|
-
.header
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
105
|
+
.header-content {
|
|
106
|
+
display: flex;
|
|
107
|
+
align-items: center;
|
|
108
|
+
gap: 12px;
|
|
85
109
|
}
|
|
86
110
|
|
|
87
111
|
.status {
|
|
88
|
-
font-size:
|
|
112
|
+
font-size: 14px;
|
|
89
113
|
color: var(--ios-green);
|
|
114
|
+
line-height: 1;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* NEW: Project Info Styles */
|
|
118
|
+
.project-info {
|
|
119
|
+
display: flex;
|
|
120
|
+
flex-direction: column;
|
|
121
|
+
justify-content: center;
|
|
122
|
+
line-height: 1.2;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.project-name {
|
|
126
|
+
font-weight: 700;
|
|
127
|
+
font-size: 16px;
|
|
128
|
+
color: var(--text-primary);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.project-meta {
|
|
132
|
+
font-size: 11px;
|
|
133
|
+
color: var(--text-secondary);
|
|
134
|
+
font-family: monospace;
|
|
90
135
|
}
|
|
91
136
|
|
|
92
137
|
.theme-toggle {
|
|
@@ -62,8 +62,14 @@
|
|
|
62
62
|
<div class="container">
|
|
63
63
|
<div class="header">
|
|
64
64
|
<div class="header-content">
|
|
65
|
-
|
|
66
|
-
<
|
|
65
|
+
<!-- Status Dot -->
|
|
66
|
+
<span class="status" id="status" style="font-size: 14px;">●</span>
|
|
67
|
+
|
|
68
|
+
<!-- NEW: Project Info Section -->
|
|
69
|
+
<div class="project-info">
|
|
70
|
+
<div class="project-name" id="project-name">Loading...</div>
|
|
71
|
+
<div class="project-meta" id="project-meta">...</div>
|
|
72
|
+
</div>
|
|
67
73
|
</div>
|
|
68
74
|
<button class="theme-toggle" id="theme-toggle" title="Toggle Dark Mode">
|
|
69
75
|
<span id="theme-icon">🌙</span>
|
|
@@ -180,6 +186,8 @@
|
|
|
180
186
|
<div style="height: 50px;"></div>
|
|
181
187
|
</div>
|
|
182
188
|
</div>
|
|
189
|
+
<!-- Resize Handler -->
|
|
190
|
+
<div id="resize-handler" class="resize-handler"></div>
|
|
183
191
|
|
|
184
192
|
<!-- CỘT PHẢI: AI Iframe & Editor -->
|
|
185
193
|
<div class="right-panel">
|
|
@@ -275,4 +283,4 @@
|
|
|
275
283
|
<script type="module" src="/js/main.js"></script>
|
|
276
284
|
</body>
|
|
277
285
|
|
|
278
|
-
</html>
|
|
286
|
+
</html>
|
|
@@ -100,6 +100,30 @@ export async function commitChanges(message) {
|
|
|
100
100
|
return true;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
// --- Tree State API ---
|
|
104
|
+
|
|
105
|
+
export async function saveTreeState(excludedPaths) {
|
|
106
|
+
const res = await fetch(`${API_BASE}/api/tree-state/save`, {
|
|
107
|
+
method: 'POST',
|
|
108
|
+
headers: { 'Content-Type': 'application/json' },
|
|
109
|
+
body: JSON.stringify({ excludedPaths })
|
|
110
|
+
});
|
|
111
|
+
if (!res.ok) {
|
|
112
|
+
const data = await res.json();
|
|
113
|
+
throw new Error(data.error || 'Failed to save tree state');
|
|
114
|
+
}
|
|
115
|
+
return await res.json();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function loadTreeState() {
|
|
119
|
+
const res = await fetch(`${API_BASE}/api/tree-state/load`);
|
|
120
|
+
if (!res.ok) {
|
|
121
|
+
const data = await res.json();
|
|
122
|
+
throw new Error(data.error || 'Failed to load tree state');
|
|
123
|
+
}
|
|
124
|
+
return await res.json();
|
|
125
|
+
}
|
|
126
|
+
|
|
103
127
|
// ------------------------
|
|
104
128
|
|
|
105
129
|
export async function copyAsFile(filename, content) {
|
|
@@ -3,7 +3,8 @@ const AI_PROVIDERS = [
|
|
|
3
3
|
{ id: 'kimi', name: 'Kimi AI', url: 'https://www.kimi.com' },
|
|
4
4
|
{ id: 'deepseek', name: 'DeepSeek', url: 'https://chat.deepseek.com' },
|
|
5
5
|
{ id: 'gemini', name: 'Google Gemini', url: 'https://gemini.google.com/app' },
|
|
6
|
-
{ id: 'aistudio', name: 'Google AI Studio', url: 'https://aistudio.google.com/prompts/new_chat' }
|
|
6
|
+
{ id: 'aistudio', name: 'Google AI Studio', url: 'https://aistudio.google.com/prompts/new_chat' },
|
|
7
|
+
{ id: 'gork', name: 'Gork', url: 'https://grok.com' },
|
|
7
8
|
];
|
|
8
9
|
|
|
9
10
|
export function initIframeManager() {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature: Left Panel Resize
|
|
3
|
+
* Allows the user to drag the right edge of the left panel to resize it.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export function initResizeHandler() {
|
|
7
|
+
const leftPanel = document.querySelector('.left-panel');
|
|
8
|
+
const handle = document.getElementById('resize-handler'); // Match the ID we will add in HTML
|
|
9
|
+
const splitLayout = document.querySelector('.split-layout');
|
|
10
|
+
|
|
11
|
+
if (!leftPanel || !handle) {
|
|
12
|
+
console.warn('Resize elements not found');
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let isResizing = false;
|
|
17
|
+
let startX = 0;
|
|
18
|
+
let startWidth = 0;
|
|
19
|
+
|
|
20
|
+
// Mouse Down
|
|
21
|
+
handle.addEventListener('mousedown', (e) => {
|
|
22
|
+
isResizing = true;
|
|
23
|
+
startX = e.clientX;
|
|
24
|
+
startWidth = leftPanel.getBoundingClientRect().width;
|
|
25
|
+
|
|
26
|
+
// Add resizing class for styling/cursor
|
|
27
|
+
document.body.classList.add('resizing');
|
|
28
|
+
|
|
29
|
+
// Disable text selection during drag
|
|
30
|
+
e.preventDefault();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Mouse Move
|
|
34
|
+
document.addEventListener('mousemove', (e) => {
|
|
35
|
+
if (!isResizing) return;
|
|
36
|
+
|
|
37
|
+
requestAnimationFrame(() => {
|
|
38
|
+
const currentX = e.clientX;
|
|
39
|
+
const diffX = currentX - startX;
|
|
40
|
+
const newWidth = Math.max(250, startWidth + diffX); // Min width 250px
|
|
41
|
+
const maxWidth = window.innerWidth - 300; // Leave space for right panel
|
|
42
|
+
|
|
43
|
+
if (newWidth < maxWidth) {
|
|
44
|
+
leftPanel.style.flex = `0 0 ${newWidth}px`;
|
|
45
|
+
leftPanel.style.width = `${newWidth}px`;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Mouse Up
|
|
51
|
+
document.addEventListener('mouseup', () => {
|
|
52
|
+
if (isResizing) {
|
|
53
|
+
isResizing = false;
|
|
54
|
+
document.body.classList.remove('resizing');
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|