@gxp-dev/tools 2.0.6 → 2.0.8

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.
Files changed (100) hide show
  1. package/bin/lib/commands/build.js +18 -12
  2. package/browser-extensions/README.md +1 -0
  3. package/browser-extensions/chrome/background.js +857 -0
  4. package/browser-extensions/chrome/content.js +51 -0
  5. package/browser-extensions/chrome/devtools.html +9 -0
  6. package/browser-extensions/chrome/devtools.js +23 -0
  7. package/browser-extensions/chrome/icons/gx_off_128.png +0 -0
  8. package/browser-extensions/chrome/icons/gx_off_16.png +0 -0
  9. package/browser-extensions/chrome/icons/gx_off_32.png +0 -0
  10. package/browser-extensions/chrome/icons/gx_off_64.png +0 -0
  11. package/browser-extensions/chrome/icons/gx_on_128.png +0 -0
  12. package/browser-extensions/chrome/icons/gx_on_16.png +0 -0
  13. package/browser-extensions/chrome/icons/gx_on_32.png +0 -0
  14. package/browser-extensions/chrome/icons/gx_on_64.png +0 -0
  15. package/browser-extensions/chrome/inspector.js +1087 -0
  16. package/browser-extensions/chrome/manifest.json +70 -0
  17. package/browser-extensions/chrome/panel.html +638 -0
  18. package/browser-extensions/chrome/panel.js +862 -0
  19. package/browser-extensions/chrome/popup.html +399 -0
  20. package/browser-extensions/chrome/popup.js +515 -0
  21. package/browser-extensions/chrome/rules.json +1 -0
  22. package/browser-extensions/chrome/test-chrome.html +145 -0
  23. package/browser-extensions/chrome/test-mixed-content.html +190 -0
  24. package/browser-extensions/chrome/test-uri-pattern.html +199 -0
  25. package/browser-extensions/firefox/README.md +134 -0
  26. package/browser-extensions/firefox/background.js +804 -0
  27. package/browser-extensions/firefox/content.js +120 -0
  28. package/browser-extensions/firefox/debug-errors.html +229 -0
  29. package/browser-extensions/firefox/debug-https.html +113 -0
  30. package/browser-extensions/firefox/devtools.html +9 -0
  31. package/browser-extensions/firefox/devtools.js +24 -0
  32. package/browser-extensions/firefox/icons/gx_off_128.png +0 -0
  33. package/browser-extensions/firefox/icons/gx_off_16.png +0 -0
  34. package/browser-extensions/firefox/icons/gx_off_32.png +0 -0
  35. package/browser-extensions/firefox/icons/gx_off_64.png +0 -0
  36. package/browser-extensions/firefox/icons/gx_on_128.png +0 -0
  37. package/browser-extensions/firefox/icons/gx_on_16.png +0 -0
  38. package/browser-extensions/firefox/icons/gx_on_32.png +0 -0
  39. package/browser-extensions/firefox/icons/gx_on_64.png +0 -0
  40. package/browser-extensions/firefox/inspector.js +1087 -0
  41. package/browser-extensions/firefox/manifest.json +67 -0
  42. package/browser-extensions/firefox/panel.html +638 -0
  43. package/browser-extensions/firefox/panel.js +862 -0
  44. package/browser-extensions/firefox/popup.html +525 -0
  45. package/browser-extensions/firefox/popup.js +536 -0
  46. package/browser-extensions/firefox/test-gramercy.html +126 -0
  47. package/browser-extensions/firefox/test-imports.html +58 -0
  48. package/browser-extensions/firefox/test-masking.html +147 -0
  49. package/browser-extensions/firefox/test-uri-pattern.html +199 -0
  50. package/package.json +7 -2
  51. package/runtime/PortalContainer.vue +326 -0
  52. package/runtime/dev-tools/DevToolsModal.vue +217 -0
  53. package/runtime/dev-tools/LayoutSwitcher.vue +221 -0
  54. package/runtime/dev-tools/MockDataEditor.vue +621 -0
  55. package/runtime/dev-tools/SocketSimulator.vue +562 -0
  56. package/runtime/dev-tools/StoreInspector.vue +644 -0
  57. package/runtime/dev-tools/index.js +6 -0
  58. package/runtime/gxpStringsPlugin.js +428 -0
  59. package/runtime/index.html +22 -0
  60. package/runtime/main.js +32 -0
  61. package/runtime/mock-api/auth-middleware.js +97 -0
  62. package/runtime/mock-api/image-generator.js +221 -0
  63. package/runtime/mock-api/index.js +197 -0
  64. package/runtime/mock-api/response-generator.js +394 -0
  65. package/runtime/mock-api/route-generator.js +323 -0
  66. package/runtime/mock-api/socket-triggers.js +371 -0
  67. package/runtime/mock-api/spec-loader.js +300 -0
  68. package/runtime/server.js +180 -0
  69. package/runtime/stores/gxpPortalConfigStore.js +554 -0
  70. package/runtime/stores/index.js +6 -0
  71. package/runtime/vite-inspector-plugin.js +749 -0
  72. package/runtime/vite-source-tracker-plugin.js +232 -0
  73. package/runtime/vite.config.js +402 -0
  74. package/scripts/launch-chrome.js +90 -0
  75. package/scripts/pack-chrome.js +91 -0
  76. package/socket-events/AiSessionMessageCreated.json +18 -0
  77. package/socket-events/SocialStreamPostCreated.json +24 -0
  78. package/socket-events/SocialStreamPostVariantCompleted.json +23 -0
  79. package/template/README.md +332 -0
  80. package/template/app-manifest.json +32 -0
  81. package/template/dev-assets/images/avatar-placeholder.png +0 -0
  82. package/template/dev-assets/images/background-placeholder.jpg +0 -0
  83. package/template/dev-assets/images/banner-placeholder.jpg +0 -0
  84. package/template/dev-assets/images/icon-placeholder.png +0 -0
  85. package/template/dev-assets/images/logo-placeholder.png +0 -0
  86. package/template/dev-assets/images/product-placeholder.jpg +0 -0
  87. package/template/dev-assets/images/thumbnail-placeholder.jpg +0 -0
  88. package/template/env.example +51 -0
  89. package/template/gitignore +53 -0
  90. package/template/index.html +22 -0
  91. package/template/main.js +28 -0
  92. package/template/src/DemoPage.vue +459 -0
  93. package/template/src/Plugin.vue +38 -0
  94. package/template/src/stores/index.js +9 -0
  95. package/template/src/stores/test-data.json +173 -0
  96. package/template/theme-layouts/AdditionalStyling.css +0 -0
  97. package/template/theme-layouts/PrivateLayout.vue +39 -0
  98. package/template/theme-layouts/PublicLayout.vue +39 -0
  99. package/template/theme-layouts/SystemLayout.vue +39 -0
  100. package/template/vite.config.js +333 -0
