@dynamicu/chromedebug-mcp 2.7.1 → 2.7.4
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/CLAUDE.md +18 -0
- package/README.md +226 -16
- package/chrome-extension/background.js +569 -64
- package/chrome-extension/browser-recording-manager.js +34 -0
- package/chrome-extension/content.js +438 -32
- package/chrome-extension/firebase-config.public-sw.js +1 -1
- package/chrome-extension/firebase-config.public.js +1 -1
- package/chrome-extension/frame-capture.js +31 -10
- package/chrome-extension/image-processor.js +193 -0
- package/chrome-extension/manifest.free.json +1 -1
- package/chrome-extension/options.html +2 -2
- package/chrome-extension/options.js +4 -4
- package/chrome-extension/popup.html +82 -4
- package/chrome-extension/popup.js +1106 -38
- package/chrome-extension/pro/frame-editor.html +259 -6
- package/chrome-extension/pro/frame-editor.js +959 -10
- package/chrome-extension/pro/video-exporter.js +917 -0
- package/chrome-extension/pro/video-player.js +545 -0
- package/dist/chromedebug-extension-free.zip +0 -0
- package/package.json +1 -1
- package/scripts/postinstall.js +1 -1
- package/scripts/webpack.config.free.cjs +6 -0
- package/scripts/webpack.config.pro.cjs +6 -0
- package/src/chrome-controller.js +6 -6
- package/src/database.js +226 -39
- package/src/http-server.js +55 -11
- package/src/validation/schemas.js +20 -5
|
@@ -56,34 +56,42 @@ class FrameCapture {
|
|
|
56
56
|
}, this.emergencyMaxDuration);
|
|
57
57
|
|
|
58
58
|
// Update settings with passed values or apply mode-specific defaults
|
|
59
|
+
console.log('[FrameCapture] Received settings:', settings, 'Mode:', this.mode);
|
|
59
60
|
if (this.mode === 'browser-only') {
|
|
60
61
|
// Browser-only mode: ultra-low resolution and quality for storage efficiency
|
|
61
62
|
this.settings = {
|
|
62
63
|
frameRate: settings.frameRate || 1,
|
|
63
64
|
imageQuality: settings.imageQuality || 15, // Lower quality for browser storage
|
|
64
|
-
frameFlash: settings.frameFlash !== false
|
|
65
|
+
frameFlash: settings.frameFlash !== false,
|
|
66
|
+
videoMode: settings?.videoMode || false
|
|
65
67
|
};
|
|
66
68
|
} else {
|
|
67
69
|
// Server mode: standard settings
|
|
68
70
|
this.settings = {
|
|
69
71
|
frameRate: settings.frameRate || 1,
|
|
70
72
|
imageQuality: settings.imageQuality || 30,
|
|
71
|
-
frameFlash: settings.frameFlash !== false
|
|
73
|
+
frameFlash: settings.frameFlash !== false,
|
|
74
|
+
videoMode: settings?.videoMode || false
|
|
72
75
|
};
|
|
73
76
|
}
|
|
77
|
+
console.log('[FrameCapture] Using settings:', this.settings, 'Interval:', this.settings.frameRate * 1000, 'ms');
|
|
74
78
|
|
|
75
79
|
// Trigger visual feedback system start
|
|
76
80
|
chrome.runtime.sendMessage({
|
|
77
81
|
type: 'start-screen-capture-tracking',
|
|
78
|
-
sessionId: this.sessionId
|
|
82
|
+
sessionId: this.sessionId,
|
|
83
|
+
tabId: this.tabId, // Include tabId for offscreen document routing
|
|
84
|
+
videoMode: this.settings.videoMode || false
|
|
79
85
|
}).catch(() => {});
|
|
80
86
|
|
|
81
87
|
// Create a synthetic first frame at scheduled start time to catch early logs
|
|
88
|
+
// Mark as synthetic so video exporter can skip it (1x1 placeholder can't be exported)
|
|
82
89
|
const syntheticFrame = {
|
|
83
90
|
timestamp: 0,
|
|
84
91
|
absoluteTimestamp: this.scheduledStartTime,
|
|
85
92
|
imageData: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==', // 1x1 transparent pixel
|
|
86
|
-
logs: []
|
|
93
|
+
logs: [],
|
|
94
|
+
isSynthetic: true // Flag to identify placeholder frames for video export filtering
|
|
87
95
|
};
|
|
88
96
|
this.frames.push(syntheticFrame);
|
|
89
97
|
this.totalFramesCaptured++;
|
|
@@ -102,8 +110,8 @@ class FrameCapture {
|
|
|
102
110
|
mandatory: {
|
|
103
111
|
chromeMediaSource: 'tab',
|
|
104
112
|
chromeMediaSourceId: streamId,
|
|
105
|
-
maxWidth: 640, //
|
|
106
|
-
maxHeight: 360
|
|
113
|
+
maxWidth: settings.maxWidth || 640, // Default 640, allow override for video export
|
|
114
|
+
maxHeight: settings.maxHeight || 360 // Default 360, allow override for video export
|
|
107
115
|
}
|
|
108
116
|
};
|
|
109
117
|
|
|
@@ -180,7 +188,8 @@ class FrameCapture {
|
|
|
180
188
|
|
|
181
189
|
// Trigger visual feedback for frame captured
|
|
182
190
|
chrome.runtime.sendMessage({
|
|
183
|
-
type: 'show-screen-capture-flash'
|
|
191
|
+
type: 'show-screen-capture-flash',
|
|
192
|
+
tabId: this.tabId // Include tabId for offscreen document routing
|
|
184
193
|
}).catch(() => {});
|
|
185
194
|
|
|
186
195
|
// Send frame immediately to keep memory usage low
|
|
@@ -266,7 +275,8 @@ class FrameCapture {
|
|
|
266
275
|
|
|
267
276
|
// Stop visual feedback system
|
|
268
277
|
chrome.runtime.sendMessage({
|
|
269
|
-
type: 'stop-screen-capture-tracking'
|
|
278
|
+
type: 'stop-screen-capture-tracking',
|
|
279
|
+
tabId: this.tabId // Include tabId for offscreen document routing
|
|
270
280
|
}).catch(() => {});
|
|
271
281
|
|
|
272
282
|
// Notify completion
|
|
@@ -338,7 +348,8 @@ class FrameCapture {
|
|
|
338
348
|
|
|
339
349
|
// Trigger visual feedback for manual snapshot
|
|
340
350
|
chrome.runtime.sendMessage({
|
|
341
|
-
type: 'show-screen-capture-flash'
|
|
351
|
+
type: 'show-screen-capture-flash',
|
|
352
|
+
tabId: this.tabId // Include tabId for offscreen document routing
|
|
342
353
|
}).catch(() => {});
|
|
343
354
|
|
|
344
355
|
// Flash indicator for manual snapshot
|
|
@@ -379,12 +390,22 @@ class FrameCapture {
|
|
|
379
390
|
|
|
380
391
|
const batch = this.frames.splice(0, 5); // Send up to 5 frames at a time
|
|
381
392
|
|
|
393
|
+
// Filter out synthetic frames before sending to server
|
|
394
|
+
// Synthetic frames are 1x1 transparent pixels used only for early log capture
|
|
395
|
+
// They can't be exported to video and the server may reject the isSynthetic field
|
|
396
|
+
const realFrames = batch.filter(frame => !frame.isSynthetic);
|
|
397
|
+
|
|
398
|
+
if (realFrames.length === 0) {
|
|
399
|
+
// All frames in batch were synthetic, nothing to send
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
382
403
|
chrome.runtime.sendMessage({
|
|
383
404
|
type: 'frame-batch-ready',
|
|
384
405
|
target: 'background',
|
|
385
406
|
data: {
|
|
386
407
|
sessionId: this.sessionId,
|
|
387
|
-
frames:
|
|
408
|
+
frames: realFrames
|
|
388
409
|
}
|
|
389
410
|
});
|
|
390
411
|
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Processor Utility (v2.7.3)
|
|
3
|
+
*
|
|
4
|
+
* Handles watermarking and image processing for Chrome Debug extension.
|
|
5
|
+
*
|
|
6
|
+
* CRITICAL DESIGN DECISION (from Second Opinion review):
|
|
7
|
+
* - Watermarks are applied at DISPLAY/EXPORT time, NOT storage time
|
|
8
|
+
* - This allows upgrade recovery (users who upgrade mid-session get clean images)
|
|
9
|
+
* - Storage remains clean without watermarks
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Watermark configuration
|
|
13
|
+
const WATERMARK_CONFIG = {
|
|
14
|
+
text: 'Chrome Debug FREE',
|
|
15
|
+
position: 'bottom-right',
|
|
16
|
+
opacity: 0.7,
|
|
17
|
+
fontSize: 14,
|
|
18
|
+
padding: 10,
|
|
19
|
+
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
|
20
|
+
textColor: '#ffffff'
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Check if this is the PRO version
|
|
25
|
+
* @returns {boolean}
|
|
26
|
+
*/
|
|
27
|
+
function isProVersion() {
|
|
28
|
+
try {
|
|
29
|
+
const manifest = chrome.runtime.getManifest();
|
|
30
|
+
return manifest.name.includes('PRO');
|
|
31
|
+
} catch (e) {
|
|
32
|
+
// If we can't access manifest, assume FREE version
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Check if watermarking should be applied
|
|
39
|
+
* @returns {boolean}
|
|
40
|
+
*/
|
|
41
|
+
function shouldWatermark() {
|
|
42
|
+
return !isProVersion();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Apply watermark to image data URL at DISPLAY or EXPORT time
|
|
47
|
+
* @param {string} imageDataUrl - Base64 image data URL
|
|
48
|
+
* @param {object} options - Watermark options (optional overrides)
|
|
49
|
+
* @returns {Promise<string>} - Watermarked image data URL
|
|
50
|
+
*/
|
|
51
|
+
async function applyWatermark(imageDataUrl, options = {}) {
|
|
52
|
+
const config = {
|
|
53
|
+
...WATERMARK_CONFIG,
|
|
54
|
+
...options
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
const img = new Image();
|
|
59
|
+
|
|
60
|
+
img.onload = () => {
|
|
61
|
+
try {
|
|
62
|
+
const canvas = document.createElement('canvas');
|
|
63
|
+
canvas.width = img.width;
|
|
64
|
+
canvas.height = img.height;
|
|
65
|
+
const ctx = canvas.getContext('2d');
|
|
66
|
+
|
|
67
|
+
// Draw original image
|
|
68
|
+
ctx.drawImage(img, 0, 0);
|
|
69
|
+
|
|
70
|
+
// Calculate badge dimensions
|
|
71
|
+
ctx.font = `bold ${config.fontSize}px Arial, sans-serif`;
|
|
72
|
+
const textMetrics = ctx.measureText(config.text);
|
|
73
|
+
const textWidth = textMetrics.width;
|
|
74
|
+
const badgeWidth = textWidth + config.padding * 2;
|
|
75
|
+
const badgeHeight = config.fontSize + config.padding * 2;
|
|
76
|
+
|
|
77
|
+
// Calculate position (bottom-right corner)
|
|
78
|
+
let x, y;
|
|
79
|
+
switch (config.position) {
|
|
80
|
+
case 'bottom-right':
|
|
81
|
+
x = canvas.width - badgeWidth - 10;
|
|
82
|
+
y = canvas.height - badgeHeight - 10;
|
|
83
|
+
break;
|
|
84
|
+
case 'bottom-left':
|
|
85
|
+
x = 10;
|
|
86
|
+
y = canvas.height - badgeHeight - 10;
|
|
87
|
+
break;
|
|
88
|
+
case 'top-right':
|
|
89
|
+
x = canvas.width - badgeWidth - 10;
|
|
90
|
+
y = 10;
|
|
91
|
+
break;
|
|
92
|
+
case 'top-left':
|
|
93
|
+
x = 10;
|
|
94
|
+
y = 10;
|
|
95
|
+
break;
|
|
96
|
+
default:
|
|
97
|
+
x = canvas.width - badgeWidth - 10;
|
|
98
|
+
y = canvas.height - badgeHeight - 10;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Draw semi-transparent badge background with rounded corners
|
|
102
|
+
ctx.fillStyle = config.backgroundColor;
|
|
103
|
+
ctx.beginPath();
|
|
104
|
+
roundRect(ctx, x, y, badgeWidth, badgeHeight, 5);
|
|
105
|
+
ctx.fill();
|
|
106
|
+
|
|
107
|
+
// Draw text
|
|
108
|
+
ctx.fillStyle = config.textColor;
|
|
109
|
+
ctx.fillText(config.text, x + config.padding, y + config.fontSize + config.padding / 2);
|
|
110
|
+
|
|
111
|
+
// Return watermarked image
|
|
112
|
+
resolve(canvas.toDataURL('image/jpeg', 0.92));
|
|
113
|
+
} catch (error) {
|
|
114
|
+
console.error('[ImageProcessor] Error applying watermark:', error);
|
|
115
|
+
// Return original image on error
|
|
116
|
+
resolve(imageDataUrl);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
img.onerror = () => {
|
|
121
|
+
console.error('[ImageProcessor] Failed to load image for watermarking');
|
|
122
|
+
// Return original image on error
|
|
123
|
+
resolve(imageDataUrl);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
img.src = imageDataUrl;
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Helper function to draw rounded rectangle
|
|
132
|
+
*/
|
|
133
|
+
function roundRect(ctx, x, y, width, height, radius) {
|
|
134
|
+
ctx.moveTo(x + radius, y);
|
|
135
|
+
ctx.lineTo(x + width - radius, y);
|
|
136
|
+
ctx.arcTo(x + width, y, x + width, y + radius, radius);
|
|
137
|
+
ctx.lineTo(x + width, y + height - radius);
|
|
138
|
+
ctx.arcTo(x + width, y + height, x + width - radius, y + height, radius);
|
|
139
|
+
ctx.lineTo(x + radius, y + height);
|
|
140
|
+
ctx.arcTo(x, y + height, x, y + height - radius, radius);
|
|
141
|
+
ctx.lineTo(x, y + radius);
|
|
142
|
+
ctx.arcTo(x, y, x + radius, y, radius);
|
|
143
|
+
ctx.closePath();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Process image for display - applies watermark if FREE version
|
|
148
|
+
* @param {string} imageDataUrl - Base64 image data URL
|
|
149
|
+
* @returns {Promise<string>} - Processed image data URL
|
|
150
|
+
*/
|
|
151
|
+
async function processImageForDisplay(imageDataUrl) {
|
|
152
|
+
if (shouldWatermark()) {
|
|
153
|
+
return await applyWatermark(imageDataUrl);
|
|
154
|
+
}
|
|
155
|
+
return imageDataUrl;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Process image for export - applies watermark if FREE version
|
|
160
|
+
* @param {string} imageDataUrl - Base64 image data URL
|
|
161
|
+
* @returns {Promise<string>} - Processed image data URL
|
|
162
|
+
*/
|
|
163
|
+
async function processImageForExport(imageDataUrl) {
|
|
164
|
+
if (shouldWatermark()) {
|
|
165
|
+
return await applyWatermark(imageDataUrl);
|
|
166
|
+
}
|
|
167
|
+
return imageDataUrl;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Process multiple images for display/export
|
|
172
|
+
* @param {string[]} imageDataUrls - Array of base64 image data URLs
|
|
173
|
+
* @returns {Promise<string[]>} - Array of processed image data URLs
|
|
174
|
+
*/
|
|
175
|
+
async function processImagesForDisplay(imageDataUrls) {
|
|
176
|
+
if (shouldWatermark()) {
|
|
177
|
+
return await Promise.all(imageDataUrls.map(url => applyWatermark(url)));
|
|
178
|
+
}
|
|
179
|
+
return imageDataUrls;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Export functions for use in other scripts
|
|
183
|
+
if (typeof window !== 'undefined') {
|
|
184
|
+
window.ChromeDebugImageProcessor = {
|
|
185
|
+
isProVersion,
|
|
186
|
+
shouldWatermark,
|
|
187
|
+
applyWatermark,
|
|
188
|
+
processImageForDisplay,
|
|
189
|
+
processImageForExport,
|
|
190
|
+
processImagesForDisplay,
|
|
191
|
+
WATERMARK_CONFIG
|
|
192
|
+
};
|
|
193
|
+
}
|
|
@@ -156,10 +156,10 @@
|
|
|
156
156
|
<div class="setting-group">
|
|
157
157
|
<h3>Recording Safety</h3>
|
|
158
158
|
<label for="inactivity-timeout">Auto-Stop After Inactivity (seconds)</label>
|
|
159
|
-
<input type="number" id="inactivity-timeout" min="5" max="3600" value="
|
|
159
|
+
<input type="number" id="inactivity-timeout" min="5" max="3600" value="60">
|
|
160
160
|
<div class="help-text">
|
|
161
161
|
Screen recording will automatically stop if no mouse or keyboard activity is detected.
|
|
162
|
-
Default:
|
|
162
|
+
Default: 60 seconds. Adjust for long demo recordings or presentations.
|
|
163
163
|
</div>
|
|
164
164
|
</div>
|
|
165
165
|
|
|
@@ -9,8 +9,8 @@ chrome.storage.sync.get(['serverPort', 'chromePilotMode', 'chromePilotAllowedSit
|
|
|
9
9
|
document.getElementById('server-port').value = data.serverPort;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
// Load inactivity timeout (default
|
|
13
|
-
document.getElementById('inactivity-timeout').value = data.inactivityTimeout ||
|
|
12
|
+
// Load inactivity timeout (default 60 seconds)
|
|
13
|
+
document.getElementById('inactivity-timeout').value = data.inactivityTimeout || 60;
|
|
14
14
|
|
|
15
15
|
// Load site restriction settings
|
|
16
16
|
const mode = data.chromePilotMode || 'whitelist';
|
|
@@ -38,7 +38,7 @@ document.getElementById('save').addEventListener('click', async () => {
|
|
|
38
38
|
|
|
39
39
|
// Get inactivity timeout
|
|
40
40
|
const inactivityTimeoutInput = document.getElementById('inactivity-timeout');
|
|
41
|
-
const inactivityTimeout = parseInt(inactivityTimeoutInput.value) ||
|
|
41
|
+
const inactivityTimeout = parseInt(inactivityTimeoutInput.value) || 60;
|
|
42
42
|
if (inactivityTimeout < 5 || inactivityTimeout > 3600) {
|
|
43
43
|
showStatus('error', 'Inactivity timeout must be between 5 and 3600 seconds');
|
|
44
44
|
return;
|
|
@@ -68,7 +68,7 @@ document.getElementById('save').addEventListener('click', async () => {
|
|
|
68
68
|
// Reset to default
|
|
69
69
|
document.getElementById('reset').addEventListener('click', async () => {
|
|
70
70
|
document.getElementById('server-port').value = '';
|
|
71
|
-
document.getElementById('inactivity-timeout').value = '
|
|
71
|
+
document.getElementById('inactivity-timeout').value = '60';
|
|
72
72
|
document.querySelector('input[value="whitelist"]').checked = true;
|
|
73
73
|
document.getElementById('allowed-sites').value = 'localhost:*\n127.0.0.1:*\n*.local\n*.test\n*.dev';
|
|
74
74
|
document.getElementById('blocked-sites').value = 'youtube.com\n*.youtube.com\ngoogle.com\n*.google.com\nfacebook.com\ntwitter.com\nx.com';
|
|
@@ -151,6 +151,24 @@
|
|
|
151
151
|
color: #f44336;
|
|
152
152
|
font-weight: bold;
|
|
153
153
|
}
|
|
154
|
+
|
|
155
|
+
.delete-all-btn {
|
|
156
|
+
transition: background 0.2s, transform 0.1s;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.delete-all-btn:hover {
|
|
160
|
+
background: #d32f2f !important;
|
|
161
|
+
transform: scale(1.05);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.delete-all-btn:active {
|
|
165
|
+
transform: scale(0.95);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.delete-all-btn:disabled {
|
|
169
|
+
opacity: 0.5;
|
|
170
|
+
cursor: not-allowed;
|
|
171
|
+
}
|
|
154
172
|
</style>
|
|
155
173
|
</head>
|
|
156
174
|
<body>
|
|
@@ -171,7 +189,7 @@
|
|
|
171
189
|
<span class="server-status disconnected" id="serverStatus"></span>
|
|
172
190
|
<span id="statusText">Checking...</span>
|
|
173
191
|
</div>
|
|
174
|
-
<a href="https://
|
|
192
|
+
<a href="https://www.npmjs.com/package/@dynamicu/chromedebug-mcp" target="_blank" style="font-size: 10px; color: #2196F3; text-decoration: none;">📚 Documentation</a>
|
|
175
193
|
</div>
|
|
176
194
|
<div style="flex: 0 0 auto; width: 200px;"></div>
|
|
177
195
|
</div>
|
|
@@ -283,11 +301,45 @@
|
|
|
283
301
|
Start Workflow Recording
|
|
284
302
|
<span id="workflowProBadge" style="position: absolute; top: -8px; right: -8px; background: #ff6b6b; color: white; font-size: 9px; padding: 2px 6px; border-radius: 10px; font-weight: bold; display: none;">PRO</span>
|
|
285
303
|
</button>
|
|
304
|
+
|
|
305
|
+
<!-- Screenshot Recording Button -->
|
|
306
|
+
<button class="record-btn" id="screenshotRecordBtn" style="background: #FF9800; position: relative; padding: 8px; font-size: 11px; margin-top: 4px;">
|
|
307
|
+
📸 Start Screenshot Recording
|
|
308
|
+
<span id="screenshotProBadge" style="position: absolute; top: -8px; right: -8px; background: #ff6b6b; color: white; font-size: 9px; padding: 2px 6px; border-radius: 10px; font-weight: bold; display: none;">PRO</span>
|
|
309
|
+
</button>
|
|
310
|
+
|
|
311
|
+
<!-- Screenshot Recording Settings (shown when button is clicked) -->
|
|
312
|
+
<div id="screenshotRecordingSettings" style="display: none; background: #fff3e0; padding: 6px; border-radius: 3px; margin-top: 4px; border: 1px solid #FF9800;">
|
|
313
|
+
<div style="font-size: 9px; font-weight: bold; margin-bottom: 4px; color: #E65100;">📸 Screenshot Settings</div>
|
|
314
|
+
<label style="font-size: 9px; display: flex; align-items: center; cursor: pointer; margin-bottom: 4px;">
|
|
315
|
+
<input type="checkbox" id="hideMouseCursorCheckbox" checked style="margin-right: 4px;">
|
|
316
|
+
Hide mouse cursor
|
|
317
|
+
</label>
|
|
318
|
+
<div style="display: flex; align-items: center; gap: 4px;">
|
|
319
|
+
<label style="font-size: 9px;">Countdown:</label>
|
|
320
|
+
<select id="screenshotCountdown" style="font-size: 9px; padding: 2px;">
|
|
321
|
+
<option value="0">Instant (0s)</option>
|
|
322
|
+
<option value="3" selected>3 seconds</option>
|
|
323
|
+
<option value="5">5 seconds</option>
|
|
324
|
+
</select>
|
|
325
|
+
</div>
|
|
326
|
+
</div>
|
|
327
|
+
|
|
286
328
|
<button class="record-btn" id="saveRestorePointBtn" style="background: #2196F3; display: none; margin-top: 4px; padding: 6px; font-size: 10px;">📍 Save Point</button>
|
|
287
329
|
<div class="recording-status" id="workflowRecordingStatus" style="font-size: 10px;"></div>
|
|
288
|
-
|
|
330
|
+
<div class="recording-status" id="screenshotRecordingStatus" style="font-size: 10px;"></div>
|
|
331
|
+
|
|
289
332
|
<div class="recordings-list" id="workflowRecordingsList" style="margin-top: 10px; display: none;">
|
|
290
|
-
<
|
|
333
|
+
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px;">
|
|
334
|
+
<h4 style="margin: 0; font-size: 11px;">Saved Recordings:</h4>
|
|
335
|
+
<button id="deleteAllWorkflowsBtn" class="delete-all-btn" style="display: none; font-size: 8px; padding: 2px 6px; background: #f44336; color: white; border: none; border-radius: 3px; cursor: pointer; flex-shrink: 0;" title="Delete all workflow recordings">Delete All</button>
|
|
336
|
+
</div>
|
|
337
|
+
<!-- Tab System for Workflow/Screenshot filtering -->
|
|
338
|
+
<div id="workflowTypeTabs" style="display: flex; margin-bottom: 6px; border-bottom: 1px solid #ddd;">
|
|
339
|
+
<button id="tabAllWorkflows" class="workflow-tab active" style="flex: 1; padding: 4px 8px; font-size: 10px; background: #9c27b0; color: white; border: none; border-radius: 4px 4px 0 0; cursor: pointer; margin-right: 2px;">All</button>
|
|
340
|
+
<button id="tabWorkflowOnly" class="workflow-tab" style="flex: 1; padding: 4px 8px; font-size: 10px; background: #e0e0e0; color: #333; border: none; border-radius: 4px 4px 0 0; cursor: pointer; margin-right: 2px;">Workflow</button>
|
|
341
|
+
<button id="tabScreenshotsOnly" class="workflow-tab" style="flex: 1; padding: 4px 8px; font-size: 10px; background: #e0e0e0; color: #333; border: none; border-radius: 4px 4px 0 0; cursor: pointer;">📸 Screenshots</button>
|
|
342
|
+
</div>
|
|
291
343
|
<div id="workflowRecordingsContainer" style="max-height: 300px; overflow-y: auto; overflow-x: hidden; border: 1px solid #e0e0e0; border-radius: 4px; padding: 6px;"></div>
|
|
292
344
|
</div>
|
|
293
345
|
|
|
@@ -346,11 +398,28 @@
|
|
|
346
398
|
<input type="checkbox" id="frameFlash"> Flash
|
|
347
399
|
</label>
|
|
348
400
|
</div>
|
|
401
|
+
|
|
402
|
+
<!-- Mouse Tracking (PRO-only) -->
|
|
403
|
+
<div class="mouse-tracking-settings" id="mouseTrackingSection" style="margin-top: 8px; padding-top: 8px; border-top: 1px solid #e0e0e0;">
|
|
404
|
+
<label style="font-size: 9px; color: #666; display: flex; align-items: center; margin-bottom: 4px;">
|
|
405
|
+
<input type="checkbox" id="enableMouseTracking" style="margin-right: 4px;">
|
|
406
|
+
Track mouse movement
|
|
407
|
+
</label>
|
|
408
|
+
<select id="mouseSampleRate" disabled style="width: 100%; padding: 2px; font-size: 9px; margin-bottom: 4px;">
|
|
409
|
+
<option value="50">Fast (50ms) - Smooth</option>
|
|
410
|
+
<option value="100" selected>Normal (100ms)</option>
|
|
411
|
+
<option value="200">Slow (200ms)</option>
|
|
412
|
+
</select>
|
|
413
|
+
<p style="font-size: 8px; color: #999; margin: 0; line-height: 1.2;">
|
|
414
|
+
Records cursor position for video playback visualization.
|
|
415
|
+
</p>
|
|
416
|
+
</div>
|
|
349
417
|
</div>
|
|
350
418
|
</details>
|
|
351
419
|
</div>
|
|
352
420
|
|
|
353
421
|
<button class="record-btn" id="recordBtn" style="padding: 8px; font-size: 11px;">Start Recording</button>
|
|
422
|
+
<button class="record-btn" id="startVideoRecordingBtn" style="padding: 8px; font-size: 11px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); margin-top: 4px;">🎥 Start Video Recording</button>
|
|
354
423
|
<button class="record-btn" id="manualSnapshotBtn" style="background: #4CAF50; display: none; margin-top: 4px; padding: 6px; font-size: 10px;">Manual Snapshot</button>
|
|
355
424
|
<div class="recording-status" id="recordingStatus" style="font-size: 10px;"></div>
|
|
356
425
|
<div class="countdown-display" id="countdownDisplay" style="display: none; text-align: center; margin-top: 10px; padding: 10px; background: #f0f0f0; border-radius: 4px;">
|
|
@@ -359,7 +428,16 @@
|
|
|
359
428
|
</div>
|
|
360
429
|
|
|
361
430
|
<div class="recordings-list" id="recordingsList" style="margin-top: 10px; display: none;">
|
|
362
|
-
<
|
|
431
|
+
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px;">
|
|
432
|
+
<h4 style="margin: 0; font-size: 11px;">Saved Recordings:</h4>
|
|
433
|
+
<button id="deleteAllScreenRecordingsBtn" class="delete-all-btn" style="display: none; font-size: 8px; padding: 2px 6px; background: #f44336; color: white; border: none; border-radius: 3px; cursor: pointer; flex-shrink: 0;" title="Delete all screen recordings">Delete All</button>
|
|
434
|
+
</div>
|
|
435
|
+
<!-- Recording type tabs -->
|
|
436
|
+
<div id="recordingTypeTabs" style="display: flex; margin-bottom: 6px; border-bottom: 1px solid #ddd;">
|
|
437
|
+
<button id="tabAllRecordings" class="recording-tab active" style="flex: 1; padding: 4px 8px; font-size: 10px; background: #2196F3; color: white; border: none; border-radius: 4px 4px 0 0; cursor: pointer; margin-right: 2px;">All</button>
|
|
438
|
+
<button id="tabScreenRecordings" class="recording-tab" style="flex: 1; padding: 4px 8px; font-size: 10px; background: #e0e0e0; color: #333; border: none; border-radius: 4px 4px 0 0; cursor: pointer; margin-right: 2px;">📸 Screen</button>
|
|
439
|
+
<button id="tabVideoRecordings" class="recording-tab" style="flex: 1; padding: 4px 8px; font-size: 10px; background: #e0e0e0; color: #333; border: none; border-radius: 4px 4px 0 0; cursor: pointer;">🎥 Video</button>
|
|
440
|
+
</div>
|
|
363
441
|
<div id="recordingsContainer" style="max-height: 300px; overflow-y: auto; overflow-x: hidden; border: 1px solid #e0e0e0; border-radius: 4px; padding: 6px;"></div>
|
|
364
442
|
</div>
|
|
365
443
|
</div>
|