vg-coder-cli 2.0.31 → 2.0.32
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/ARCHITECTURE.md +255 -0
- package/README.md +0 -11
- package/change.sh +0 -0
- package/dist/vg-coder-bundle.js +42 -0
- package/gulpfile.js +111 -0
- package/package.json +19 -11
- package/src/index.js +28 -220
- package/src/server/api-server.js +120 -428
- package/src/server/views/css/bubble.css +81 -0
- package/src/server/views/css/code-viewer.css +58 -0
- package/src/server/views/css/terminal.css +59 -155
- package/src/server/views/dashboard.css +78 -678
- package/src/server/views/dashboard.html +39 -278
- package/src/server/views/js/api.js +2 -22
- package/src/server/views/js/config.js +27 -15
- package/src/server/views/js/event-protocol.js +263 -0
- package/src/server/views/js/features/bubble-features/index.js +125 -0
- package/src/server/views/js/features/bubble-features/paste-run-feature.js +16 -0
- package/src/server/views/js/features/bubble-features/terminal-feature.js +16 -0
- package/src/server/views/js/features/bubble.js +175 -0
- package/src/server/views/js/features/code-viewer.js +90 -0
- package/src/server/views/js/features/commands.js +34 -81
- package/src/server/views/js/features/editor-tabs.js +19 -46
- package/src/server/views/js/features/git-view.js +63 -81
- package/src/server/views/js/features/iframe-manager.js +3 -97
- package/src/server/views/js/features/monaco-manager.js +19 -39
- package/src/server/views/js/features/project-switcher.js +7 -63
- package/src/server/views/js/features/resize.js +5 -16
- package/src/server/views/js/features/structure.js +38 -106
- package/src/server/views/js/features/terminal.js +102 -418
- package/src/server/views/js/handlers.js +60 -43
- package/src/server/views/js/main.js +75 -179
- package/src/server/views/js/shadow-entry.js +21 -0
- package/src/server/views/js/utils.js +48 -28
- package/src/server/views/vg-coder/_metadata/generated_indexed_rulesets/_ruleset1 +0 -0
- package/src/server/views/vg-coder/controller.js +33 -258
- package/vetgo-auto/chrome/src/utils/injector-script.ts +33 -258
- package/vetgo-auto/vg-coder.zip +0 -0
- package/src/server/views/dashboard.js +0 -457
- package/test-pty.js +0 -31
|
@@ -1,99 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
{ id: 'kimi', name: 'Kimi AI', url: 'https://www.kimi.com' },
|
|
4
|
-
{ id: 'deepseek', name: 'DeepSeek', url: 'https://chat.deepseek.com' },
|
|
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' },
|
|
7
|
-
{ id: 'gork', name: 'Gork', url: 'https://grok.com' },
|
|
8
|
-
];
|
|
9
|
-
|
|
1
|
+
// Iframe Manager - Deprecated and Removed
|
|
2
|
+
// This file is kept as a placeholder to prevent build errors if referenced elsewhere
|
|
10
3
|
export function initIframeManager() {
|
|
11
|
-
|
|
12
|
-
const iframe = document.getElementById('ai-iframe');
|
|
13
|
-
// Removed externalLink reference
|
|
14
|
-
const placeholderLink = document.getElementById('ai-placeholder-link');
|
|
15
|
-
|
|
16
|
-
// Guide elements
|
|
17
|
-
const guideContainer = document.getElementById('iframe-placeholder');
|
|
18
|
-
const guideToggleBtn = document.getElementById('guide-toggle-btn');
|
|
19
|
-
const guideCloseBtn = document.getElementById('guide-close-btn');
|
|
20
|
-
const guideDoneBtn = document.getElementById('guide-done-btn');
|
|
21
|
-
|
|
22
|
-
if (!select || !iframe) return;
|
|
23
|
-
|
|
24
|
-
// 1. Populate options
|
|
25
|
-
AI_PROVIDERS.forEach(provider => {
|
|
26
|
-
const option = document.createElement('option');
|
|
27
|
-
option.value = provider.id;
|
|
28
|
-
option.textContent = provider.name;
|
|
29
|
-
select.appendChild(option);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
// 2. Load saved selection
|
|
33
|
-
const savedProviderId = localStorage.getItem('ai_provider') || 'chatgpt';
|
|
34
|
-
const isValid = AI_PROVIDERS.some(p => p.id === savedProviderId);
|
|
35
|
-
select.value = isValid ? savedProviderId : AI_PROVIDERS[0].id;
|
|
36
|
-
|
|
37
|
-
const updateProvider = (providerId) => {
|
|
38
|
-
const provider = AI_PROVIDERS.find(p => p.id === providerId) || AI_PROVIDERS[0];
|
|
39
|
-
|
|
40
|
-
// Add VG Coder context parameter to prevent nested iframe injection
|
|
41
|
-
const urlWithParam = new URL(provider.url);
|
|
42
|
-
urlWithParam.searchParams.set('vg_coder_context', 'true');
|
|
43
|
-
|
|
44
|
-
// Reset iframe source to trigger reload
|
|
45
|
-
iframe.src = 'about:blank';
|
|
46
|
-
setTimeout(() => {
|
|
47
|
-
iframe.src = urlWithParam.toString();
|
|
48
|
-
}, 50);
|
|
49
|
-
|
|
50
|
-
// Update placeholder link (without the parameter for direct access)
|
|
51
|
-
if (placeholderLink) {
|
|
52
|
-
placeholderLink.href = provider.url;
|
|
53
|
-
placeholderLink.textContent = `Mở ${provider.name} tab mới ↗`;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
localStorage.setItem('ai_provider', providerId);
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
// Initial load
|
|
60
|
-
updateProvider(select.value);
|
|
61
|
-
|
|
62
|
-
// Event listeners
|
|
63
|
-
select.addEventListener('change', (e) => {
|
|
64
|
-
updateProvider(e.target.value);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// --- GUIDE TOGGLE LOGIC ---
|
|
68
|
-
|
|
69
|
-
const showGuide = () => {
|
|
70
|
-
if (guideContainer) guideContainer.classList.remove('hidden');
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const hideGuide = () => {
|
|
74
|
-
if (guideContainer) guideContainer.classList.add('hidden');
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const reloadIframe = () => {
|
|
78
|
-
const currentSrc = iframe.src;
|
|
79
|
-
iframe.src = 'about:blank';
|
|
80
|
-
setTimeout(() => {
|
|
81
|
-
iframe.src = currentSrc;
|
|
82
|
-
}, 100);
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
if (guideToggleBtn) {
|
|
86
|
-
guideToggleBtn.addEventListener('click', showGuide);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (guideCloseBtn) {
|
|
90
|
-
guideCloseBtn.addEventListener('click', hideGuide);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (guideDoneBtn) {
|
|
94
|
-
guideDoneBtn.addEventListener('click', () => {
|
|
95
|
-
hideGuide();
|
|
96
|
-
reloadIframe();
|
|
97
|
-
});
|
|
98
|
-
}
|
|
4
|
+
// No-op
|
|
99
5
|
}
|
|
@@ -1,40 +1,44 @@
|
|
|
1
1
|
import { API_BASE } from '../config.js';
|
|
2
|
-
import { showToast } from '../utils.js';
|
|
2
|
+
import { showToast, getById } from '../utils.js';
|
|
3
3
|
|
|
4
4
|
let editor = null;
|
|
5
|
-
let models = new Map();
|
|
5
|
+
let models = new Map();
|
|
6
6
|
let isMonacoLoaded = false;
|
|
7
7
|
|
|
8
|
-
// Cấu hình đường dẫn cho AMD Loader của Monaco
|
|
9
8
|
export function initMonaco() {
|
|
10
|
-
|
|
9
|
+
if (typeof require !== 'undefined' && require.config) {
|
|
10
|
+
require.config({ paths: { 'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs' }});
|
|
11
|
+
}
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
/**
|
|
14
|
-
* Đảm bảo Editor đã được khởi tạo
|
|
15
|
-
*/
|
|
16
14
|
async function ensureEditor() {
|
|
17
15
|
if (editor) return editor;
|
|
18
16
|
|
|
19
17
|
return new Promise((resolve) => {
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
const amdRequire = window.require;
|
|
19
|
+
|
|
20
|
+
if (!amdRequire) {
|
|
21
|
+
console.error('Monaco Loader not found');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
amdRequire(['vs/editor/editor.main'], function () {
|
|
26
|
+
const container = getById('monaco-container');
|
|
27
|
+
if (!container) return;
|
|
22
28
|
|
|
23
|
-
// Xác định theme dựa trên theme hiện tại của web
|
|
24
29
|
const currentTheme = document.documentElement.getAttribute('data-theme') === 'dark' ? 'vs-dark' : 'vs';
|
|
25
30
|
|
|
26
31
|
editor = monaco.editor.create(container, {
|
|
27
32
|
value: '',
|
|
28
33
|
language: 'plaintext',
|
|
29
34
|
theme: currentTheme,
|
|
30
|
-
automaticLayout: true,
|
|
35
|
+
automaticLayout: true,
|
|
31
36
|
minimap: { enabled: true },
|
|
32
37
|
fontSize: 13,
|
|
33
38
|
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
|
|
34
39
|
scrollBeyondLastLine: false,
|
|
35
40
|
});
|
|
36
41
|
|
|
37
|
-
// Lắng nghe phím tắt Ctrl+S / Cmd+S để lưu
|
|
38
42
|
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
|
|
39
43
|
saveCurrentFile();
|
|
40
44
|
});
|
|
@@ -45,13 +49,10 @@ async function ensureEditor() {
|
|
|
45
49
|
});
|
|
46
50
|
}
|
|
47
51
|
|
|
48
|
-
/**
|
|
49
|
-
* Mở file vào Editor
|
|
50
|
-
*/
|
|
51
52
|
export async function openFileInMonaco(path) {
|
|
52
53
|
const editorInstance = await ensureEditor();
|
|
54
|
+
if (!editorInstance) return;
|
|
53
55
|
|
|
54
|
-
// 1. Nếu Model đã tồn tại trong bộ nhớ -> Dùng lại
|
|
55
56
|
if (models.has(path)) {
|
|
56
57
|
const stored = models.get(path);
|
|
57
58
|
editorInstance.setModel(stored.model);
|
|
@@ -62,22 +63,14 @@ export async function openFileInMonaco(path) {
|
|
|
62
63
|
return;
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
// 2. Nếu chưa -> Fetch nội dung từ Server
|
|
66
66
|
try {
|
|
67
67
|
const res = await fetch(`${API_BASE}/api/read-file?path=${encodeURIComponent(path)}`);
|
|
68
68
|
const data = await res.json();
|
|
69
69
|
|
|
70
70
|
if (res.ok) {
|
|
71
|
-
// Xác định ngôn ngữ từ extension
|
|
72
71
|
const language = getLanguageFromPath(path);
|
|
73
|
-
|
|
74
|
-
// Tạo Model mới
|
|
75
72
|
const newModel = monaco.editor.createModel(data.content, language, monaco.Uri.file(path));
|
|
76
|
-
|
|
77
|
-
// Lưu vào cache
|
|
78
73
|
models.set(path, { model: newModel, viewState: null });
|
|
79
|
-
|
|
80
|
-
// Gán vào Editor
|
|
81
74
|
editorInstance.setModel(newModel);
|
|
82
75
|
} else {
|
|
83
76
|
showToast(`Error opening file: ${data.error}`, 'error');
|
|
@@ -87,9 +80,6 @@ export async function openFileInMonaco(path) {
|
|
|
87
80
|
}
|
|
88
81
|
}
|
|
89
82
|
|
|
90
|
-
/**
|
|
91
|
-
* Lưu trạng thái View (Scroll, Cursor) trước khi chuyển tab
|
|
92
|
-
*/
|
|
93
83
|
export function saveViewState(path) {
|
|
94
84
|
if (editor && models.has(path)) {
|
|
95
85
|
const viewState = editor.saveViewState();
|
|
@@ -99,35 +89,26 @@ export function saveViewState(path) {
|
|
|
99
89
|
}
|
|
100
90
|
}
|
|
101
91
|
|
|
102
|
-
/**
|
|
103
|
-
* Giải phóng Model khi đóng Tab
|
|
104
|
-
*/
|
|
105
92
|
export function disposeModel(path) {
|
|
106
93
|
if (models.has(path)) {
|
|
107
94
|
const stored = models.get(path);
|
|
108
|
-
stored.model.dispose();
|
|
95
|
+
stored.model.dispose();
|
|
109
96
|
models.delete(path);
|
|
110
97
|
}
|
|
111
98
|
}
|
|
112
99
|
|
|
113
|
-
/**
|
|
114
|
-
* Cập nhật Theme cho Monaco khi web đổi theme
|
|
115
|
-
*/
|
|
116
100
|
export function updateMonacoTheme(theme) {
|
|
117
101
|
if (editor) {
|
|
118
102
|
monaco.editor.setTheme(theme === 'dark' ? 'vs-dark' : 'vs');
|
|
119
103
|
}
|
|
120
104
|
}
|
|
121
105
|
|
|
122
|
-
/**
|
|
123
|
-
* Lưu file hiện tại
|
|
124
|
-
*/
|
|
125
106
|
async function saveCurrentFile() {
|
|
126
107
|
const model = editor.getModel();
|
|
127
108
|
if (!model) return;
|
|
128
109
|
|
|
129
110
|
const content = model.getValue();
|
|
130
|
-
const filePath = model.uri.fsPath;
|
|
111
|
+
const filePath = model.uri.fsPath;
|
|
131
112
|
|
|
132
113
|
try {
|
|
133
114
|
const res = await fetch(`${API_BASE}/api/save-file`, {
|
|
@@ -146,7 +127,6 @@ async function saveCurrentFile() {
|
|
|
146
127
|
}
|
|
147
128
|
}
|
|
148
129
|
|
|
149
|
-
// Helper: Map extension to Monaco Language
|
|
150
130
|
function getLanguageFromPath(path) {
|
|
151
131
|
const ext = path.split('.').pop().toLowerCase();
|
|
152
132
|
const map = {
|
|
@@ -1,25 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
import { API_BASE } from '../config.js';
|
|
2
|
+
import { getById, showToast } from '../utils.js';
|
|
2
3
|
|
|
3
|
-
/**
|
|
4
|
-
* Initialize project switcher
|
|
5
|
-
*/
|
|
6
4
|
export async function initProjectSwitcher() {
|
|
7
|
-
// Load initial project list
|
|
8
5
|
await loadProjects();
|
|
9
|
-
|
|
10
|
-
// Poll for project updates every 5 seconds
|
|
11
6
|
setInterval(loadProjects, 5000);
|
|
12
|
-
|
|
13
|
-
// Listen for socket events
|
|
14
7
|
setupSocketListeners();
|
|
15
8
|
}
|
|
16
9
|
|
|
17
|
-
/**
|
|
18
|
-
* Load and display all projects
|
|
19
|
-
*/
|
|
20
10
|
async function loadProjects() {
|
|
21
11
|
try {
|
|
22
|
-
const response = await fetch(
|
|
12
|
+
const response = await fetch(`${API_BASE}/api/projects`);
|
|
23
13
|
const data = await response.json();
|
|
24
14
|
|
|
25
15
|
updateProjectSelector(data.projects, data.activeProjectId);
|
|
@@ -30,17 +20,12 @@ async function loadProjects() {
|
|
|
30
20
|
}
|
|
31
21
|
}
|
|
32
22
|
|
|
33
|
-
/**
|
|
34
|
-
* Update project selector dropdown
|
|
35
|
-
*/
|
|
36
23
|
function updateProjectSelector(projects, activeProjectId) {
|
|
37
|
-
const selector =
|
|
24
|
+
const selector = getById('project-selector');
|
|
38
25
|
if (!selector) return;
|
|
39
26
|
|
|
40
|
-
// Clear existing options
|
|
41
27
|
selector.innerHTML = '';
|
|
42
28
|
|
|
43
|
-
// Add projects
|
|
44
29
|
projects.forEach(project => {
|
|
45
30
|
const option = document.createElement('option');
|
|
46
31
|
option.value = project.id;
|
|
@@ -50,22 +35,16 @@ function updateProjectSelector(projects, activeProjectId) {
|
|
|
50
35
|
});
|
|
51
36
|
}
|
|
52
37
|
|
|
53
|
-
/**
|
|
54
|
-
* Update project count badge
|
|
55
|
-
*/
|
|
56
38
|
function updateProjectCount(count) {
|
|
57
|
-
const badge =
|
|
39
|
+
const badge = getById('project-count');
|
|
58
40
|
if (!badge) return;
|
|
59
41
|
|
|
60
42
|
badge.textContent = `${count} project${count !== 1 ? 's' : ''}`;
|
|
61
43
|
}
|
|
62
44
|
|
|
63
|
-
/**
|
|
64
|
-
* Switch to a different project
|
|
65
|
-
*/
|
|
66
45
|
export async function switchProject(projectId) {
|
|
67
46
|
try {
|
|
68
|
-
const response = await fetch(
|
|
47
|
+
const response = await fetch(`${API_BASE}/api/projects/switch`, {
|
|
69
48
|
method: 'POST',
|
|
70
49
|
headers: { 'Content-Type': 'application/json' },
|
|
71
50
|
body: JSON.stringify({ projectId })
|
|
@@ -74,7 +53,6 @@ export async function switchProject(projectId) {
|
|
|
74
53
|
const data = await response.json();
|
|
75
54
|
|
|
76
55
|
if (data.success) {
|
|
77
|
-
// Emit custom event for other components to react
|
|
78
56
|
const event = new CustomEvent('project-switched', {
|
|
79
57
|
detail: {
|
|
80
58
|
projectId,
|
|
@@ -84,7 +62,6 @@ export async function switchProject(projectId) {
|
|
|
84
62
|
});
|
|
85
63
|
window.dispatchEvent(event);
|
|
86
64
|
|
|
87
|
-
// Show toast
|
|
88
65
|
showToast(`Switched to: ${data.project.name}`, 'success');
|
|
89
66
|
} else {
|
|
90
67
|
showToast('Failed to switch project', 'error');
|
|
@@ -95,59 +72,26 @@ export async function switchProject(projectId) {
|
|
|
95
72
|
}
|
|
96
73
|
}
|
|
97
74
|
|
|
98
|
-
/**
|
|
99
|
-
* Setup socket listeners for real-time updates
|
|
100
|
-
*/
|
|
101
75
|
function setupSocketListeners() {
|
|
102
76
|
if (typeof io === 'undefined') return;
|
|
103
77
|
|
|
104
|
-
const socket = io();
|
|
78
|
+
const socket = io(API_BASE);
|
|
105
79
|
|
|
106
|
-
// New project registered
|
|
107
80
|
socket.on('project:registered', (data) => {
|
|
108
81
|
console.log('New project registered:', data);
|
|
109
82
|
loadProjects();
|
|
110
83
|
showToast(`New project joined: ${data.name}`, 'info');
|
|
111
84
|
});
|
|
112
85
|
|
|
113
|
-
// Project switched
|
|
114
86
|
socket.on('project:switched', (data) => {
|
|
115
87
|
console.log('Project switched:', data);
|
|
116
88
|
loadProjects();
|
|
117
89
|
});
|
|
118
90
|
|
|
119
|
-
// Project removed
|
|
120
91
|
socket.on('project:removed', (data) => {
|
|
121
92
|
console.log('Project removed:', data);
|
|
122
93
|
loadProjects();
|
|
123
94
|
});
|
|
124
95
|
}
|
|
125
96
|
|
|
126
|
-
/**
|
|
127
|
-
* Show toast notification
|
|
128
|
-
*/
|
|
129
|
-
function showToast(message, type = 'info') {
|
|
130
|
-
const toast = document.getElementById('toast');
|
|
131
|
-
if (!toast) return;
|
|
132
|
-
|
|
133
|
-
toast.textContent = message;
|
|
134
|
-
toast.className = 'toast show';
|
|
135
|
-
|
|
136
|
-
if (type === 'success') {
|
|
137
|
-
toast.style.background = '#28a745';
|
|
138
|
-
} else if (type === 'error') {
|
|
139
|
-
toast.style.background = '#dc3545';
|
|
140
|
-
} else if (type === 'warning') {
|
|
141
|
-
toast.style.background = '#ffc107';
|
|
142
|
-
toast.style.color = '#000';
|
|
143
|
-
} else {
|
|
144
|
-
toast.style.background = '#007bff';
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
setTimeout(() => {
|
|
148
|
-
toast.classList.remove('show');
|
|
149
|
-
}, 3000);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Export for global access
|
|
153
97
|
window.switchProject = switchProject;
|
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
* Feature: Left Panel Resize
|
|
3
|
-
* Allows the user to drag the right edge of the left panel to resize it.
|
|
4
|
-
*/
|
|
1
|
+
import { qs, getById } from '../utils.js';
|
|
5
2
|
|
|
6
3
|
export function initResizeHandler() {
|
|
7
|
-
const leftPanel =
|
|
8
|
-
const handle =
|
|
9
|
-
const splitLayout = document.querySelector('.split-layout');
|
|
4
|
+
const leftPanel = qs('.left-panel');
|
|
5
|
+
const handle = getById('resize-handler');
|
|
10
6
|
|
|
11
7
|
if (!leftPanel || !handle) {
|
|
12
|
-
console.warn('Resize elements not found');
|
|
13
8
|
return;
|
|
14
9
|
}
|
|
15
10
|
|
|
@@ -17,28 +12,23 @@ export function initResizeHandler() {
|
|
|
17
12
|
let startX = 0;
|
|
18
13
|
let startWidth = 0;
|
|
19
14
|
|
|
20
|
-
// Mouse Down
|
|
21
15
|
handle.addEventListener('mousedown', (e) => {
|
|
22
16
|
isResizing = true;
|
|
23
17
|
startX = e.clientX;
|
|
24
18
|
startWidth = leftPanel.getBoundingClientRect().width;
|
|
25
19
|
|
|
26
|
-
// Add resizing class for styling/cursor
|
|
27
20
|
document.body.classList.add('resizing');
|
|
28
|
-
|
|
29
|
-
// Disable text selection during drag
|
|
30
21
|
e.preventDefault();
|
|
31
22
|
});
|
|
32
23
|
|
|
33
|
-
// Mouse Move
|
|
34
24
|
document.addEventListener('mousemove', (e) => {
|
|
35
25
|
if (!isResizing) return;
|
|
36
26
|
|
|
37
27
|
requestAnimationFrame(() => {
|
|
38
28
|
const currentX = e.clientX;
|
|
39
29
|
const diffX = currentX - startX;
|
|
40
|
-
const newWidth = Math.max(250, startWidth + diffX);
|
|
41
|
-
const maxWidth = window.innerWidth - 300;
|
|
30
|
+
const newWidth = Math.max(250, startWidth + diffX);
|
|
31
|
+
const maxWidth = window.innerWidth - 300;
|
|
42
32
|
|
|
43
33
|
if (newWidth < maxWidth) {
|
|
44
34
|
leftPanel.style.flex = `0 0 ${newWidth}px`;
|
|
@@ -47,7 +37,6 @@ export function initResizeHandler() {
|
|
|
47
37
|
});
|
|
48
38
|
});
|
|
49
39
|
|
|
50
|
-
// Mouse Up
|
|
51
40
|
document.addEventListener('mouseup', () => {
|
|
52
41
|
if (isResizing) {
|
|
53
42
|
isResizing = false;
|