@capillarytech/cap-ui-dev-tools 1.2.0 → 1.3.0
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/package.json
CHANGED
|
@@ -155,6 +155,207 @@ class ReportEnhancer {
|
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Generate logo HTML header with toggle button
|
|
160
|
+
* @private
|
|
161
|
+
* @returns {string} Logo HTML
|
|
162
|
+
*/
|
|
163
|
+
generateLogoHTML() {
|
|
164
|
+
// Inline SVG logo - CapVision branded logo (icon size)
|
|
165
|
+
const logoSVG = `
|
|
166
|
+
<svg width="120" height="80" viewBox="0 0 1600 800" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="display: inline-block; vertical-align: middle;">
|
|
167
|
+
<defs>
|
|
168
|
+
<!-- Neon gradient used for strokes/fills -->
|
|
169
|
+
<linearGradient id="neonGradient" x1="0" x2="1" y1="0" y2="1">
|
|
170
|
+
<stop offset="0%" stop-color="#00D4FF"/>
|
|
171
|
+
<stop offset="25%" stop-color="#3A7CFF"/>
|
|
172
|
+
<stop offset="50%" stop-color="#9A4BFF"/>
|
|
173
|
+
<stop offset="70%" stop-color="#FF4CA3"/>
|
|
174
|
+
<stop offset="85%" stop-color="#FF8E3C"/>
|
|
175
|
+
<stop offset="100%" stop-color="#FFD800"/>
|
|
176
|
+
</linearGradient>
|
|
177
|
+
|
|
178
|
+
<!-- Wordmark gradient (cool blue) -->
|
|
179
|
+
<linearGradient id="wordGradient" x1="0" x2="1">
|
|
180
|
+
<stop offset="0%" stop-color="#19C6FF"/>
|
|
181
|
+
<stop offset="100%" stop-color="#2EA6FF"/>
|
|
182
|
+
</linearGradient>
|
|
183
|
+
|
|
184
|
+
<!-- Soft inner shadow for icon fill -->
|
|
185
|
+
<linearGradient id="iconFill" x1="0" x2="1">
|
|
186
|
+
<stop offset="0%" stop-color="#061022" stop-opacity="0.0"/>
|
|
187
|
+
<stop offset="100%" stop-color="#061022" stop-opacity="0.08"/>
|
|
188
|
+
</linearGradient>
|
|
189
|
+
|
|
190
|
+
<!-- Stroke style for neon lines -->
|
|
191
|
+
<style type="text/css"><![CDATA[
|
|
192
|
+
.neon { stroke: url(#neonGradient); stroke-width:14; stroke-linecap:square; stroke-linejoin:miter; fill: none; }
|
|
193
|
+
.neon-thin { stroke: url(#neonGradient); stroke-width:8; stroke-linecap:square; stroke-linejoin:miter; fill: none; }
|
|
194
|
+
.eye-fill { fill: none; stroke: url(#neonGradient); stroke-width:12; stroke-linecap:square; stroke-linejoin:miter; }
|
|
195
|
+
.circuit { stroke: url(#neonGradient); stroke-width:10; stroke-linecap:square; fill: none; }
|
|
196
|
+
.accent-dot { fill: url(#neonGradient); }
|
|
197
|
+
.word { font-family: "Inter", "Montserrat", sans-serif; font-weight: 700; font-size:120px; fill: url(#wordGradient); letter-spacing: -2px; }
|
|
198
|
+
.bg-texture { opacity: 0.02; }
|
|
199
|
+
]]></style>
|
|
200
|
+
</defs>
|
|
201
|
+
|
|
202
|
+
<!-- Dark background (keeps high contrast for presentation and dashboards) -->
|
|
203
|
+
<rect id="bg" x="0" y="0" width="1600" height="800" fill="#060719" />
|
|
204
|
+
|
|
205
|
+
<!-- subtle grid/texture (very faint) -->
|
|
206
|
+
<g class="bg-texture" transform="translate(0,0)">
|
|
207
|
+
<rect x="0" y="0" width="1600" height="800" fill="url(#iconFill)" />
|
|
208
|
+
</g>
|
|
209
|
+
|
|
210
|
+
<!-- LEFT ICON GROUP -->
|
|
211
|
+
<g id="icon" transform="translate(160,210) scale(1.4)">
|
|
212
|
+
<!-- robot-head-ish rounded rectangle -->
|
|
213
|
+
<rect x="0" y="0" rx="8" ry="8" width="220" height="150" class="neon" />
|
|
214
|
+
|
|
215
|
+
<!-- antenna -->
|
|
216
|
+
<line x1="110" y1="-30" x2="110" y2="0" class="neon-thin" />
|
|
217
|
+
<circle cx="110" cy="-36" r="12" class="accent-dot" />
|
|
218
|
+
|
|
219
|
+
<!-- small scan lines / UI fragments on head (to suggest vision/processing) -->
|
|
220
|
+
<path d="M170,30 h40" class="neon-thin" stroke-linecap="square" />
|
|
221
|
+
<path d="M162,54 h30" class="neon-thin" stroke-linecap="square" />
|
|
222
|
+
<path d="M28,28 h-12" class="neon-thin" />
|
|
223
|
+
<rect x="20" y="20" width="6" height="28" rx="1" class="neon-thin" />
|
|
224
|
+
|
|
225
|
+
<!-- Abstract eye -->
|
|
226
|
+
<g transform="translate(10,50)">
|
|
227
|
+
<!-- eye outline -->
|
|
228
|
+
<path d="M40,60 C20,60 0,44 0,30 C0,16 20,0 40,0 C60,0 80,16 80,30 C80,44 60,60 40,60 Z" class="eye-fill"/>
|
|
229
|
+
<!-- iris concentric rings -->
|
|
230
|
+
<circle cx="40" cy="30" r="16" stroke="url(#neonGradient)" stroke-width="8" fill="none" />
|
|
231
|
+
<circle cx="40" cy="30" r="8" stroke="url(#neonGradient)" stroke-width="6" fill="none" />
|
|
232
|
+
<circle cx="40" cy="30" r="3" fill="#04102A"/>
|
|
233
|
+
|
|
234
|
+
<!-- subtle scan ticks inside eye -->
|
|
235
|
+
<path d="M20,30 h6" class="neon-thin" />
|
|
236
|
+
<path d="M66,30 h6" class="neon-thin" />
|
|
237
|
+
<path d="M40,8 v6" class="neon-thin" />
|
|
238
|
+
<path d="M40,52 v6" class="neon-thin" />
|
|
239
|
+
</g>
|
|
240
|
+
|
|
241
|
+
<!-- Circuit-like legs below eye (AI & automation) -->
|
|
242
|
+
<g transform="translate(15,125)">
|
|
243
|
+
<!-- crossing trace lines -->
|
|
244
|
+
<path d="M20,0 L75,60" class="circuit" />
|
|
245
|
+
<path d="M110,0 L58,60" class="circuit" />
|
|
246
|
+
|
|
247
|
+
<!-- branch nodes -->
|
|
248
|
+
<circle cx="20" cy="0" r="7" class="accent-dot" />
|
|
249
|
+
<circle cx="110" cy="0" r="7" class="accent-dot" />
|
|
250
|
+
|
|
251
|
+
<circle cx="75" cy="60" r="8" class="accent-dot" />
|
|
252
|
+
<circle cx="58" cy="60" r="8" class="accent-dot" />
|
|
253
|
+
|
|
254
|
+
<!-- side traces -->
|
|
255
|
+
<path d="M10,18 L35,38" class="circuit" />
|
|
256
|
+
<path d="M120,18 L95,38" class="circuit" />
|
|
257
|
+
|
|
258
|
+
<circle cx="8" cy="18" r="6" class="accent-dot" />
|
|
259
|
+
<circle cx="122" cy="18" r="6" class="accent-dot" />
|
|
260
|
+
</g>
|
|
261
|
+
</g>
|
|
262
|
+
|
|
263
|
+
<!-- WORDMARK (Right side) -->
|
|
264
|
+
<g id="wordmark" transform="translate(530,360)">
|
|
265
|
+
<!-- Main text -->
|
|
266
|
+
<text class="word" x="0" y="30">CapVision</text>
|
|
267
|
+
|
|
268
|
+
<!-- subtle tech underline / focus ring hint under 'Vision' -->
|
|
269
|
+
<path d="M210,52 h160" stroke="url(#wordGradient)" stroke-width="6" stroke-linecap="square" opacity="0.35" />
|
|
270
|
+
<circle cx="360" cy="28" r="10" fill="none" stroke="url(#wordGradient)" stroke-width="4" opacity="0.45" />
|
|
271
|
+
</g>
|
|
272
|
+
|
|
273
|
+
<!-- Accessibility: invisible title/desc -->
|
|
274
|
+
<title>CapVision — cyberpunk AI vision logo</title>
|
|
275
|
+
<desc>Futuristic neon logo composed of an abstract robotic-eye icon and circuit traces left, and a bold CapVision wordmark right. Gradient neon colors and clean strokes create a cyberpunk high-tech look.</desc>
|
|
276
|
+
</svg>
|
|
277
|
+
`.trim();
|
|
278
|
+
|
|
279
|
+
return `
|
|
280
|
+
<tr class="recording-logo-header">
|
|
281
|
+
<td colspan="2">
|
|
282
|
+
<div style="padding: 20px 15px; background: #fff; border-bottom: 2px solid #e9ecef; display: flex; justify-content: space-between; align-items: center;">
|
|
283
|
+
<span id="capvision-toggle-wrapper"
|
|
284
|
+
onclick="toggleCapVisionRecordings()"
|
|
285
|
+
style="cursor: pointer; padding: 10px; user-select: none; background-color: #C8E6C9; border-radius: 3px; display: inline-block; flex: 1; max-width: calc(100% - 150px); margin-right: 20px;">
|
|
286
|
+
<span id="capvision-toggle-icon" class="collapsed" style="font-size: 12px; display: inline-block;"></span>
|
|
287
|
+
</span>
|
|
288
|
+
<div style="text-align: right;">
|
|
289
|
+
${logoSVG}
|
|
290
|
+
</div>
|
|
291
|
+
</div>
|
|
292
|
+
</td>
|
|
293
|
+
</tr>`;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Generate wrapper HTML for recordings (table structure)
|
|
298
|
+
* @private
|
|
299
|
+
* @param {string} logoHTML - Logo HTML
|
|
300
|
+
* @param {string} recordingPlayersHTML - Recording players HTML
|
|
301
|
+
* @returns {string} Wrapped HTML
|
|
302
|
+
*/
|
|
303
|
+
generateRecordingsWrapper(logoHTML, recordingPlayersHTML) {
|
|
304
|
+
return `
|
|
305
|
+
<table class="table table-bordered" style="margin-top: 20px;">
|
|
306
|
+
<thead>
|
|
307
|
+
${logoHTML}
|
|
308
|
+
</thead>
|
|
309
|
+
<tbody id="capvision-recordings-container" class="collapsed" style="display: table-row-group;">
|
|
310
|
+
${recordingPlayersHTML}
|
|
311
|
+
</tbody>
|
|
312
|
+
</table>
|
|
313
|
+
<style>
|
|
314
|
+
#capvision-recordings-container {
|
|
315
|
+
transition: opacity 0.3s ease-in-out;
|
|
316
|
+
}
|
|
317
|
+
#capvision-recordings-container.collapsed {
|
|
318
|
+
display: none !important;
|
|
319
|
+
}
|
|
320
|
+
#capvision-toggle-wrapper {
|
|
321
|
+
transition: background-color 0.2s ease;
|
|
322
|
+
}
|
|
323
|
+
#capvision-toggle-wrapper:hover {
|
|
324
|
+
background-color: #C8E6C9;
|
|
325
|
+
}
|
|
326
|
+
#capvision-toggle-wrapper:hover #capvision-toggle-icon {
|
|
327
|
+
color: #333;
|
|
328
|
+
}
|
|
329
|
+
#capvision-toggle-icon {
|
|
330
|
+
display: inline-block;
|
|
331
|
+
}
|
|
332
|
+
#capvision-toggle-icon::before {
|
|
333
|
+
font-family: "Glyphicons Halflings", "Font Awesome", Arial, sans-serif;
|
|
334
|
+
color: #333;
|
|
335
|
+
}
|
|
336
|
+
#capvision-toggle-icon.collapsed::before {
|
|
337
|
+
content: "\\e113";
|
|
338
|
+
}
|
|
339
|
+
#capvision-toggle-icon:not(.collapsed)::before {
|
|
340
|
+
content: "\\e114";
|
|
341
|
+
}
|
|
342
|
+
</style>
|
|
343
|
+
<script>
|
|
344
|
+
function toggleCapVisionRecordings() {
|
|
345
|
+
const container = document.getElementById('capvision-recordings-container');
|
|
346
|
+
const toggleIcon = document.getElementById('capvision-toggle-icon');
|
|
347
|
+
|
|
348
|
+
if (container.classList.contains('collapsed')) {
|
|
349
|
+
container.classList.remove('collapsed');
|
|
350
|
+
toggleIcon.classList.remove('collapsed');
|
|
351
|
+
} else {
|
|
352
|
+
container.classList.add('collapsed');
|
|
353
|
+
toggleIcon.classList.add('collapsed');
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
</script>`;
|
|
357
|
+
}
|
|
358
|
+
|
|
158
359
|
/**
|
|
159
360
|
* Add inline recording players to HTML content
|
|
160
361
|
* @private
|
|
@@ -167,17 +368,60 @@ class ReportEnhancer {
|
|
|
167
368
|
return htmlContent;
|
|
168
369
|
}
|
|
169
370
|
|
|
170
|
-
|
|
371
|
+
// Find table with class "table table-bordered table-suite"
|
|
372
|
+
const tableSuiteRegex = /<table[^>]*class=["'][^"']*table[^"']*table-bordered[^"']*table-suite[^"']*["'][^>]*>/i;
|
|
373
|
+
const match = htmlContent.match(tableSuiteRegex);
|
|
374
|
+
|
|
375
|
+
if (!match) {
|
|
376
|
+
// Table not found, return original content
|
|
377
|
+
return htmlContent;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const tableStartIndex = match.index;
|
|
381
|
+
const tableStartTag = match[0];
|
|
382
|
+
|
|
383
|
+
// Find the matching closing </table> tag for this table
|
|
384
|
+
// We need to account for nested tables
|
|
385
|
+
let depth = 1;
|
|
386
|
+
let searchIndex = tableStartIndex + tableStartTag.length;
|
|
387
|
+
let closingTableIndex = -1;
|
|
388
|
+
|
|
389
|
+
while (searchIndex < htmlContent.length && depth > 0) {
|
|
390
|
+
const nextOpen = htmlContent.indexOf('<table', searchIndex);
|
|
391
|
+
const nextClose = htmlContent.indexOf('</table>', searchIndex);
|
|
392
|
+
|
|
393
|
+
if (nextClose === -1) {
|
|
394
|
+
break; // No closing tag found
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (nextOpen !== -1 && nextOpen < nextClose) {
|
|
398
|
+
depth++;
|
|
399
|
+
searchIndex = nextOpen + 6;
|
|
400
|
+
} else {
|
|
401
|
+
depth--;
|
|
402
|
+
if (depth === 0) {
|
|
403
|
+
closingTableIndex = nextClose;
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
searchIndex = nextClose + 8;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (closingTableIndex === -1) {
|
|
411
|
+
// If we can't find matching closing tag, return original content
|
|
412
|
+
return htmlContent;
|
|
413
|
+
}
|
|
171
414
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
415
|
+
// Generate logo and players HTML
|
|
416
|
+
const logoHTML = this.generateLogoHTML();
|
|
417
|
+
const recordingPlayersHTML = recordings.map((recording, index) => {
|
|
418
|
+
const testName = recording.data.testName || 'Unknown Test';
|
|
419
|
+
const recordingNumber = recording.data.recordingNumber || (index + 1);
|
|
420
|
+
const displayName = recording.data.testName
|
|
421
|
+
? `${testName} (Recording #${recordingNumber})`
|
|
422
|
+
: recording.filename;
|
|
179
423
|
|
|
180
|
-
|
|
424
|
+
return `
|
|
181
425
|
<tr class="test-row recording">
|
|
182
426
|
<td colspan="2">
|
|
183
427
|
<div class="recordingWrapper" style="padding: 15px; background: #f8f9fa; margin: 10px 0;">
|
|
@@ -195,10 +439,15 @@ class ReportEnhancer {
|
|
|
195
439
|
</div>
|
|
196
440
|
</td>
|
|
197
441
|
</tr>`;
|
|
198
|
-
|
|
442
|
+
}).join('');
|
|
443
|
+
|
|
444
|
+
// Wrap logo and players in a new table and inject after the closing </table> tag
|
|
445
|
+
const recordingsWrapper = this.generateRecordingsWrapper(logoHTML, recordingPlayersHTML);
|
|
446
|
+
const insertPosition = closingTableIndex + 8; // 8 = length of '</table>'
|
|
199
447
|
|
|
200
|
-
|
|
201
|
-
|
|
448
|
+
return htmlContent.substring(0, insertPosition) +
|
|
449
|
+
recordingsWrapper +
|
|
450
|
+
htmlContent.substring(insertPosition);
|
|
202
451
|
}
|
|
203
452
|
|
|
204
453
|
/**
|
|
@@ -441,7 +690,7 @@ class ReportEnhancer {
|
|
|
441
690
|
// Add player scripts and CSS
|
|
442
691
|
const capVisionPlayerHTML = this.generateCapVisionPlayerHTML(recordings);
|
|
443
692
|
const bodyEndIndex = htmlContent.lastIndexOf('</body>');
|
|
444
|
-
|
|
693
|
+
|
|
445
694
|
if (bodyEndIndex === -1) {
|
|
446
695
|
console.log('🟡 Could not find </body> tag, appending to end');
|
|
447
696
|
htmlContent += capVisionPlayerHTML;
|