@gxp-dev/tools 2.0.62 → 2.0.64
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 +32 -31
- package/bin/gx-devtools.js +74 -54
- package/bin/lib/cli.js +23 -21
- package/bin/lib/commands/add-dependency.js +366 -325
- package/bin/lib/commands/assets.js +137 -139
- package/bin/lib/commands/build.js +169 -174
- package/bin/lib/commands/datastore.js +181 -183
- package/bin/lib/commands/dev.js +127 -131
- package/bin/lib/commands/extensions.js +147 -149
- package/bin/lib/commands/extract-config.js +73 -67
- package/bin/lib/commands/index.js +12 -12
- package/bin/lib/commands/init.js +342 -240
- package/bin/lib/commands/publish.js +69 -75
- package/bin/lib/commands/socket.js +69 -69
- package/bin/lib/commands/ssl.js +14 -14
- package/bin/lib/constants.js +10 -24
- package/bin/lib/tui/App.tsx +761 -705
- package/bin/lib/tui/components/AIPanel.tsx +191 -171
- package/bin/lib/tui/components/CommandInput.tsx +394 -343
- package/bin/lib/tui/components/GeminiPanel.tsx +175 -151
- package/bin/lib/tui/components/Header.tsx +23 -21
- package/bin/lib/tui/components/LogPanel.tsx +244 -220
- package/bin/lib/tui/components/TabBar.tsx +50 -48
- package/bin/lib/tui/components/WelcomeScreen.tsx +126 -71
- package/bin/lib/tui/index.tsx +37 -39
- package/bin/lib/tui/services/AIService.ts +518 -462
- package/bin/lib/tui/services/ExtensionService.ts +140 -129
- package/bin/lib/tui/services/GeminiService.ts +367 -337
- package/bin/lib/tui/services/ServiceManager.ts +344 -322
- package/bin/lib/tui/services/SocketService.ts +168 -168
- package/bin/lib/tui/services/ViteService.ts +88 -88
- package/bin/lib/tui/services/index.ts +47 -22
- package/bin/lib/utils/ai-scaffold.js +291 -280
- package/bin/lib/utils/extract-config.js +157 -140
- package/bin/lib/utils/files.js +82 -86
- package/bin/lib/utils/index.js +7 -7
- package/bin/lib/utils/paths.js +34 -34
- package/bin/lib/utils/prompts.js +194 -169
- package/bin/lib/utils/ssl.js +79 -81
- package/browser-extensions/README.md +0 -1
- package/browser-extensions/chrome/background.js +244 -237
- package/browser-extensions/chrome/content.js +32 -29
- package/browser-extensions/chrome/devtools.html +7 -7
- package/browser-extensions/chrome/devtools.js +19 -19
- package/browser-extensions/chrome/inspector.js +802 -767
- package/browser-extensions/chrome/manifest.json +71 -63
- package/browser-extensions/chrome/panel.html +674 -636
- package/browser-extensions/chrome/panel.js +722 -712
- package/browser-extensions/chrome/popup.html +586 -543
- package/browser-extensions/chrome/popup.js +282 -244
- package/browser-extensions/chrome/rules.json +1 -1
- package/browser-extensions/chrome/test-chrome.html +216 -136
- package/browser-extensions/chrome/test-mixed-content.html +284 -189
- package/browser-extensions/chrome/test-uri-pattern.html +221 -198
- package/browser-extensions/firefox/README.md +9 -6
- package/browser-extensions/firefox/background.js +221 -218
- package/browser-extensions/firefox/content.js +55 -52
- package/browser-extensions/firefox/debug-errors.html +386 -228
- package/browser-extensions/firefox/debug-https.html +153 -105
- package/browser-extensions/firefox/devtools.html +7 -7
- package/browser-extensions/firefox/devtools.js +23 -20
- package/browser-extensions/firefox/inspector.js +802 -767
- package/browser-extensions/firefox/manifest.json +68 -68
- package/browser-extensions/firefox/panel.html +674 -636
- package/browser-extensions/firefox/panel.js +722 -712
- package/browser-extensions/firefox/popup.html +572 -535
- package/browser-extensions/firefox/popup.js +281 -236
- package/browser-extensions/firefox/test-gramercy.html +170 -125
- package/browser-extensions/firefox/test-imports.html +59 -55
- package/browser-extensions/firefox/test-masking.html +231 -140
- package/browser-extensions/firefox/test-uri-pattern.html +221 -198
- package/dist/tui/App.d.ts +1 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +154 -150
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/components/AIPanel.d.ts.map +1 -1
- package/dist/tui/components/AIPanel.js +42 -35
- package/dist/tui/components/AIPanel.js.map +1 -1
- package/dist/tui/components/CommandInput.d.ts +1 -1
- package/dist/tui/components/CommandInput.d.ts.map +1 -1
- package/dist/tui/components/CommandInput.js +92 -62
- package/dist/tui/components/CommandInput.js.map +1 -1
- package/dist/tui/components/GeminiPanel.d.ts.map +1 -1
- package/dist/tui/components/GeminiPanel.js +37 -30
- package/dist/tui/components/GeminiPanel.js.map +1 -1
- package/dist/tui/components/Header.d.ts.map +1 -1
- package/dist/tui/components/Header.js +1 -1
- package/dist/tui/components/Header.js.map +1 -1
- package/dist/tui/components/LogPanel.d.ts +1 -1
- package/dist/tui/components/LogPanel.d.ts.map +1 -1
- package/dist/tui/components/LogPanel.js +26 -24
- package/dist/tui/components/LogPanel.js.map +1 -1
- package/dist/tui/components/TabBar.d.ts +2 -2
- package/dist/tui/components/TabBar.d.ts.map +1 -1
- package/dist/tui/components/TabBar.js +11 -11
- package/dist/tui/components/TabBar.js.map +1 -1
- package/dist/tui/components/WelcomeScreen.d.ts.map +1 -1
- package/dist/tui/components/WelcomeScreen.js +6 -6
- package/dist/tui/components/WelcomeScreen.js.map +1 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +8 -8
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/services/AIService.d.ts +2 -2
- package/dist/tui/services/AIService.d.ts.map +1 -1
- package/dist/tui/services/AIService.js +165 -125
- package/dist/tui/services/AIService.js.map +1 -1
- package/dist/tui/services/ExtensionService.d.ts +1 -1
- package/dist/tui/services/ExtensionService.d.ts.map +1 -1
- package/dist/tui/services/ExtensionService.js +33 -26
- package/dist/tui/services/ExtensionService.js.map +1 -1
- package/dist/tui/services/GeminiService.d.ts +1 -1
- package/dist/tui/services/GeminiService.d.ts.map +1 -1
- package/dist/tui/services/GeminiService.js +87 -76
- package/dist/tui/services/GeminiService.js.map +1 -1
- package/dist/tui/services/ServiceManager.d.ts +3 -3
- package/dist/tui/services/ServiceManager.d.ts.map +1 -1
- package/dist/tui/services/ServiceManager.js +72 -58
- package/dist/tui/services/ServiceManager.js.map +1 -1
- package/dist/tui/services/SocketService.d.ts.map +1 -1
- package/dist/tui/services/SocketService.js +32 -32
- package/dist/tui/services/SocketService.js.map +1 -1
- package/dist/tui/services/ViteService.d.ts.map +1 -1
- package/dist/tui/services/ViteService.js +26 -28
- package/dist/tui/services/ViteService.js.map +1 -1
- package/dist/tui/services/index.d.ts +6 -6
- package/dist/tui/services/index.d.ts.map +1 -1
- package/dist/tui/services/index.js +6 -6
- package/dist/tui/services/index.js.map +1 -1
- package/mcp/gxp-api-server.js +83 -81
- package/package.json +109 -93
- package/runtime/PortalContainer.vue +258 -234
- package/runtime/dev-tools/DevToolsModal.vue +153 -155
- package/runtime/dev-tools/LayoutSwitcher.vue +144 -140
- package/runtime/dev-tools/MockDataEditor.vue +456 -433
- package/runtime/dev-tools/SocketSimulator.vue +379 -371
- package/runtime/dev-tools/StoreInspector.vue +517 -455
- package/runtime/dev-tools/index.js +5 -5
- package/runtime/fallback-layouts/PrivateLayout.vue +2 -2
- package/runtime/fallback-layouts/PublicLayout.vue +2 -2
- package/runtime/fallback-layouts/SystemLayout.vue +2 -2
- package/runtime/gxpStringsPlugin.js +159 -134
- package/runtime/index.html +17 -19
- package/runtime/main.js +24 -22
- package/runtime/mock-api/auth-middleware.js +15 -15
- package/runtime/mock-api/image-generator.js +46 -46
- package/runtime/mock-api/index.js +55 -55
- package/runtime/mock-api/response-generator.js +116 -105
- package/runtime/mock-api/route-generator.js +107 -84
- package/runtime/mock-api/socket-triggers.js +94 -93
- package/runtime/mock-api/spec-loader.js +79 -80
- package/runtime/package.json +3 -0
- package/runtime/server.js +68 -68
- package/runtime/stores/gxpPortalConfigStore.js +204 -186
- package/runtime/stores/index.js +2 -2
- package/runtime/vite-inspector-plugin.js +858 -707
- package/runtime/vite-source-tracker-plugin.js +132 -113
- package/runtime/vite.config.js +207 -132
- package/scripts/launch-chrome.js +41 -41
- package/scripts/pack-chrome.js +38 -39
- package/socket-events/AiSessionMessageCreated.json +17 -17
- package/socket-events/SocialStreamPostCreated.json +23 -23
- package/socket-events/SocialStreamPostVariantCompleted.json +22 -22
- package/template/.claude/agents/gxp-developer.md +100 -99
- package/template/.claude/settings.json +7 -7
- package/template/AGENTS.md +30 -23
- package/template/GEMINI.md +20 -20
- package/template/README.md +70 -53
- package/template/app-manifest.json +2 -4
- package/template/configuration.json +10 -10
- package/template/default-styling.css +1 -1
- package/template/index.html +18 -20
- package/template/main.js +24 -22
- package/template/src/DemoPage.vue +415 -362
- package/template/src/Plugin.vue +76 -85
- package/template/src/stores/index.js +3 -3
- package/template/src/stores/test-data.json +164 -172
- package/template/theme-layouts/AdditionalStyling.css +50 -50
- package/template/theme-layouts/PrivateLayout.vue +8 -12
- package/template/theme-layouts/PublicLayout.vue +8 -12
- package/template/theme-layouts/SystemLayout.vue +8 -12
- package/template/vite.extend.js +45 -0
- package/template/vite.config.js +0 -409
|
@@ -6,189 +6,189 @@
|
|
|
6
6
|
* and the background script for messaging.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
(function() {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
9
|
+
;(function () {
|
|
10
|
+
"use strict"
|
|
11
|
+
|
|
12
|
+
// Configuration
|
|
13
|
+
const DEV_SERVER_URL = "https://localhost:3060"
|
|
14
|
+
const API_PREFIX = "/__gxp-inspector"
|
|
15
|
+
|
|
16
|
+
// State
|
|
17
|
+
let isSelectMode = false
|
|
18
|
+
let isConnected = false
|
|
19
|
+
let currentComponent = null
|
|
20
|
+
let selectedString = null
|
|
21
|
+
let hasSelection = false // Track if an element is currently selected
|
|
22
|
+
|
|
23
|
+
// DOM Elements
|
|
24
|
+
const statusIndicator = document.getElementById("statusIndicator")
|
|
25
|
+
const selectBtn = document.getElementById("selectBtn")
|
|
26
|
+
const refreshBtn = document.getElementById("refreshBtn")
|
|
27
|
+
const emptyState = document.getElementById("emptyState")
|
|
28
|
+
const inspectorContent = document.getElementById("inspectorContent")
|
|
29
|
+
const componentName = document.getElementById("componentName")
|
|
30
|
+
const componentFile = document.getElementById("componentFile")
|
|
31
|
+
const stringsSection = document.getElementById("stringsSection")
|
|
32
|
+
const stringsCount = document.getElementById("stringsCount")
|
|
33
|
+
const stringsList = document.getElementById("stringsList")
|
|
34
|
+
const extractForm = document.getElementById("extractForm")
|
|
35
|
+
const extractText = document.getElementById("extractText")
|
|
36
|
+
const extractKey = document.getElementById("extractKey")
|
|
37
|
+
const extractFile = document.getElementById("extractFile")
|
|
38
|
+
const cancelExtract = document.getElementById("cancelExtract")
|
|
39
|
+
const doExtract = document.getElementById("doExtract")
|
|
40
|
+
const extractStatus = document.getElementById("extractStatus")
|
|
41
|
+
const propsSection = document.getElementById("propsSection")
|
|
42
|
+
const propsTree = document.getElementById("propsTree")
|
|
43
|
+
const dataSection = document.getElementById("dataSection")
|
|
44
|
+
const dataTree = document.getElementById("dataTree")
|
|
45
|
+
|
|
46
|
+
// Edit form elements
|
|
47
|
+
const editForm = document.getElementById("editForm")
|
|
48
|
+
const editKey = document.getElementById("editKey")
|
|
49
|
+
const editValue = document.getElementById("editValue")
|
|
50
|
+
const editFile = document.getElementById("editFile")
|
|
51
|
+
const cancelEdit = document.getElementById("cancelEdit")
|
|
52
|
+
const doEdit = document.getElementById("doEdit")
|
|
53
|
+
const editStatus = document.getElementById("editStatus")
|
|
54
|
+
|
|
55
|
+
// Track string info for editing
|
|
56
|
+
let currentStringInfo = null
|
|
57
|
+
|
|
58
|
+
// ============================================================
|
|
59
|
+
// API Communication
|
|
60
|
+
// ============================================================
|
|
61
|
+
|
|
62
|
+
async function apiCall(endpoint, options = {}) {
|
|
63
|
+
const url = `${DEV_SERVER_URL}${API_PREFIX}${endpoint}`
|
|
64
|
+
try {
|
|
65
|
+
const response = await fetch(url, {
|
|
66
|
+
...options,
|
|
67
|
+
headers: {
|
|
68
|
+
"Content-Type": "application/json",
|
|
69
|
+
...options.headers,
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
return await response.json()
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error("[GxP Panel] API Error:", error)
|
|
75
|
+
return { success: false, error: error.message }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function checkConnection() {
|
|
80
|
+
try {
|
|
81
|
+
const result = await apiCall("/ping")
|
|
82
|
+
isConnected = result.success
|
|
83
|
+
updateConnectionStatus()
|
|
84
|
+
return isConnected
|
|
85
|
+
} catch {
|
|
86
|
+
isConnected = false
|
|
87
|
+
updateConnectionStatus()
|
|
88
|
+
return false
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function extractString(data) {
|
|
93
|
+
return apiCall("/extract-string", {
|
|
94
|
+
method: "POST",
|
|
95
|
+
body: JSON.stringify(data),
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function lookupString(text, filePath) {
|
|
100
|
+
return apiCall("/lookup-string", {
|
|
101
|
+
method: "POST",
|
|
102
|
+
body: JSON.stringify({ text, filePath }),
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function updateString(data) {
|
|
107
|
+
return apiCall("/update-string", {
|
|
108
|
+
method: "POST",
|
|
109
|
+
body: JSON.stringify(data),
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function getStrings() {
|
|
114
|
+
return apiCall("/strings")
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function analyzeText(text, filePath) {
|
|
118
|
+
return apiCall("/analyze-text", {
|
|
119
|
+
method: "POST",
|
|
120
|
+
body: JSON.stringify({ text, filePath }),
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ============================================================
|
|
125
|
+
// Content Script Communication
|
|
126
|
+
// ============================================================
|
|
127
|
+
|
|
128
|
+
function evalInPage(code) {
|
|
129
|
+
return new Promise((resolve, reject) => {
|
|
130
|
+
chrome.devtools.inspectedWindow.eval(code, (result, exceptionInfo) => {
|
|
131
|
+
if (exceptionInfo) {
|
|
132
|
+
reject(exceptionInfo)
|
|
133
|
+
} else {
|
|
134
|
+
resolve(result)
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function enableInspectorInPage() {
|
|
141
|
+
try {
|
|
142
|
+
await evalInPage(`
|
|
143
143
|
if (window.gxpInspector) {
|
|
144
144
|
window.gxpInspector.enable();
|
|
145
145
|
true;
|
|
146
146
|
} else {
|
|
147
147
|
false;
|
|
148
148
|
}
|
|
149
|
-
`)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
149
|
+
`)
|
|
150
|
+
return true
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error("Failed to enable inspector:", error)
|
|
153
|
+
return false
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function disableInspectorInPage() {
|
|
158
|
+
try {
|
|
159
|
+
await evalInPage(`
|
|
160
160
|
if (window.gxpInspector) {
|
|
161
161
|
window.gxpInspector.disable();
|
|
162
162
|
true;
|
|
163
163
|
} else {
|
|
164
164
|
false;
|
|
165
165
|
}
|
|
166
|
-
`)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
166
|
+
`)
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.error("Failed to disable inspector:", error)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function clearSelectionInPage() {
|
|
173
|
+
try {
|
|
174
|
+
await evalInPage(`
|
|
175
175
|
if (window.gxpInspector) {
|
|
176
176
|
window.gxpInspector.clearSelection();
|
|
177
177
|
true;
|
|
178
178
|
} else {
|
|
179
179
|
false;
|
|
180
180
|
}
|
|
181
|
-
`)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
181
|
+
`)
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.error("Failed to clear selection:", error)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function getSelectedElement() {
|
|
188
|
+
// Uses $0 which is the last selected element in Elements panel
|
|
189
|
+
// or we can use our custom selection from inspector.js
|
|
190
|
+
try {
|
|
191
|
+
const result = await evalInPage(`
|
|
192
192
|
(function() {
|
|
193
193
|
// Try to get from our inspector's selected element
|
|
194
194
|
if (window.__gxpSelectedElement) {
|
|
@@ -305,171 +305,178 @@
|
|
|
305
305
|
}
|
|
306
306
|
return null;
|
|
307
307
|
})()
|
|
308
|
-
`)
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
308
|
+
`)
|
|
309
|
+
return result
|
|
310
|
+
} catch (error) {
|
|
311
|
+
console.error("Failed to get selected element:", error)
|
|
312
|
+
return null
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// ============================================================
|
|
317
|
+
// UI Updates
|
|
318
|
+
// ============================================================
|
|
319
|
+
|
|
320
|
+
function updateConnectionStatus() {
|
|
321
|
+
if (isConnected) {
|
|
322
|
+
statusIndicator.classList.add("connected")
|
|
323
|
+
statusIndicator.title = "Connected to Vite dev server"
|
|
324
|
+
} else {
|
|
325
|
+
statusIndicator.classList.remove("connected")
|
|
326
|
+
statusIndicator.title = "Not connected - start Vite dev server"
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function showEmptyState() {
|
|
331
|
+
emptyState.classList.remove("hidden")
|
|
332
|
+
inspectorContent.classList.add("hidden")
|
|
333
|
+
currentComponent = null
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function showInspectorContent() {
|
|
337
|
+
emptyState.classList.add("hidden")
|
|
338
|
+
inspectorContent.classList.remove("hidden")
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async function updateComponentInfo(data) {
|
|
342
|
+
if (!data) {
|
|
343
|
+
showEmptyState()
|
|
344
|
+
return
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
showInspectorContent()
|
|
348
|
+
currentComponent = data
|
|
349
|
+
|
|
350
|
+
// Update component name and file
|
|
351
|
+
if (data.component) {
|
|
352
|
+
componentName.textContent = `<${data.component.name}>`
|
|
353
|
+
componentFile.textContent = data.component.file || "Unknown file"
|
|
354
|
+
} else {
|
|
355
|
+
componentName.textContent = `<${data.tagName}>`
|
|
356
|
+
componentFile.textContent = "Not a Vue component"
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Build string info list from attribute detection
|
|
360
|
+
const stringInfos = []
|
|
361
|
+
const filePath = data.component?.file || null
|
|
362
|
+
|
|
363
|
+
// Add strings from textsWithAttributes (direct element text with gxp-string detection)
|
|
364
|
+
if (data.textsWithAttributes && data.textsWithAttributes.length > 0) {
|
|
365
|
+
data.textsWithAttributes.forEach((info) => {
|
|
366
|
+
stringInfos.push({
|
|
367
|
+
text: info.text,
|
|
368
|
+
isExtracted: info.isExtracted,
|
|
369
|
+
key: info.gxpStringKey || null,
|
|
370
|
+
// Use injected data-gxp-source attribute if available (from vite plugin)
|
|
371
|
+
isDynamic: info.isDynamic || false,
|
|
372
|
+
expression: info.sourceExpression || null,
|
|
373
|
+
expressionType: info.sourceExpression
|
|
374
|
+
? detectExpressionType(info.sourceExpression)
|
|
375
|
+
: null,
|
|
376
|
+
})
|
|
377
|
+
})
|
|
378
|
+
} else if (data.texts && data.texts.length > 0) {
|
|
379
|
+
// Fallback to plain texts if textsWithAttributes not available
|
|
380
|
+
data.texts.forEach((text) => {
|
|
381
|
+
stringInfos.push({
|
|
382
|
+
text: text,
|
|
383
|
+
isExtracted: data.isExtracted || false,
|
|
384
|
+
key: data.gxpStringKey || null,
|
|
385
|
+
// Check element-level source expression
|
|
386
|
+
isDynamic: data.isDynamic || false,
|
|
387
|
+
expression: data.sourceExpression || null,
|
|
388
|
+
expressionType: data.sourceExpression
|
|
389
|
+
? detectExpressionType(data.sourceExpression)
|
|
390
|
+
: null,
|
|
391
|
+
})
|
|
392
|
+
})
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Also add child elements with gxp-string attributes
|
|
396
|
+
if (data.childGxpStrings && data.childGxpStrings.length > 0) {
|
|
397
|
+
data.childGxpStrings.forEach((child) => {
|
|
398
|
+
// Check if this text is already in the list
|
|
399
|
+
const exists = stringInfos.some(
|
|
400
|
+
(info) => info.text === child.text && info.key === child.key,
|
|
401
|
+
)
|
|
402
|
+
if (!exists) {
|
|
403
|
+
stringInfos.push({
|
|
404
|
+
text: child.text,
|
|
405
|
+
isExtracted: true,
|
|
406
|
+
key: child.key,
|
|
407
|
+
element: child.element,
|
|
408
|
+
isDynamic: false,
|
|
409
|
+
expression: null,
|
|
410
|
+
expressionType: null,
|
|
411
|
+
})
|
|
412
|
+
}
|
|
413
|
+
})
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Analyze each string to check if it's dynamic (from a template expression)
|
|
417
|
+
// Only call API for strings that don't already have source info from data-gxp-source attribute
|
|
418
|
+
if (isConnected && filePath) {
|
|
419
|
+
for (const info of stringInfos) {
|
|
420
|
+
// Skip if already extracted with gxp-string or already has source expression
|
|
421
|
+
if (info.isExtracted || info.isDynamic) continue
|
|
422
|
+
|
|
423
|
+
try {
|
|
424
|
+
const analysis = await analyzeText(info.text, filePath)
|
|
425
|
+
if (analysis.success && analysis.isDynamic) {
|
|
426
|
+
info.isDynamic = true
|
|
427
|
+
info.expression = analysis.expression
|
|
428
|
+
info.expressionType = analysis.expressionType
|
|
429
|
+
info.sourceKey = analysis.sourceKey
|
|
430
|
+
}
|
|
431
|
+
} catch (e) {
|
|
432
|
+
// Ignore analysis errors, treat as static
|
|
433
|
+
console.warn("[GxP Panel] Failed to analyze text:", e)
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Update strings list display
|
|
439
|
+
if (stringInfos.length > 0) {
|
|
440
|
+
stringsSection.classList.remove("hidden")
|
|
441
|
+
stringsCount.textContent = stringInfos.length
|
|
442
|
+
|
|
443
|
+
// Render the strings with their status
|
|
444
|
+
stringsList.innerHTML = stringInfos
|
|
445
|
+
.map((info, index) => {
|
|
446
|
+
let badgeClass, badgeText, actionText, itemClass, showAction
|
|
447
|
+
|
|
448
|
+
if (info.isExtracted) {
|
|
449
|
+
badgeClass = "extracted"
|
|
450
|
+
badgeText = "gxp-string"
|
|
451
|
+
actionText = "Edit"
|
|
452
|
+
itemClass = "string-item extracted"
|
|
453
|
+
showAction = true
|
|
454
|
+
} else if (info.isDynamic) {
|
|
455
|
+
badgeClass = "dynamic"
|
|
456
|
+
badgeText = info.expressionType || "dynamic"
|
|
457
|
+
actionText = ""
|
|
458
|
+
itemClass = "string-item dynamic"
|
|
459
|
+
showAction = false
|
|
460
|
+
} else {
|
|
461
|
+
badgeClass = "raw"
|
|
462
|
+
badgeText = "raw text"
|
|
463
|
+
actionText = "Extract"
|
|
464
|
+
itemClass = "string-item"
|
|
465
|
+
showAction = true
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const expressionHtml = info.expression
|
|
469
|
+
? `<span class="string-expression" title="Source: ${escapeHtml(info.expression)}">${escapeHtml(info.expression)}</span>`
|
|
470
|
+
: ""
|
|
471
|
+
|
|
472
|
+
const actionHtml = showAction
|
|
473
|
+
? `<div class="string-actions"><button class="action-btn" data-action="${info.isExtracted ? "edit" : "extract"}">${actionText}</button></div>`
|
|
474
|
+
: ""
|
|
475
|
+
|
|
476
|
+
return `
|
|
470
477
|
<div class="${itemClass}" data-index="${index}" data-text="${escapeHtml(info.text)}"
|
|
471
|
-
data-extracted="${info.isExtracted}" data-key="${info.key ||
|
|
472
|
-
data-dynamic="${info.isDynamic}" data-expression="${escapeHtml(info.expression ||
|
|
478
|
+
data-extracted="${info.isExtracted}" data-key="${info.key || ""}"
|
|
479
|
+
data-dynamic="${info.isDynamic}" data-expression="${escapeHtml(info.expression || "")}">
|
|
473
480
|
<div class="string-content">
|
|
474
481
|
<span class="string-text">"${escapeHtml(info.text)}"</span>
|
|
475
482
|
${expressionHtml}
|
|
@@ -477,386 +484,389 @@
|
|
|
477
484
|
<span class="string-badge ${badgeClass}">${badgeText}</span>
|
|
478
485
|
${actionHtml}
|
|
479
486
|
</div>
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
487
|
+
`
|
|
488
|
+
})
|
|
489
|
+
.join("")
|
|
490
|
+
|
|
491
|
+
// Add click handlers
|
|
492
|
+
stringsList.querySelectorAll(".string-item").forEach((item) => {
|
|
493
|
+
item.addEventListener("click", () => {
|
|
494
|
+
selectStringItem(item)
|
|
495
|
+
})
|
|
496
|
+
|
|
497
|
+
const actionBtn = item.querySelector(".action-btn")
|
|
498
|
+
if (actionBtn) {
|
|
499
|
+
const action = actionBtn.dataset.action
|
|
500
|
+
|
|
501
|
+
actionBtn.addEventListener("click", (e) => {
|
|
502
|
+
e.stopPropagation()
|
|
503
|
+
if (action === "edit") {
|
|
504
|
+
showEditForm(item.dataset.text, item.dataset.key)
|
|
505
|
+
} else if (action === "extract") {
|
|
506
|
+
showExtractForm(item.dataset.text)
|
|
507
|
+
}
|
|
508
|
+
})
|
|
509
|
+
}
|
|
510
|
+
})
|
|
511
|
+
} else {
|
|
512
|
+
stringsSection.classList.add("hidden")
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Update props
|
|
516
|
+
if (data.component && Object.keys(data.component.props).length > 0) {
|
|
517
|
+
propsSection.classList.remove("hidden")
|
|
518
|
+
propsTree.innerHTML = formatProps(data.component.props)
|
|
519
|
+
} else {
|
|
520
|
+
propsSection.classList.add("hidden")
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Update data
|
|
524
|
+
if (data.component && Object.keys(data.component.data).length > 0) {
|
|
525
|
+
dataSection.classList.remove("hidden")
|
|
526
|
+
dataTree.innerHTML = formatProps(data.component.data)
|
|
527
|
+
} else {
|
|
528
|
+
dataSection.classList.add("hidden")
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Hide extract form
|
|
532
|
+
hideExtractForm()
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function formatProps(obj, indent = 0) {
|
|
536
|
+
let html = ""
|
|
537
|
+
const indentStr = " ".repeat(indent)
|
|
538
|
+
|
|
539
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
540
|
+
const type = typeof value
|
|
541
|
+
let valueHtml = ""
|
|
542
|
+
|
|
543
|
+
if (value === null) {
|
|
544
|
+
valueHtml = '<span class="prop-value boolean">null</span>'
|
|
545
|
+
} else if (type === "boolean") {
|
|
546
|
+
valueHtml = `<span class="prop-value boolean">${value}</span>`
|
|
547
|
+
} else if (type === "number") {
|
|
548
|
+
valueHtml = `<span class="prop-value number">${value}</span>`
|
|
549
|
+
} else if (type === "string") {
|
|
550
|
+
valueHtml = `<span class="prop-value">"${escapeHtml(value)}"</span>`
|
|
551
|
+
} else if (Array.isArray(value)) {
|
|
552
|
+
if (value.length === 0) {
|
|
553
|
+
valueHtml = '<span class="prop-value">[]</span>'
|
|
554
|
+
} else {
|
|
555
|
+
valueHtml = `<span class="prop-value">[${value.length} items]</span>`
|
|
556
|
+
}
|
|
557
|
+
} else if (type === "object") {
|
|
558
|
+
valueHtml = `<span class="prop-value">{...}</span>`
|
|
559
|
+
} else {
|
|
560
|
+
valueHtml = `<span class="prop-value">${escapeHtml(String(value))}</span>`
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
html += `<div class="prop-item">${indentStr}<span class="prop-key">${key}</span>: ${valueHtml}</div>`
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
return html
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function selectStringItem(item) {
|
|
570
|
+
// Remove selection from all items
|
|
571
|
+
stringsList.querySelectorAll(".string-item").forEach((i) => {
|
|
572
|
+
i.classList.remove("selected")
|
|
573
|
+
})
|
|
574
|
+
|
|
575
|
+
// Select this item
|
|
576
|
+
item.classList.add("selected")
|
|
577
|
+
selectedString = item.dataset.text
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function showExtractForm(text) {
|
|
581
|
+
extractForm.classList.remove("hidden")
|
|
582
|
+
extractText.value = text
|
|
583
|
+
extractKey.value = textToKey(text)
|
|
584
|
+
extractFile.value = currentComponent?.component?.file || ""
|
|
585
|
+
extractStatus.classList.add("hidden")
|
|
586
|
+
selectedString = text
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function hideExtractForm() {
|
|
590
|
+
extractForm.classList.add("hidden")
|
|
591
|
+
extractStatus.classList.add("hidden")
|
|
592
|
+
selectedString = null
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function showEditForm(text, key) {
|
|
596
|
+
// Hide extract form if visible
|
|
597
|
+
hideExtractForm()
|
|
598
|
+
|
|
599
|
+
editForm.classList.remove("hidden")
|
|
600
|
+
editKey.value = key || ""
|
|
601
|
+
editValue.value = text
|
|
602
|
+
editFile.value = currentComponent?.component?.file || ""
|
|
603
|
+
editStatus.classList.add("hidden")
|
|
604
|
+
|
|
605
|
+
// Store current string info for the update
|
|
606
|
+
currentStringInfo = {
|
|
607
|
+
oldKey: key,
|
|
608
|
+
text: text,
|
|
609
|
+
filePath: currentComponent?.component?.file || "",
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function hideEditForm() {
|
|
614
|
+
editForm.classList.add("hidden")
|
|
615
|
+
editStatus.classList.add("hidden")
|
|
616
|
+
currentStringInfo = null
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
function showEditStatus(message, type = "info") {
|
|
620
|
+
editStatus.textContent = message
|
|
621
|
+
editStatus.className = `status-message ${type}`
|
|
622
|
+
editStatus.classList.remove("hidden")
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function showStatus(message, type = "info") {
|
|
626
|
+
extractStatus.textContent = message
|
|
627
|
+
extractStatus.className = `status-message ${type}`
|
|
628
|
+
extractStatus.classList.remove("hidden")
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// ============================================================
|
|
632
|
+
// Event Handlers
|
|
633
|
+
// ============================================================
|
|
634
|
+
|
|
635
|
+
selectBtn.addEventListener("click", async () => {
|
|
636
|
+
if (isSelectMode) {
|
|
637
|
+
// Cancel selection mode (clicking during selection)
|
|
638
|
+
isSelectMode = false
|
|
639
|
+
selectBtn.classList.remove("active")
|
|
640
|
+
selectBtn.querySelector("span").textContent = hasSelection
|
|
641
|
+
? "Cancel Selection"
|
|
642
|
+
: "Select Element"
|
|
643
|
+
await disableInspectorInPage()
|
|
644
|
+
} else if (hasSelection) {
|
|
645
|
+
// Clear selection (clicking when element is already selected)
|
|
646
|
+
hasSelection = false
|
|
647
|
+
currentComponent = null
|
|
648
|
+
await clearSelectionInPage()
|
|
649
|
+
showEmptyState()
|
|
650
|
+
selectBtn.querySelector("span").textContent = "Select Element"
|
|
651
|
+
} else {
|
|
652
|
+
// Start selection mode
|
|
653
|
+
isSelectMode = true
|
|
654
|
+
selectBtn.classList.add("active")
|
|
655
|
+
selectBtn.querySelector("span").textContent = "Cancel Selection"
|
|
656
|
+
await enableInspectorInPage()
|
|
657
|
+
}
|
|
658
|
+
})
|
|
659
|
+
|
|
660
|
+
refreshBtn.addEventListener("click", async () => {
|
|
661
|
+
await checkConnection()
|
|
662
|
+
const data = await getSelectedElement()
|
|
663
|
+
updateComponentInfo(data)
|
|
664
|
+
})
|
|
665
|
+
|
|
666
|
+
cancelExtract.addEventListener("click", () => {
|
|
667
|
+
hideExtractForm()
|
|
668
|
+
})
|
|
669
|
+
|
|
670
|
+
cancelEdit.addEventListener("click", () => {
|
|
671
|
+
hideEditForm()
|
|
672
|
+
})
|
|
673
|
+
|
|
674
|
+
doEdit.addEventListener("click", async () => {
|
|
675
|
+
if (!currentStringInfo) {
|
|
676
|
+
showEditStatus("No string selected for editing", "error")
|
|
677
|
+
return
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
const newKey = editKey.value
|
|
681
|
+
const newValue = editValue.value
|
|
682
|
+
const filePath = editFile.value
|
|
683
|
+
|
|
684
|
+
if (!newKey) {
|
|
685
|
+
showEditStatus("String key is required", "error")
|
|
686
|
+
return
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
if (!filePath) {
|
|
690
|
+
showEditStatus("Cannot determine source file", "error")
|
|
691
|
+
return
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
if (!isConnected) {
|
|
695
|
+
showEditStatus("Not connected to Vite dev server", "error")
|
|
696
|
+
return
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
doEdit.disabled = true
|
|
700
|
+
doEdit.textContent = "Updating..."
|
|
701
|
+
|
|
702
|
+
try {
|
|
703
|
+
const result = await updateString({
|
|
704
|
+
oldKey: currentStringInfo.oldKey,
|
|
705
|
+
newKey: newKey,
|
|
706
|
+
newValue: newValue,
|
|
707
|
+
filePath: filePath,
|
|
708
|
+
})
|
|
709
|
+
|
|
710
|
+
if (result.success) {
|
|
711
|
+
showEditStatus(`Success! Updated gxp-string="${newKey}"`, "success")
|
|
712
|
+
|
|
713
|
+
// Refresh the component info to reflect changes
|
|
714
|
+
setTimeout(async () => {
|
|
715
|
+
hideEditForm()
|
|
716
|
+
const data = await getSelectedElement()
|
|
717
|
+
updateComponentInfo(data)
|
|
718
|
+
}, 1500)
|
|
719
|
+
} else {
|
|
720
|
+
showEditStatus(result.error || "Update failed", "error")
|
|
721
|
+
}
|
|
722
|
+
} catch (error) {
|
|
723
|
+
showEditStatus(error.message, "error")
|
|
724
|
+
} finally {
|
|
725
|
+
doEdit.disabled = false
|
|
726
|
+
doEdit.textContent = "Update gxp-string"
|
|
727
|
+
}
|
|
728
|
+
})
|
|
729
|
+
|
|
730
|
+
doExtract.addEventListener("click", async () => {
|
|
731
|
+
const text = extractText.value
|
|
732
|
+
const key = extractKey.value
|
|
733
|
+
const filePath = extractFile.value
|
|
734
|
+
|
|
735
|
+
if (!text || !key) {
|
|
736
|
+
showStatus("Text and key are required", "error")
|
|
737
|
+
return
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (!filePath) {
|
|
741
|
+
showStatus("Cannot determine source file", "error")
|
|
742
|
+
return
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
if (!isConnected) {
|
|
746
|
+
showStatus("Not connected to Vite dev server", "error")
|
|
747
|
+
return
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
doExtract.disabled = true
|
|
751
|
+
doExtract.textContent = "Extracting..."
|
|
752
|
+
|
|
753
|
+
try {
|
|
754
|
+
const result = await extractString({ text, key, filePath })
|
|
755
|
+
|
|
756
|
+
if (result.success) {
|
|
757
|
+
showStatus(`Success! Added gxp-string="${key}" attribute`, "success")
|
|
758
|
+
// Refresh the component info to reflect changes
|
|
759
|
+
setTimeout(async () => {
|
|
760
|
+
hideExtractForm()
|
|
761
|
+
const data = await getSelectedElement()
|
|
762
|
+
updateComponentInfo(data)
|
|
763
|
+
}, 1500)
|
|
764
|
+
} else {
|
|
765
|
+
showStatus(result.error || "Extraction failed", "error")
|
|
766
|
+
}
|
|
767
|
+
} catch (error) {
|
|
768
|
+
showStatus(error.message, "error")
|
|
769
|
+
} finally {
|
|
770
|
+
doExtract.disabled = false
|
|
771
|
+
doExtract.textContent = "Extract to gxp-string"
|
|
772
|
+
}
|
|
773
|
+
})
|
|
774
|
+
|
|
775
|
+
// ============================================================
|
|
776
|
+
// Message Listener for Content Script Updates
|
|
777
|
+
// ============================================================
|
|
778
|
+
|
|
779
|
+
// Create a connection to the background script
|
|
780
|
+
const backgroundConnection = chrome.runtime.connect({
|
|
781
|
+
name: "gxp-devtools-panel",
|
|
782
|
+
})
|
|
783
|
+
|
|
784
|
+
backgroundConnection.postMessage({
|
|
785
|
+
name: "init",
|
|
786
|
+
tabId: chrome.devtools.inspectedWindow.tabId,
|
|
787
|
+
})
|
|
788
|
+
|
|
789
|
+
backgroundConnection.onMessage.addListener((message) => {
|
|
790
|
+
if (message.type === "elementSelected") {
|
|
791
|
+
updateComponentInfo(message.data)
|
|
792
|
+
isSelectMode = false
|
|
793
|
+
hasSelection = true
|
|
794
|
+
selectBtn.classList.remove("active")
|
|
795
|
+
selectBtn.querySelector("span").textContent = "Cancel Selection"
|
|
796
|
+
}
|
|
797
|
+
})
|
|
798
|
+
|
|
799
|
+
// Also listen for selection changes via polling
|
|
800
|
+
// (backup method since content script communication can be tricky)
|
|
801
|
+
setInterval(async () => {
|
|
802
|
+
if (isSelectMode) {
|
|
803
|
+
const data = await getSelectedElement()
|
|
804
|
+
if (data && JSON.stringify(data) !== JSON.stringify(currentComponent)) {
|
|
805
|
+
updateComponentInfo(data)
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}, 500)
|
|
809
|
+
|
|
810
|
+
// ============================================================
|
|
811
|
+
// Utility Functions
|
|
812
|
+
// ============================================================
|
|
813
|
+
|
|
814
|
+
function textToKey(text) {
|
|
815
|
+
return text
|
|
816
|
+
.toLowerCase()
|
|
817
|
+
.replace(/[^a-z0-9\s]/g, "")
|
|
818
|
+
.replace(/\s+/g, "_")
|
|
819
|
+
.substring(0, 40)
|
|
820
|
+
.replace(/_+$/, "")
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Detect the type of expression from a source expression string
|
|
825
|
+
* @param {string} expression - The source expression (e.g., "gxpStore.getString('key')")
|
|
826
|
+
* @returns {string} - The expression type: 'getString', 'store', 'variable', 'computed'
|
|
827
|
+
*/
|
|
828
|
+
function detectExpressionType(expression) {
|
|
829
|
+
if (!expression) return null
|
|
830
|
+
|
|
831
|
+
// Check for getString calls
|
|
832
|
+
if (expression.includes("getString")) {
|
|
833
|
+
return "getString"
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Check for store access
|
|
837
|
+
if (expression.includes("Store") || expression.includes("store.")) {
|
|
838
|
+
return "store"
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// Check for computed/method calls
|
|
842
|
+
if (expression.includes("(")) {
|
|
843
|
+
return "computed"
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// Default to variable
|
|
847
|
+
return "variable"
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function escapeHtml(text) {
|
|
851
|
+
const div = document.createElement("div")
|
|
852
|
+
div.textContent = text
|
|
853
|
+
return div.innerHTML
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// ============================================================
|
|
857
|
+
// Panel Lifecycle
|
|
858
|
+
// ============================================================
|
|
859
|
+
|
|
860
|
+
// Called when panel becomes visible
|
|
861
|
+
window.panelShown = function () {
|
|
862
|
+
checkConnection()
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// Initial setup
|
|
866
|
+
checkConnection()
|
|
867
|
+
|
|
868
|
+
// Check connection periodically
|
|
869
|
+
setInterval(checkConnection, 10000)
|
|
870
|
+
|
|
871
|
+
console.log("[GxP Panel] DevTools panel initialized")
|
|
872
|
+
})()
|