vg-coder-cli 2.0.30 → 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/scripts/postinstall.js +13 -3
- 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
|
@@ -1,21 +1,49 @@
|
|
|
1
|
-
|
|
2
|
-
import { SYSTEM_PROMPT } from './config.js';
|
|
1
|
+
import { SYSTEM_PROMPT, API_BASE } from './config.js';
|
|
3
2
|
import { analyzeProject, executeScript, copyToClipboard, readFromClipboard } from './api.js';
|
|
4
|
-
import { showToast, showLoading, resetButton, showResponse, showCopiedState } from './utils.js';
|
|
5
|
-
|
|
6
|
-
import { handleStructureView, handleToggleFolder, handleCheckboxChange, handleCopySelected } from './features/structure.js';
|
|
3
|
+
import { showToast, showLoading, resetButton, showResponse, showCopiedState, getById } from './utils.js';
|
|
4
|
+
import { globalDispatcher, EVENT_TYPES } from './event-protocol.js';
|
|
7
5
|
|
|
8
6
|
let lastAnalyzeResult = null;
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Initialize event handlers for bubble menu features
|
|
10
|
+
* This creates the bridge between UI events and handler functions
|
|
11
|
+
*/
|
|
12
|
+
export function initEventHandlers() {
|
|
13
|
+
// Paste & Run from Clipboard
|
|
14
|
+
globalDispatcher.on(EVENT_TYPES.PASTE_RUN, async (event) => {
|
|
15
|
+
console.log('[Handlers] Paste & Run event received:', event);
|
|
16
|
+
await executeFromClipboard();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// New Terminal
|
|
20
|
+
globalDispatcher.on(EVENT_TYPES.TERMINAL_NEW, (event) => {
|
|
21
|
+
console.log('[Handlers] New Terminal event received:', event);
|
|
22
|
+
if (typeof window.createNewTerminal === 'function') {
|
|
23
|
+
window.createNewTerminal();
|
|
24
|
+
} else {
|
|
25
|
+
showToast('Terminal feature not available', 'error');
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Terminal Execute (for future use)
|
|
30
|
+
globalDispatcher.on(EVENT_TYPES.TERMINAL_EXECUTE, (event) => {
|
|
31
|
+
console.log('[Handlers] Terminal Execute event received:', event);
|
|
32
|
+
const { command, terminalId } = event.payload || {};
|
|
33
|
+
if (command) {
|
|
34
|
+
// TODO: Implement terminal execute logic
|
|
35
|
+
console.log(`Execute in terminal ${terminalId}: ${command}`);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
console.log('[Handlers] Event handlers initialized');
|
|
40
|
+
}
|
|
13
41
|
|
|
14
42
|
export function toggleSystemPrompt() {
|
|
15
|
-
const content =
|
|
16
|
-
const icon =
|
|
17
|
-
content.classList.toggle('open');
|
|
18
|
-
icon.classList.toggle('open');
|
|
43
|
+
const content = getById('system-prompt-content');
|
|
44
|
+
const icon = getById('toggle-icon');
|
|
45
|
+
if(content) content.classList.toggle('open');
|
|
46
|
+
if(icon) icon.classList.toggle('open');
|
|
19
47
|
}
|
|
20
48
|
|
|
21
49
|
export function copySystemPromptFromHeader(event) {
|
|
@@ -33,8 +61,8 @@ export function copySystemPromptFromHeader(event) {
|
|
|
33
61
|
|
|
34
62
|
export function copySystemPrompt(event) {
|
|
35
63
|
const copyBtn = event.target.closest('.btn-copy');
|
|
36
|
-
const copyIcon =
|
|
37
|
-
const copyText =
|
|
64
|
+
const copyIcon = getById('copy-icon');
|
|
65
|
+
const copyText = getById('copy-text');
|
|
38
66
|
|
|
39
67
|
navigator.clipboard.writeText(SYSTEM_PROMPT).then(() => {
|
|
40
68
|
showCopiedState(copyBtn, copyIcon, copyText, '📋', 'Copy System Prompt');
|
|
@@ -42,13 +70,12 @@ export function copySystemPrompt(event) {
|
|
|
42
70
|
}).catch(err => showToast('Lỗi copy: ' + err.message, 'error'));
|
|
43
71
|
}
|
|
44
72
|
|
|
45
|
-
// ==========================================
|
|
46
|
-
// ANALYZE HANDLERS
|
|
47
|
-
// ==========================================
|
|
48
|
-
|
|
49
73
|
export async function testAnalyze(event) {
|
|
50
74
|
const btn = event.target.closest('.btn') || event.target.closest('.btn-icon-head');
|
|
51
|
-
const
|
|
75
|
+
const pathInput = getById('analyze-path');
|
|
76
|
+
if (!pathInput) return;
|
|
77
|
+
|
|
78
|
+
const path = pathInput.value;
|
|
52
79
|
|
|
53
80
|
showLoading(btn, btn.innerHTML);
|
|
54
81
|
try {
|
|
@@ -78,11 +105,12 @@ export async function testAnalyze(event) {
|
|
|
78
105
|
|
|
79
106
|
export async function copyAnalyzeResult(event) {
|
|
80
107
|
const copyBtn = event.target.closest('.btn-copy');
|
|
81
|
-
const copyIcon =
|
|
82
|
-
const copyText =
|
|
108
|
+
const copyIcon = getById('analyze-copy-icon');
|
|
109
|
+
const copyText = getById('analyze-copy-text');
|
|
110
|
+
const pathInput = getById('analyze-path');
|
|
83
111
|
|
|
84
|
-
if (!lastAnalyzeResult) {
|
|
85
|
-
const path =
|
|
112
|
+
if (!lastAnalyzeResult && pathInput) {
|
|
113
|
+
const path = pathInput.value;
|
|
86
114
|
showLoading(copyBtn, copyBtn.innerHTML);
|
|
87
115
|
try {
|
|
88
116
|
lastAnalyzeResult = await analyzeProject(path);
|
|
@@ -103,13 +131,11 @@ export async function copyAnalyzeResult(event) {
|
|
|
103
131
|
}
|
|
104
132
|
}
|
|
105
133
|
|
|
106
|
-
// ==========================================
|
|
107
|
-
// EXECUTE HANDLERS
|
|
108
|
-
// ==========================================
|
|
109
|
-
|
|
110
134
|
export async function testExecute(event) {
|
|
111
135
|
const btn = event.target.closest('.btn');
|
|
112
|
-
const bashInput =
|
|
136
|
+
const bashInput = getById('execute-bash');
|
|
137
|
+
if (!bashInput) return;
|
|
138
|
+
|
|
113
139
|
const bash = bashInput.value;
|
|
114
140
|
|
|
115
141
|
if (!bash.trim()) {
|
|
@@ -131,15 +157,16 @@ export async function testExecute(event) {
|
|
|
131
157
|
}
|
|
132
158
|
|
|
133
159
|
export async function executeFromClipboard(event) {
|
|
134
|
-
const btn = event
|
|
135
|
-
const bashInput =
|
|
160
|
+
const btn = event?.target?.closest('.btn');
|
|
161
|
+
const bashInput = getById('execute-bash');
|
|
162
|
+
if (!bashInput) return;
|
|
136
163
|
|
|
137
|
-
showLoading(btn, btn.innerHTML);
|
|
164
|
+
if (btn) showLoading(btn, btn.innerHTML);
|
|
138
165
|
try {
|
|
139
166
|
const clipboardText = await readFromClipboard();
|
|
140
167
|
if (!clipboardText || !clipboardText.trim()) {
|
|
141
168
|
showToast('Clipboard trống!', 'error');
|
|
142
|
-
resetButton(btn);
|
|
169
|
+
if (btn) resetButton(btn);
|
|
143
170
|
return;
|
|
144
171
|
}
|
|
145
172
|
bashInput.value = clipboardText;
|
|
@@ -160,13 +187,9 @@ export async function executeFromClipboard(event) {
|
|
|
160
187
|
showToast('Lỗi: ' + err.message, 'error');
|
|
161
188
|
}
|
|
162
189
|
}
|
|
163
|
-
resetButton(btn);
|
|
190
|
+
if (btn) resetButton(btn);
|
|
164
191
|
}
|
|
165
192
|
|
|
166
|
-
// ==========================================
|
|
167
|
-
// EXPORT TO WINDOW (GLOBAL)
|
|
168
|
-
// ==========================================
|
|
169
|
-
|
|
170
193
|
window.toggleSystemPrompt = toggleSystemPrompt;
|
|
171
194
|
window.copySystemPrompt = copySystemPrompt;
|
|
172
195
|
window.copySystemPromptFromHeader = copySystemPromptFromHeader;
|
|
@@ -174,9 +197,3 @@ window.testAnalyze = testAnalyze;
|
|
|
174
197
|
window.copyAnalyzeResult = copyAnalyzeResult;
|
|
175
198
|
window.testExecute = testExecute;
|
|
176
199
|
window.executeFromClipboard = executeFromClipboard;
|
|
177
|
-
|
|
178
|
-
// Map Structure handlers from feature module to window
|
|
179
|
-
window.testStructure = handleStructureView;
|
|
180
|
-
window.toggleFolder = handleToggleFolder;
|
|
181
|
-
window.handleCheckboxChange = handleCheckboxChange;
|
|
182
|
-
window.copySelectedStructure = handleCopySelected;
|
|
@@ -1,100 +1,66 @@
|
|
|
1
|
-
|
|
2
|
-
import { SYSTEM_PROMPT } from './config.js';
|
|
1
|
+
import { API_BASE, SYSTEM_PROMPT } from './config.js';
|
|
3
2
|
import { checkHealth } from './api.js';
|
|
4
|
-
import './handlers.js';
|
|
5
|
-
import { showToast,
|
|
6
|
-
import { initIframeManager } from './features/iframe-manager.js';
|
|
3
|
+
import { initEventHandlers } from './handlers.js';
|
|
4
|
+
import { showToast, getById, setRoot, qs } from './utils.js';
|
|
7
5
|
import { initGitView } from './features/git-view.js';
|
|
8
|
-
import { initTerminal
|
|
6
|
+
import { initTerminal } from './features/terminal.js';
|
|
9
7
|
import { initEditorTabs } from './features/editor-tabs.js';
|
|
10
|
-
|
|
8
|
+
// REMOVED: initMonaco
|
|
11
9
|
import { initResizeHandler } from './features/resize.js';
|
|
12
10
|
import { initSavedCommands } from './features/commands.js';
|
|
13
11
|
import { initProjectSwitcher } from './features/project-switcher.js';
|
|
12
|
+
import './features/structure.js';
|
|
13
|
+
import { initBubble } from './features/bubble.js';
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
document.getElementById('prompt-text').textContent = SYSTEM_PROMPT;
|
|
18
|
-
|
|
19
|
-
// Check server status
|
|
20
|
-
await checkServerStatus();
|
|
15
|
+
export async function initMain() {
|
|
16
|
+
console.log('VG Coder: Starting Main Logic...');
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
// Load Extension Path
|
|
29
|
-
loadExtensionPath();
|
|
30
|
-
|
|
31
|
-
// Initialize Iframe Manager
|
|
32
|
-
initIframeManager();
|
|
33
|
-
|
|
34
|
-
// Initialize Git View
|
|
35
|
-
initGitView();
|
|
36
|
-
|
|
37
|
-
// Initialize Terminal System
|
|
38
|
-
initTerminal();
|
|
18
|
+
try {
|
|
19
|
+
const promptEl = getById('prompt-text');
|
|
20
|
+
if (promptEl) promptEl.textContent = SYSTEM_PROMPT;
|
|
21
|
+
|
|
22
|
+
await checkServerStatus();
|
|
23
|
+
await loadProjectInfo();
|
|
39
24
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
25
|
+
initTheme();
|
|
26
|
+
loadExtensionPath();
|
|
27
|
+
|
|
28
|
+
// Initialize event handlers FIRST (before bubble which dispatches events)
|
|
29
|
+
initEventHandlers();
|
|
30
|
+
|
|
31
|
+
initGitView();
|
|
32
|
+
initTerminal();
|
|
33
|
+
initEditorTabs();
|
|
34
|
+
initResizeHandler();
|
|
35
|
+
initSavedCommands();
|
|
36
|
+
await initProjectSwitcher();
|
|
37
|
+
|
|
38
|
+
// Init Bubble (will use event protocol)
|
|
39
|
+
initBubble();
|
|
44
40
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// Initialize Saved Commands
|
|
49
|
-
initSavedCommands();
|
|
50
|
-
|
|
51
|
-
// Initialize Project Switcher
|
|
52
|
-
await initProjectSwitcher();
|
|
53
|
-
|
|
54
|
-
// Set default tab to AI Assistant
|
|
55
|
-
if (window.switchTab) {
|
|
56
|
-
window.switchTab('ai-assistant');
|
|
41
|
+
console.log('✅ VG Coder: Initialization Complete');
|
|
42
|
+
} catch (e) {
|
|
43
|
+
console.error('VG Coder Init Failed:', e);
|
|
57
44
|
}
|
|
58
|
-
}
|
|
45
|
+
}
|
|
59
46
|
|
|
60
|
-
//
|
|
47
|
+
// ... (Giữ nguyên các hàm helper khác: switchProject, loadProjectInfo, initTheme...)
|
|
48
|
+
// Để ngắn gọn, tôi copy lại phần còn lại của main.js
|
|
61
49
|
window.addEventListener('project-switched', async (event) => {
|
|
62
|
-
const { projectId, projectName
|
|
63
|
-
console.log(`Project switched to: ${projectName}`);
|
|
64
|
-
|
|
65
|
-
// Reload project info
|
|
50
|
+
const { projectId, projectName } = event.detail;
|
|
66
51
|
await loadProjectInfo();
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// Reload saved commands for new project
|
|
74
|
-
if (window.loadSavedCommands) {
|
|
75
|
-
await window.loadSavedCommands();
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Reset tree view (hide it)
|
|
79
|
-
const treeContainer = document.getElementById('structure-tree');
|
|
80
|
-
if (treeContainer) {
|
|
81
|
-
treeContainer.style.display = 'none';
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Clear tree content
|
|
85
|
-
const treeContent = document.getElementById('tree-content');
|
|
86
|
-
if (treeContent) {
|
|
87
|
-
treeContent.innerHTML = '';
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// TODO: Refresh other context-dependent components
|
|
91
|
-
// - Git view refresh
|
|
52
|
+
if (window.updateTerminalVisibility) window.updateTerminalVisibility(projectId);
|
|
53
|
+
if (window.loadSavedCommands) await window.loadSavedCommands();
|
|
54
|
+
const treeContainer = getById('structure-tree');
|
|
55
|
+
if (treeContainer) treeContainer.style.display = 'none';
|
|
56
|
+
const treeContent = getById('tree-content');
|
|
57
|
+
if (treeContent) treeContent.innerHTML = '';
|
|
92
58
|
});
|
|
93
59
|
|
|
94
60
|
async function checkServerStatus() {
|
|
95
|
-
const statusEl =
|
|
61
|
+
const statusEl = getById('status');
|
|
62
|
+
if (!statusEl) return;
|
|
96
63
|
const isHealthy = await checkHealth();
|
|
97
|
-
|
|
98
64
|
if (isHealthy) {
|
|
99
65
|
statusEl.textContent = '●';
|
|
100
66
|
statusEl.style.background = 'transparent';
|
|
@@ -108,129 +74,59 @@ async function checkServerStatus() {
|
|
|
108
74
|
|
|
109
75
|
async function loadProjectInfo() {
|
|
110
76
|
try {
|
|
111
|
-
|
|
112
|
-
const res = await fetch('/api/info?path=.');
|
|
77
|
+
const res = await fetch(`${API_BASE}/api/info?path=.`);
|
|
113
78
|
const data = await res.json();
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const fullPath = data.path;
|
|
120
|
-
// Handle both Windows (\) and Unix (/) paths
|
|
121
|
-
const folderName = fullPath.split(/[\\/]/).pop();
|
|
122
|
-
|
|
123
|
-
projectNameEl.textContent = folderName;
|
|
124
|
-
projectMetaEl.textContent = `${data.primaryType} • ${fullPath}`;
|
|
125
|
-
|
|
126
|
-
} catch (err) {
|
|
127
|
-
console.error('Failed to load project info:', err);
|
|
128
|
-
document.getElementById('project-name').textContent = 'Unknown Project';
|
|
129
|
-
document.getElementById('project-meta').textContent = 'Error loading info';
|
|
130
|
-
}
|
|
79
|
+
const projectNameEl = getById('project-name');
|
|
80
|
+
const projectMetaEl = getById('project-meta');
|
|
81
|
+
if (projectNameEl) projectNameEl.textContent = data.path.split(/[\\/]/).pop();
|
|
82
|
+
if (projectMetaEl) projectMetaEl.textContent = `${data.primaryType} • ${data.path}`;
|
|
83
|
+
} catch (err) {}
|
|
131
84
|
}
|
|
132
85
|
|
|
133
86
|
function initTheme() {
|
|
134
|
-
const themeBtn =
|
|
87
|
+
const themeBtn = getById('theme-toggle');
|
|
88
|
+
if (!themeBtn) return;
|
|
135
89
|
let currentTheme = localStorage.getItem('theme') || 'light';
|
|
136
|
-
|
|
137
|
-
|
|
90
|
+
applyTheme(currentTheme);
|
|
138
91
|
themeBtn.addEventListener('click', () => {
|
|
139
92
|
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
|
|
140
|
-
document.documentElement.setAttribute('data-theme', newTheme);
|
|
141
93
|
localStorage.setItem('theme', newTheme);
|
|
142
94
|
currentTheme = newTheme;
|
|
143
|
-
|
|
144
|
-
updateMonacoTheme(newTheme);
|
|
95
|
+
applyTheme(newTheme);
|
|
145
96
|
});
|
|
146
97
|
}
|
|
147
98
|
|
|
148
|
-
function
|
|
149
|
-
const themeIcon =
|
|
150
|
-
if (theme === 'dark'
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
themeIcon.textContent = '🌙';
|
|
154
|
-
}
|
|
99
|
+
function applyTheme(theme) {
|
|
100
|
+
const themeIcon = getById('theme-icon');
|
|
101
|
+
if (themeIcon) themeIcon.textContent = theme === 'dark' ? '☀️' : '🌙';
|
|
102
|
+
const wrapper = getById('vg-app-root');
|
|
103
|
+
if (wrapper) wrapper.setAttribute('data-theme', theme);
|
|
155
104
|
}
|
|
156
105
|
|
|
157
106
|
async function loadExtensionPath() {
|
|
158
107
|
try {
|
|
159
|
-
const res = await fetch(
|
|
108
|
+
const res = await fetch(`${API_BASE}/api/extension-path`);
|
|
160
109
|
const data = await res.json();
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
if (input) {
|
|
164
|
-
if (data.exists) input.value = data.path;
|
|
165
|
-
else {
|
|
166
|
-
input.value = "Error: Extension folder not found.";
|
|
167
|
-
input.style.color = "var(--ios-red)";
|
|
168
|
-
}
|
|
169
|
-
}
|
|
110
|
+
const input = getById('extension-path-input-center');
|
|
111
|
+
if (input && data.exists) input.value = data.path;
|
|
170
112
|
} catch (err) {}
|
|
171
113
|
}
|
|
172
114
|
|
|
173
|
-
window.copyExtensionPath = function(event) {
|
|
174
|
-
const input = document.getElementById('extension-path-input-center');
|
|
175
|
-
const btn = event.currentTarget;
|
|
176
|
-
const originalText = btn.textContent;
|
|
177
|
-
|
|
178
|
-
navigator.clipboard.writeText(input.value).then(() => {
|
|
179
|
-
btn.textContent = '✓';
|
|
180
|
-
btn.style.background = 'var(--ios-green)';
|
|
181
|
-
btn.style.color = 'white';
|
|
182
|
-
btn.style.borderColor = 'var(--ios-green)';
|
|
183
|
-
showToast('Đã copy đường dẫn extension', 'success');
|
|
184
|
-
|
|
185
|
-
setTimeout(() => {
|
|
186
|
-
btn.textContent = originalText;
|
|
187
|
-
btn.style.background = '';
|
|
188
|
-
btn.style.color = '';
|
|
189
|
-
btn.style.borderColor = '';
|
|
190
|
-
}, 2000);
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
window.copyChromeUrl = function(event) {
|
|
195
|
-
const input = document.getElementById('chrome-url-input-center');
|
|
196
|
-
const btn = event.currentTarget;
|
|
197
|
-
const originalText = btn.textContent;
|
|
198
|
-
|
|
199
|
-
navigator.clipboard.writeText(input.value).then(() => {
|
|
200
|
-
btn.textContent = '✓';
|
|
201
|
-
btn.style.background = 'var(--ios-green)';
|
|
202
|
-
btn.style.color = 'white';
|
|
203
|
-
btn.style.borderColor = 'var(--ios-green)';
|
|
204
|
-
showToast('Đã copy URL', 'success');
|
|
205
|
-
|
|
206
|
-
setTimeout(() => {
|
|
207
|
-
btn.textContent = originalText;
|
|
208
|
-
btn.style.background = '';
|
|
209
|
-
btn.style.color = '';
|
|
210
|
-
btn.style.borderColor = '';
|
|
211
|
-
}, 2000);
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
|
|
215
115
|
window.stopServer = async function() {
|
|
216
|
-
if (!confirm('Are you sure you want to stop the server?'))
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
|
|
116
|
+
if (!confirm('Are you sure you want to stop the server?')) return;
|
|
220
117
|
try {
|
|
221
|
-
await fetch(
|
|
118
|
+
await fetch(`${API_BASE}/api/shutdown`, { method: 'POST' });
|
|
222
119
|
showToast('Server stopped successfully', 'success');
|
|
223
|
-
|
|
224
|
-
// Show a message that server is stopped
|
|
225
120
|
setTimeout(() => {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
<h2>🛑 Server Stopped</h2>
|
|
229
|
-
<p>You can close this tab now.</p>
|
|
230
|
-
</div>
|
|
231
|
-
`;
|
|
121
|
+
const wrapper = getById('vg-app-root');
|
|
122
|
+
if(wrapper) wrapper.innerHTML = '<div style="display:flex;justify-content:center;align-items:center;height:100vh;"><h2>🛑 Server Stopped</h2></div>';
|
|
232
123
|
}, 1000);
|
|
233
|
-
} catch (error) {
|
|
234
|
-
|
|
235
|
-
|
|
124
|
+
} catch (error) {}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!window.__VG_CODER_ROOT__) {
|
|
128
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
129
|
+
setRoot(document);
|
|
130
|
+
initMain();
|
|
131
|
+
});
|
|
236
132
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { initMain } from './main.js';
|
|
2
|
+
import { setRoot } from './utils.js';
|
|
3
|
+
|
|
4
|
+
// Entry point for Shadow DOM Injector
|
|
5
|
+
(function() {
|
|
6
|
+
console.log('🚀 VG Coder: Initializing in Shadow Context...');
|
|
7
|
+
|
|
8
|
+
// 1. Determine Root (ShadowRoot exposed by gulp wrapper)
|
|
9
|
+
const root = window.__VG_CODER_ROOT__;
|
|
10
|
+
|
|
11
|
+
if (!root) {
|
|
12
|
+
console.error('VG Coder: Root context not found!');
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// 2. Set Context for Utils
|
|
17
|
+
setRoot(root);
|
|
18
|
+
|
|
19
|
+
// 3. Initialize Main App Logic
|
|
20
|
+
initMain();
|
|
21
|
+
})();
|
|
@@ -1,29 +1,57 @@
|
|
|
1
|
-
// UI Utility Functions
|
|
1
|
+
// UI Utility Functions & DOM Helpers
|
|
2
|
+
|
|
3
|
+
// --- Context Management ---
|
|
4
|
+
let _root = document;
|
|
5
|
+
|
|
6
|
+
export function setRoot(rootElement) {
|
|
7
|
+
_root = rootElement;
|
|
8
|
+
// Update global reference if in Shadow DOM
|
|
9
|
+
if (rootElement.host) {
|
|
10
|
+
window.__VG_CODER_ROOT__ = rootElement;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getRoot() {
|
|
15
|
+
return _root;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// DOM Helpers to replace document.*
|
|
19
|
+
export function qs(selector) {
|
|
20
|
+
return _root.querySelector(selector);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function qsa(selector) {
|
|
24
|
+
return _root.querySelectorAll(selector);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getById(id) {
|
|
28
|
+
// ShadowRoot doesn't always support getElementById in standard way, querySelector is safer
|
|
29
|
+
return _root.querySelector('#' + id);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// --- UI Utils ---
|
|
2
33
|
|
|
3
34
|
/**
|
|
4
35
|
* Display a toast notification
|
|
5
|
-
* @param {string} message - Message to display
|
|
6
|
-
* @param {string} type - Type of toast: 'success', 'error', 'info'
|
|
7
36
|
*/
|
|
8
37
|
export function showToast(message, type = 'success') {
|
|
9
|
-
const toast =
|
|
10
|
-
|
|
38
|
+
const toast = getById('toast');
|
|
39
|
+
if (!toast) return;
|
|
40
|
+
|
|
41
|
+
// Reset text content
|
|
11
42
|
toast.textContent = message;
|
|
12
43
|
toast.className = `toast ${type}`;
|
|
13
44
|
toast.classList.add('show');
|
|
14
45
|
|
|
15
|
-
// Clear previous timeout if exists
|
|
16
46
|
if (toast.timeoutId) clearTimeout(toast.timeoutId);
|
|
17
|
-
|
|
18
47
|
toast.timeoutId = setTimeout(() => toast.classList.remove('show'), 3000);
|
|
19
48
|
}
|
|
20
49
|
|
|
21
50
|
/**
|
|
22
51
|
* Show loading state on button
|
|
23
|
-
* @param {HTMLElement} button - Button element
|
|
24
|
-
* @param {string} originalText - Original button HTML
|
|
25
52
|
*/
|
|
26
53
|
export function showLoading(button, originalText) {
|
|
54
|
+
if (!button) return;
|
|
27
55
|
button.disabled = true;
|
|
28
56
|
button.innerHTML = '<span class="loading"></span>';
|
|
29
57
|
button.dataset.originalText = originalText;
|
|
@@ -31,50 +59,42 @@ export function showLoading(button, originalText) {
|
|
|
31
59
|
|
|
32
60
|
/**
|
|
33
61
|
* Reset button to original state
|
|
34
|
-
* @param {HTMLElement} button - Button element
|
|
35
62
|
*/
|
|
36
63
|
export function resetButton(button) {
|
|
64
|
+
if (!button) return;
|
|
37
65
|
button.disabled = false;
|
|
38
66
|
const originalText = button.dataset.originalText;
|
|
39
|
-
button.innerHTML = originalText;
|
|
67
|
+
if (originalText) button.innerHTML = originalText;
|
|
40
68
|
}
|
|
41
69
|
|
|
42
70
|
/**
|
|
43
|
-
* Display API response
|
|
44
|
-
* @param {string} elementId - ID of response element
|
|
45
|
-
* @param {Object} data - Response data
|
|
46
|
-
* @param {boolean} isError - Whether this is an error response
|
|
71
|
+
* Display API response
|
|
47
72
|
*/
|
|
48
73
|
export function showResponse(elementId, data, isError = false) {
|
|
49
|
-
const el =
|
|
74
|
+
const el = getById(elementId);
|
|
75
|
+
if (!el) return;
|
|
50
76
|
el.className = 'response show ' + (isError ? 'error' : 'success');
|
|
51
77
|
el.innerHTML = '<pre>' + JSON.stringify(data, null, 2) + '</pre>';
|
|
52
78
|
}
|
|
53
79
|
|
|
54
80
|
/**
|
|
55
81
|
* Update button state to show copied status
|
|
56
|
-
* @param {HTMLElement} button - Button element
|
|
57
|
-
* @param {HTMLElement} icon - Icon element
|
|
58
|
-
* @param {HTMLElement} text - Text element
|
|
59
|
-
* @param {string} originalIcon - Original icon text
|
|
60
|
-
* @param {string} originalText - Original button text
|
|
61
82
|
*/
|
|
62
83
|
export function showCopiedState(button, icon, text, originalIcon, originalText) {
|
|
84
|
+
if (!button) return;
|
|
63
85
|
button.classList.add('copied');
|
|
64
|
-
icon.textContent = '✓';
|
|
65
|
-
text.textContent = 'Copied';
|
|
86
|
+
if (icon) icon.textContent = '✓';
|
|
87
|
+
if (text) text.textContent = 'Copied';
|
|
66
88
|
|
|
67
89
|
setTimeout(() => {
|
|
68
90
|
button.classList.remove('copied');
|
|
69
|
-
icon.textContent = originalIcon;
|
|
70
|
-
text.textContent = originalText;
|
|
91
|
+
if (icon) icon.textContent = originalIcon;
|
|
92
|
+
if (text) text.textContent = originalText;
|
|
71
93
|
}, 2000);
|
|
72
94
|
}
|
|
73
95
|
|
|
74
96
|
/**
|
|
75
|
-
* Format number with commas
|
|
76
|
-
* @param {number} num - Number to format
|
|
77
|
-
* @returns {string} Formatted string
|
|
97
|
+
* Format number with commas
|
|
78
98
|
*/
|
|
79
99
|
export function formatNumber(num) {
|
|
80
100
|
if (num === undefined || num === null) return '0';
|