@@ -0,0 +1,644 @@
1
+ <template>
2
+ <div class="store-inspector">
3
+ <div class="inspector-section">
4
+ <h3 class="section-title" @click="toggleSection('pluginVars')">
5
+ <span class="toggle-icon">{{ expandedSections.pluginVars ? '▼' : '▶' }}</span>
6
+ Plugin Variables
7
+ <span class="item-count">{{ Object.keys(store.pluginVars || {}).length }}</span>
8
+ </h3>
9
+ <div v-if="expandedSections.pluginVars" class="section-content">
10
+ <div v-if="Object.keys(store.pluginVars || {}).length === 0" class="empty-state">
11
+ No plugin variables defined
12
+ </div>
13
+ <div v-else class="property-list">
14
+ <div
15
+ v-for="(value, key) in store.pluginVars"
16
+ :key="key"
17
+ class="property-item"
18
+ @mouseenter="highlightElements('gxp-settings', key)"
19
+ @mouseleave="clearHighlight()"
20
+ >
21
+ <span class="property-key">{{ key }}</span>
22
+ <input
23
+ v-if="editingKey === `pluginVars.${key}`"
24
+ v-model="editValue"
25
+ class="property-input"
26
+ @blur="saveEdit('pluginVars', key)"
27
+ @keydown.enter="saveEdit('pluginVars', key)"
28
+ @keydown.escape="cancelEdit"
29
+ ref="editInput"
30
+ />
31
+ <span
32
+ v-else
33
+ class="property-value"
34
+ :class="getValueType(value)"
35
+ @dblclick="startEdit('pluginVars', key, value)"
36
+ :title="'Double-click to edit'"
37
+ >
38
+ {{ formatValue(value) }}
39
+ </span>
40
+ </div>
41
+ </div>
42
+ </div>
43
+ </div>
44
+
45
+ <div class="inspector-section">
46
+ <h3 class="section-title" @click="toggleSection('stringsList')">
47
+ <span class="toggle-icon">{{ expandedSections.stringsList ? '▼' : '▶' }}</span>
48
+ Strings List
49
+ <span class="item-count">{{ Object.keys(store.stringsList || {}).length }}</span>
50
+ </h3>
51
+ <div v-if="expandedSections.stringsList" class="section-content">
52
+ <div v-if="Object.keys(store.stringsList || {}).length === 0" class="empty-state">
53
+ No strings defined
54
+ </div>
55
+ <div v-else class="property-list">
56
+ <div
57
+ v-for="(value, key) in store.stringsList"
58
+ :key="key"
59
+ class="property-item"
60
+ @mouseenter="highlightElements('gxp-string', key)"
61
+ @mouseleave="clearHighlight()"
62
+ >
63
+ <span class="property-key">{{ key }}</span>
64
+ <input
65
+ v-if="editingKey === `stringsList.${key}`"
66
+ v-model="editValue"
67
+ class="property-input"
68
+ @blur="saveEdit('stringsList', key)"
69
+ @keydown.enter="saveEdit('stringsList', key)"
70
+ @keydown.escape="cancelEdit"
71
+ />
72
+ <span
73
+ v-else
74
+ class="property-value string"
75
+ @dblclick="startEdit('stringsList', key, value)"
76
+ :title="'Double-click to edit'"
77
+ >
78
+ "{{ value }}"
79
+ </span>
80
+ </div>
81
+ </div>
82
+ </div>
83
+ </div>
84
+
85
+ <div class="inspector-section">
86
+ <h3 class="section-title" @click="toggleSection('assetList')">
87
+ <span class="toggle-icon">{{ expandedSections.assetList ? '▼' : '▶' }}</span>
88
+ Asset List
89
+ <span class="item-count">{{ Object.keys(store.assetList || {}).length }}</span>
90
+ </h3>
91
+ <div v-if="expandedSections.assetList" class="section-content">
92
+ <div v-if="Object.keys(store.assetList || {}).length === 0" class="empty-state">
93
+ No assets defined
94
+ </div>
95
+ <div v-else class="property-list">
96
+ <div
97
+ v-for="(value, key) in store.assetList"
98
+ :key="key"
99
+ class="property-item asset-item"
100
+ @mouseenter="highlightElements('gxp-src', key)"
101
+ @mouseleave="clearHighlight()"
102
+ >
103
+ <span class="property-key">{{ key }}</span>
104
+ <div class="asset-preview">
105
+ <img
106
+ v-if="isImageUrl(value)"
107
+ :src="value"
108
+ class="asset-thumbnail"
109
+ @error="(e) => e.target.style.display = 'none'"
110
+ />
111
+ <span class="property-value string" :title="value">
112
+ {{ truncateUrl(value) }}
113
+ </span>
114
+ </div>
115
+ </div>
116
+ </div>
117
+ </div>
118
+ </div>
119
+
120
+ <div class="inspector-section">
121
+ <h3 class="section-title" @click="toggleSection('triggerState')">
122
+ <span class="toggle-icon">{{ expandedSections.triggerState ? '▼' : '▶' }}</span>
123
+ Trigger State
124
+ <span class="item-count">{{ Object.keys(store.triggerState || {}).length }}</span>
125
+ </h3>
126
+ <div v-if="expandedSections.triggerState" class="section-content">
127
+ <div v-if="Object.keys(store.triggerState || {}).length === 0" class="empty-state">
128
+ No trigger state defined
129
+ </div>
130
+ <div v-else class="property-list">
131
+ <div
132
+ v-for="(value, key) in store.triggerState"
133
+ :key="key"
134
+ class="property-item"
135
+ @mouseenter="highlightElements('gxp-state', key)"
136
+ @mouseleave="clearHighlight()"
137
+ >
138
+ <span class="property-key">{{ key }}</span>
139
+ <span class="property-value" :class="getValueType(value)">
140
+ {{ formatValue(value) }}
141
+ </span>
142
+ </div>
143
+ </div>
144
+ </div>
145
+ </div>
146
+
147
+ <div class="inspector-section">
148
+ <h3 class="section-title" @click="toggleSection('dependencyList')">
149
+ <span class="toggle-icon">{{ expandedSections.dependencyList ? '▼' : '▶' }}</span>
150
+ Dependencies
151
+ <span class="item-count">{{ getDependencyCount() }}</span>
152
+ </h3>
153
+ <div v-if="expandedSections.dependencyList" class="section-content">
154
+ <div v-if="getDependencyCount() === 0" class="empty-state">
155
+ No dependencies defined
156
+ </div>
157
+ <div v-else class="property-list">
158
+ <div
159
+ v-for="(value, key) in store.dependencyList"
160
+ :key="key"
161
+ class="property-item"
162
+ >
163
+ <span class="property-key">{{ key }}</span>
164
+ <input
165
+ v-if="editingKey === `dependencyList.${key}`"
166
+ v-model="editValue"
167
+ class="property-input"
168
+ @blur="saveEdit('dependencyList', key)"
169
+ @keydown.enter="saveEdit('dependencyList', key)"
170
+ @keydown.escape="cancelEdit"
171
+ />
172
+ <span
173
+ v-else
174
+ class="property-value string"
175
+ @dblclick="startEdit('dependencyList', key, value)"
176
+ :title="'Double-click to edit'"
177
+ >
178
+ "{{ value }}"
179
+ </span>
180
+ </div>
181
+ </div>
182
+ </div>
183
+ </div>
184
+
185
+ <div class="inspector-actions">
186
+ <button class="action-btn" @click="refreshStore" title="Refresh store data">
187
+ Refresh
188
+ </button>
189
+ <button class="action-btn" @click="copyStoreToClipboard" title="Copy store state to clipboard">
190
+ Copy JSON
191
+ </button>
192
+ </div>
193
+ </div>
194
+ </template>
195
+
196
+ <script setup>
197
+ import { ref, reactive, nextTick, onUnmounted } from 'vue';
198
+
199
+ const props = defineProps({
200
+ store: {
201
+ type: Object,
202
+ required: true
203
+ }
204
+ });
205
+
206
+ // Element highlighting
207
+ const highlightedElements = ref([]);
208
+ const highlightOverlays = ref([]);
209
+
210
+ const expandedSections = reactive({
211
+ pluginVars: true,
212
+ stringsList: false,
213
+ assetList: false,
214
+ triggerState: false,
215
+ dependencyList: false
216
+ });
217
+
218
+ const editingKey = ref(null);
219
+ const editValue = ref('');
220
+
221
+ function toggleSection(section) {
222
+ expandedSections[section] = !expandedSections[section];
223
+ }
224
+
225
+ function getValueType(value) {
226
+ if (value === null) return 'null';
227
+ if (value === undefined) return 'undefined';
228
+ if (typeof value === 'boolean') return 'boolean';
229
+ if (typeof value === 'number') return 'number';
230
+ if (typeof value === 'string') return 'string';
231
+ if (Array.isArray(value)) return 'array';
232
+ if (typeof value === 'object') return 'object';
233
+ return 'unknown';
234
+ }
235
+
236
+ function formatValue(value) {
237
+ if (value === null) return 'null';
238
+ if (value === undefined) return 'undefined';
239
+ if (typeof value === 'boolean') return value ? 'true' : 'false';
240
+ if (typeof value === 'number') return value.toString();
241
+ if (typeof value === 'string') return `"${value}"`;
242
+ if (Array.isArray(value)) return `Array(${value.length})`;
243
+ if (typeof value === 'object') return `Object(${Object.keys(value).length})`;
244
+ return String(value);
245
+ }
246
+
247
+ function isImageUrl(url) {
248
+ if (typeof url !== 'string') return false;
249
+ return /\.(jpg|jpeg|png|gif|webp|svg)(\?.*)?$/i.test(url);
250
+ }
251
+
252
+ function truncateUrl(url, maxLength = 50) {
253
+ if (typeof url !== 'string') return url;
254
+ if (url.length <= maxLength) return url;
255
+ return url.substring(0, maxLength) + '...';
256
+ }
257
+
258
+ function getDependencyCount() {
259
+ const deps = props.store.dependencyList;
260
+ if (!deps) return 0;
261
+ if (Array.isArray(deps)) return deps.length;
262
+ return Object.keys(deps).length;
263
+ }
264
+
265
+ function startEdit(section, key, value) {
266
+ editingKey.value = `${section}.${key}`;
267
+ editValue.value = typeof value === 'string' ? value : JSON.stringify(value);
268
+ nextTick(() => {
269
+ const input = document.querySelector('.property-input');
270
+ if (input) input.focus();
271
+ });
272
+ }
273
+
274
+ function saveEdit(section, key) {
275
+ if (editingKey.value && props.store[section]) {
276
+ let newValue = editValue.value;
277
+ // Try to parse as JSON for non-string values
278
+ try {
279
+ const parsed = JSON.parse(newValue);
280
+ newValue = parsed;
281
+ } catch {
282
+ // Keep as string if not valid JSON
283
+ }
284
+
285
+ // Use the store's update methods to ensure Vue reactivity triggers properly
286
+ // This will update any gxp-string/gxp-src directives that depend on these values
287
+ if (section === 'stringsList' && typeof props.store.updateString === 'function') {
288
+ props.store.updateString(key, newValue);
289
+ } else if (section === 'pluginVars' && typeof props.store.updateSetting === 'function') {
290
+ props.store.updateSetting(key, newValue);
291
+ } else if (section === 'assetList' && typeof props.store.updateAsset === 'function') {
292
+ props.store.updateAsset(key, newValue);
293
+ } else if (section === 'triggerState' && typeof props.store.updateState === 'function') {
294
+ props.store.updateState(key, newValue);
295
+ } else {
296
+ // Fallback for older stores without update methods
297
+ props.store[section][key] = newValue;
298
+ }
299
+ console.log(`[DevTools] Updated ${section}.${key}:`, newValue);
300
+ }
301
+ cancelEdit();
302
+ }
303
+
304
+ function cancelEdit() {
305
+ editingKey.value = null;
306
+ editValue.value = '';
307
+ }
308
+
309
+ function refreshStore() {
310
+ // Force reactivity update
311
+ console.log('[DevTools] Store refreshed');
312
+ }
313
+
314
+ async function copyStoreToClipboard() {
315
+ const storeData = {
316
+ pluginVars: props.store.pluginVars,
317
+ stringsList: props.store.stringsList,
318
+ assetList: props.store.assetList,
319
+ triggerState: props.store.triggerState,
320
+ dependencyList: props.store.dependencyList
321
+ };
322
+
323
+ try {
324
+ await navigator.clipboard.writeText(JSON.stringify(storeData, null, 2));
325
+ console.log('[DevTools] Store data copied to clipboard');
326
+ } catch (err) {
327
+ console.error('[DevTools] Failed to copy:', err);
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Element highlighting functions
333
+ * Creates overlay boxes that highlight elements using gxp-* attributes
334
+ */
335
+
336
+ // CSS class name for our highlight overlay
337
+ const HIGHLIGHT_CLASS = 'gxp-devtools-highlight-overlay';
338
+
339
+ // Inject highlight styles into the document if not already present
340
+ function ensureHighlightStyles() {
341
+ if (document.getElementById('gxp-devtools-highlight-styles')) return;
342
+
343
+ const style = document.createElement('style');
344
+ style.id = 'gxp-devtools-highlight-styles';
345
+ style.textContent = `
346
+ .${HIGHLIGHT_CLASS} {
347
+ position: fixed;
348
+ pointer-events: none;
349
+ z-index: 99998;
350
+ background: rgba(97, 218, 251, 0.15);
351
+ border: 2px solid #61dafb;
352
+ border-radius: 4px;
353
+ box-shadow: 0 0 10px rgba(97, 218, 251, 0.5), inset 0 0 10px rgba(97, 218, 251, 0.1);
354
+ animation: gxp-highlight-pulse 1.5s ease-in-out infinite;
355
+ }
356
+ .${HIGHLIGHT_CLASS}::before {
357
+ content: attr(data-gxp-key);
358
+ position: absolute;
359
+ top: -22px;
360
+ left: -2px;
361
+ background: #61dafb;
362
+ color: #1e1e1e;
363
+ font-size: 10px;
364
+ font-weight: 600;
365
+ padding: 2px 6px;
366
+ border-radius: 3px 3px 0 0;
367
+ font-family: 'SF Mono', Monaco, 'Courier New', monospace;
368
+ white-space: nowrap;
369
+ }
370
+ @keyframes gxp-highlight-pulse {
371
+ 0%, 100% { box-shadow: 0 0 10px rgba(97, 218, 251, 0.5), inset 0 0 10px rgba(97, 218, 251, 0.1); }
372
+ 50% { box-shadow: 0 0 20px rgba(97, 218, 251, 0.8), inset 0 0 15px rgba(97, 218, 251, 0.2); }
373
+ }
374
+ `;
375
+ document.head.appendChild(style);
376
+ }
377
+
378
+ // Find elements that match a gxp attribute and key
379
+ function findMatchingElements(attribute, key) {
380
+ const elements = [];
381
+
382
+ // For stringsList, look for gxp-string attribute (without gxp-settings, gxp-assets, gxp-state)
383
+ // For pluginVars (gxp-settings), look for gxp-string with gxp-settings modifier
384
+ // For assetList (gxp-src), look for gxp-src attribute
385
+ // For triggerState (gxp-state), look for gxp-string or gxp-src with gxp-state modifier
386
+
387
+ if (attribute === 'gxp-string') {
388
+ // Find elements with gxp-string="key" that don't have modifiers
389
+ document.querySelectorAll(`[gxp-string="${key}"]`).forEach(el => {
390
+ if (!el.hasAttribute('gxp-settings') && !el.hasAttribute('gxp-assets') && !el.hasAttribute('gxp-state')) {
391
+ elements.push(el);
392
+ }
393
+ });
394
+ } else if (attribute === 'gxp-settings') {
395
+ // Find elements with gxp-string="key" AND gxp-settings attribute
396
+ document.querySelectorAll(`[gxp-string="${key}"][gxp-settings]`).forEach(el => {
397
+ elements.push(el);
398
+ });
399
+ } else if (attribute === 'gxp-src') {
400
+ // Find elements with gxp-src="key" that don't have gxp-state modifier
401
+ document.querySelectorAll(`[gxp-src="${key}"]`).forEach(el => {
402
+ if (!el.hasAttribute('gxp-state')) {
403
+ elements.push(el);
404
+ }
405
+ });
406
+ // Also check for gxp-string with gxp-assets modifier
407
+ document.querySelectorAll(`[gxp-string="${key}"][gxp-assets]`).forEach(el => {
408
+ elements.push(el);
409
+ });
410
+ } else if (attribute === 'gxp-state') {
411
+ // Find elements with gxp-state modifier on either gxp-string or gxp-src
412
+ document.querySelectorAll(`[gxp-string="${key}"][gxp-state]`).forEach(el => {
413
+ elements.push(el);
414
+ });
415
+ document.querySelectorAll(`[gxp-src="${key}"][gxp-state]`).forEach(el => {
416
+ elements.push(el);
417
+ });
418
+ }
419
+
420
+ return elements;
421
+ }
422
+
423
+ // Create overlay for an element
424
+ function createOverlay(element, key) {
425
+ const rect = element.getBoundingClientRect();
426
+ const overlay = document.createElement('div');
427
+ overlay.className = HIGHLIGHT_CLASS;
428
+ overlay.setAttribute('data-gxp-key', key);
429
+ overlay.style.top = `${rect.top}px`;
430
+ overlay.style.left = `${rect.left}px`;
431
+ overlay.style.width = `${rect.width}px`;
432
+ overlay.style.height = `${rect.height}px`;
433
+ document.body.appendChild(overlay);
434
+ return overlay;
435
+ }
436
+
437
+ // Highlight elements matching a key
438
+ function highlightElements(attribute, key) {
439
+ ensureHighlightStyles();
440
+ clearHighlight();
441
+
442
+ const elements = findMatchingElements(attribute, key);
443
+ highlightedElements.value = elements;
444
+
445
+ elements.forEach(el => {
446
+ const overlay = createOverlay(el, key);
447
+ highlightOverlays.value.push(overlay);
448
+ });
449
+
450
+ if (elements.length > 0) {
451
+ console.log(`[DevTools] Highlighting ${elements.length} element(s) with ${attribute}="${key}"`);
452
+ }
453
+ }
454
+
455
+ // Clear all highlight overlays
456
+ function clearHighlight() {
457
+ highlightOverlays.value.forEach(overlay => {
458
+ if (overlay && overlay.parentNode) {
459
+ overlay.parentNode.removeChild(overlay);
460
+ }
461
+ });
462
+ highlightOverlays.value = [];
463
+ highlightedElements.value = [];
464
+ }
465
+
466
+ // Clean up on unmount
467
+ onUnmounted(() => {
468
+ clearHighlight();
469
+ });
470
+ </script>
471
+
472
+ <style scoped>
473
+ .store-inspector {
474
+ display: flex;
475
+ flex-direction: column;
476
+ gap: 8px;
477
+ }
478
+
479
+ .inspector-section {
480
+ background: #2d2d2d;
481
+ border-radius: 6px;
482
+ overflow: hidden;
483
+ }
484
+
485
+ .section-title {
486
+ display: flex;
487
+ align-items: center;
488
+ gap: 8px;
489
+ margin: 0;
490
+ padding: 10px 12px;
491
+ font-size: 13px;
492
+ font-weight: 500;
493
+ cursor: pointer;
494
+ user-select: none;
495
+ transition: background 0.2s;
496
+ }
497
+
498
+ .section-title:hover {
499
+ background: #3d3d3d;
500
+ }
501
+
502
+ .toggle-icon {
503
+ font-size: 10px;
504
+ color: #888;
505
+ width: 12px;
506
+ }
507
+
508
+ .item-count {
509
+ margin-left: auto;
510
+ background: #3d3d3d;
511
+ padding: 2px 8px;
512
+ border-radius: 10px;
513
+ font-size: 11px;
514
+ color: #888;
515
+ }
516
+
517
+ .section-content {
518
+ padding: 8px 12px 12px;
519
+ border-top: 1px solid #3d3d3d;
520
+ }
521
+
522
+ .empty-state {
523
+ color: #666;
524
+ font-size: 12px;
525
+ font-style: italic;
526
+ padding: 8px 0;
527
+ }
528
+
529
+ .property-list {
530
+ display: flex;
531
+ flex-direction: column;
532
+ gap: 6px;
533
+ }
534
+
535
+ .property-item {
536
+ display: flex;
537
+ align-items: flex-start;
538
+ gap: 8px;
539
+ font-size: 12px;
540
+ font-family: 'SF Mono', Monaco, 'Courier New', monospace;
541
+ }
542
+
543
+ .property-key {
544
+ color: #9cdcfe;
545
+ min-width: 140px;
546
+ flex-shrink: 0;
547
+ }
548
+
549
+ .property-key::after {
550
+ content: ':';
551
+ color: #888;
552
+ }
553
+
554
+ .property-value {
555
+ word-break: break-all;
556
+ cursor: pointer;
557
+ padding: 2px 4px;
558
+ border-radius: 3px;
559
+ transition: background 0.2s;
560
+ }
561
+
562
+ .property-value:hover {
563
+ background: #3d3d3d;
564
+ }
565
+
566
+ .property-value.string {
567
+ color: #ce9178;
568
+ }
569
+
570
+ .property-value.number {
571
+ color: #b5cea8;
572
+ }
573
+
574
+ .property-value.boolean {
575
+ color: #569cd6;
576
+ }
577
+
578
+ .property-value.null,
579
+ .property-value.undefined {
580
+ color: #808080;
581
+ font-style: italic;
582
+ }
583
+
584
+ .property-value.object,
585
+ .property-value.array {
586
+ color: #dcdcaa;
587
+ }
588
+
589
+ .property-input {
590
+ flex: 1;
591
+ background: #3d3d3d;
592
+ border: 1px solid #61dafb;
593
+ color: #e0e0e0;
594
+ padding: 4px 8px;
595
+ border-radius: 3px;
596
+ font-family: inherit;
597
+ font-size: 12px;
598
+ outline: none;
599
+ }
600
+
601
+ .asset-item {
602
+ flex-direction: column;
603
+ gap: 4px;
604
+ }
605
+
606
+ .asset-preview {
607
+ display: flex;
608
+ align-items: center;
609
+ gap: 8px;
610
+ padding-left: 148px;
611
+ }
612
+
613
+ .asset-thumbnail {
614
+ width: 32px;
615
+ height: 32px;
616
+ object-fit: cover;
617
+ border-radius: 4px;
618
+ background: #3d3d3d;
619
+ }
620
+
621
+ .inspector-actions {
622
+ display: flex;
623
+ gap: 8px;
624
+ margin-top: 12px;
625
+ padding-top: 12px;
626
+ border-top: 1px solid #3d3d3d;
627
+ }
628
+
629
+ .action-btn {
630
+ background: #3d3d3d;
631
+ border: none;
632
+ color: #e0e0e0;
633
+ padding: 8px 16px;
634
+ border-radius: 4px;
635
+ cursor: pointer;
636
+ font-size: 12px;
637
+ transition: all 0.2s;
638
+ }
639
+
640
+ .action-btn:hover {
641
+ background: #4d4d4d;
642
+ color: #61dafb;
643
+ }
644
+ </style>
@@ -0,0 +1,6 @@
1
+ // GxP Dev Tools Components
2
+ export { default as DevToolsModal } from './DevToolsModal.vue';
3
+ export { default as StoreInspector } from './StoreInspector.vue';
4
+ export { default as LayoutSwitcher } from './LayoutSwitcher.vue';
5
+ export { default as SocketSimulator } from './SocketSimulator.vue';
6
+ export { default as MockDataEditor } from './MockDataEditor.vue';