@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
|
@@ -3,7 +3,7 @@ const EXTENSION_VERSION = '2.0.4-BUILD-20250119';
|
|
|
3
3
|
// console.log(`[popup.js] Loaded version: ${EXTENSION_VERSION}`);
|
|
4
4
|
|
|
5
5
|
// LemonSqueezy purchase URL for PRO upgrade
|
|
6
|
-
const PRO_PURCHASE_URL = 'https://chromedebug.com/buy/996773cb-682b-430f-b9e3-9ce2130bd967';
|
|
6
|
+
const PRO_PURCHASE_URL = 'https://chromedebug.com/checkout/buy/996773cb-682b-430f-b9e3-9ce2130bd967';
|
|
7
7
|
const IS_PRO_VERSION = chrome.runtime.getManifest().name.includes('PRO');
|
|
8
8
|
|
|
9
9
|
// Global variables for recording functionality
|
|
@@ -11,6 +11,7 @@ let isRecording = false;
|
|
|
11
11
|
let recordingTimer = null;
|
|
12
12
|
let recordingStartTime = null;
|
|
13
13
|
let isStoppingRecording = false;
|
|
14
|
+
let isVideoModeActive = false; // Track if we're in video recording mode
|
|
14
15
|
|
|
15
16
|
// Countdown timer variables
|
|
16
17
|
let countdownTimer = null;
|
|
@@ -23,6 +24,17 @@ let isWorkflowRecording = false;
|
|
|
23
24
|
let workflowRecordingTimer = null;
|
|
24
25
|
let workflowStartTime = null;
|
|
25
26
|
|
|
27
|
+
// Screenshot recording variables
|
|
28
|
+
let isScreenshotRecording = false;
|
|
29
|
+
let screenshotRecordingTimer = null;
|
|
30
|
+
let screenshotStartTime = null;
|
|
31
|
+
|
|
32
|
+
// Recording tab filter: 'all', 'screen', 'video'
|
|
33
|
+
let recordingTabFilter = 'all';
|
|
34
|
+
|
|
35
|
+
// Workflow tab filter: 'all', 'workflow', 'screenshot'
|
|
36
|
+
let workflowTabFilter = 'all';
|
|
37
|
+
|
|
26
38
|
// Debounce timer for screenshot quality slider
|
|
27
39
|
let screenshotQualityDebounceTimer = null;
|
|
28
40
|
|
|
@@ -150,6 +162,67 @@ function stopWorkflowTimer() {
|
|
|
150
162
|
}
|
|
151
163
|
}
|
|
152
164
|
|
|
165
|
+
// Screenshot recording UI update function
|
|
166
|
+
function updateScreenshotRecordingUI() {
|
|
167
|
+
const screenshotBtn = document.getElementById('screenshotRecordBtn');
|
|
168
|
+
const screenshotStatus = document.getElementById('screenshotRecordingStatus');
|
|
169
|
+
const screenshotSettings = document.getElementById('screenshotRecordingSettings');
|
|
170
|
+
|
|
171
|
+
if (screenshotBtn) {
|
|
172
|
+
if (isScreenshotRecording) {
|
|
173
|
+
screenshotBtn.textContent = '⏹️ Stop Screenshot Recording';
|
|
174
|
+
screenshotBtn.style.background = '#e91e63';
|
|
175
|
+
screenshotBtn.classList.add('recording');
|
|
176
|
+
startScreenshotTimer();
|
|
177
|
+
// Hide settings during recording
|
|
178
|
+
if (screenshotSettings) {
|
|
179
|
+
screenshotSettings.style.display = 'none';
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
screenshotBtn.innerHTML = '📸 Start Screenshot Recording <span id="screenshotProBadge" style="position: absolute; top: -4px; right: -4px; background: linear-gradient(135deg, #FFD700, #FFA500); color: #000; font-size: 8px; padding: 1px 4px; border-radius: 3px; font-weight: bold;">PRO</span>';
|
|
183
|
+
screenshotBtn.style.background = '#FF9800';
|
|
184
|
+
screenshotBtn.classList.remove('recording');
|
|
185
|
+
stopScreenshotTimer();
|
|
186
|
+
if (screenshotStatus) {
|
|
187
|
+
screenshotStatus.textContent = '';
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function startScreenshotTimer() {
|
|
194
|
+
// Get stored start time or use current time
|
|
195
|
+
chrome.storage.local.get(['screenshotStartTime'], (result) => {
|
|
196
|
+
screenshotStartTime = result.screenshotStartTime || Date.now();
|
|
197
|
+
|
|
198
|
+
screenshotRecordingTimer = setInterval(() => {
|
|
199
|
+
const elapsed = Math.floor((Date.now() - screenshotStartTime) / 1000);
|
|
200
|
+
const minutes = Math.floor(elapsed / 60);
|
|
201
|
+
const seconds = elapsed % 60;
|
|
202
|
+
const screenshotStatus = document.getElementById('screenshotRecordingStatus');
|
|
203
|
+
if (screenshotStatus) {
|
|
204
|
+
screenshotStatus.innerHTML = `📸 Recording: <span class="recording-timer">${minutes}:${seconds.toString().padStart(2, '0')}</span>`;
|
|
205
|
+
}
|
|
206
|
+
}, 1000);
|
|
207
|
+
|
|
208
|
+
// Update timer immediately
|
|
209
|
+
const elapsed = Math.floor((Date.now() - screenshotStartTime) / 1000);
|
|
210
|
+
const minutes = Math.floor(elapsed / 60);
|
|
211
|
+
const seconds = elapsed % 60;
|
|
212
|
+
const screenshotStatus = document.getElementById('screenshotRecordingStatus');
|
|
213
|
+
if (screenshotStatus) {
|
|
214
|
+
screenshotStatus.innerHTML = `📸 Recording: <span class="recording-timer">${minutes}:${seconds.toString().padStart(2, '0')}</span>`;
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function stopScreenshotTimer() {
|
|
220
|
+
if (screenshotRecordingTimer) {
|
|
221
|
+
clearInterval(screenshotRecordingTimer);
|
|
222
|
+
screenshotRecordingTimer = null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
153
226
|
// Smart tiered port discovery configuration
|
|
154
227
|
const PORT_TIERS = {
|
|
155
228
|
// Try common ports first with short timeout (fast discovery)
|
|
@@ -477,33 +550,80 @@ chromedebug-mcp-server`;
|
|
|
477
550
|
|
|
478
551
|
|
|
479
552
|
function updateRecordingUI() {
|
|
553
|
+
console.log('[updateRecordingUI] Called with isRecording:', isRecording, 'isVideoModeActive:', isVideoModeActive);
|
|
480
554
|
const recordBtn = document.getElementById('recordBtn');
|
|
481
555
|
const recordingStatus = document.getElementById('recordingStatus');
|
|
482
556
|
const manualSnapshotBtn = document.getElementById('manualSnapshotBtn');
|
|
483
557
|
const countdownDisplay = document.getElementById('countdownDisplay');
|
|
484
|
-
|
|
558
|
+
const videoRecBtn = document.getElementById('startVideoRecordingBtn');
|
|
559
|
+
|
|
485
560
|
if (recordBtn) {
|
|
486
561
|
if (isRecording) {
|
|
487
|
-
|
|
488
|
-
|
|
562
|
+
// Check if we're in video mode
|
|
563
|
+
if (isVideoModeActive) {
|
|
564
|
+
console.log('[updateRecordingUI] VIDEO MODE: Setting recordBtn disabled, videoRecBtn to Stop');
|
|
565
|
+
// Video mode: grey out regular recording button
|
|
566
|
+
recordBtn.textContent = 'Start Recording';
|
|
567
|
+
recordBtn.disabled = true;
|
|
568
|
+
recordBtn.style.background = '#999';
|
|
569
|
+
recordBtn.classList.remove('recording');
|
|
570
|
+
|
|
571
|
+
// Update video button to show Stop
|
|
572
|
+
if (videoRecBtn) {
|
|
573
|
+
videoRecBtn.textContent = '🎥 Stop Video Recording';
|
|
574
|
+
videoRecBtn.style.background = '#f44336';
|
|
575
|
+
videoRecBtn.disabled = false;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (recordingStatus) {
|
|
579
|
+
recordingStatus.innerHTML = '<strong style="color: #4CAF50;">🎥 Video Recording Active</strong><br>' +
|
|
580
|
+
'<small>No indicator shown. Click Stop to end.</small>';
|
|
581
|
+
}
|
|
582
|
+
} else {
|
|
583
|
+
// Normal recording mode
|
|
584
|
+
console.log('[updateRecordingUI] NORMAL MODE: Setting recordBtn to Stop, videoRecBtn disabled');
|
|
585
|
+
recordBtn.textContent = 'Stop Recording';
|
|
586
|
+
recordBtn.classList.add('recording');
|
|
587
|
+
recordBtn.disabled = false;
|
|
588
|
+
recordBtn.style.background = '';
|
|
589
|
+
|
|
590
|
+
// Grey out video button during normal recording
|
|
591
|
+
if (videoRecBtn) {
|
|
592
|
+
videoRecBtn.textContent = '🎥 Start Video Recording';
|
|
593
|
+
videoRecBtn.disabled = true;
|
|
594
|
+
videoRecBtn.style.background = '#999';
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
489
598
|
startRecordingTimer();
|
|
490
|
-
|
|
599
|
+
|
|
491
600
|
// Show manual snapshot button
|
|
492
601
|
if (manualSnapshotBtn) {
|
|
493
602
|
manualSnapshotBtn.style.display = 'block';
|
|
494
603
|
}
|
|
495
|
-
|
|
496
|
-
// Show countdown if frame rate is 3+ seconds
|
|
497
|
-
if (currentFrameRate >= 3 && countdownDisplay) {
|
|
604
|
+
|
|
605
|
+
// Show countdown if frame rate is 3+ seconds (not in video mode)
|
|
606
|
+
if (!isVideoModeActive && currentFrameRate >= 3 && countdownDisplay) {
|
|
498
607
|
countdownDisplay.style.display = 'block';
|
|
499
608
|
startCountdownTimer();
|
|
500
609
|
}
|
|
501
610
|
} else {
|
|
611
|
+
// Not recording - reset everything
|
|
612
|
+
console.log('[updateRecordingUI] NOT RECORDING: Resetting both buttons');
|
|
502
613
|
recordBtn.textContent = 'Start Recording';
|
|
503
614
|
recordBtn.classList.remove('recording');
|
|
615
|
+
recordBtn.disabled = false;
|
|
616
|
+
recordBtn.style.background = '#f44336';
|
|
504
617
|
stopRecordingTimer();
|
|
505
618
|
stopCountdownTimer();
|
|
506
|
-
|
|
619
|
+
|
|
620
|
+
// Reset Video Recording button
|
|
621
|
+
if (videoRecBtn) {
|
|
622
|
+
videoRecBtn.textContent = '🎥 Start Video Recording';
|
|
623
|
+
videoRecBtn.disabled = false;
|
|
624
|
+
videoRecBtn.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
|
|
625
|
+
}
|
|
626
|
+
|
|
507
627
|
// Hide manual snapshot button and countdown
|
|
508
628
|
if (manualSnapshotBtn) {
|
|
509
629
|
manualSnapshotBtn.style.display = 'none';
|
|
@@ -511,10 +631,13 @@ function updateRecordingUI() {
|
|
|
511
631
|
if (countdownDisplay) {
|
|
512
632
|
countdownDisplay.style.display = 'none';
|
|
513
633
|
}
|
|
514
|
-
|
|
634
|
+
|
|
515
635
|
if (recordingStatus) {
|
|
516
636
|
recordingStatus.textContent = '';
|
|
517
637
|
}
|
|
638
|
+
|
|
639
|
+
// Reset video mode flag
|
|
640
|
+
isVideoModeActive = false;
|
|
518
641
|
}
|
|
519
642
|
}
|
|
520
643
|
}
|
|
@@ -771,7 +894,7 @@ function loadRecordings() {
|
|
|
771
894
|
});
|
|
772
895
|
}
|
|
773
896
|
|
|
774
|
-
function addRecording(recordingId, isSession = false, isFrameCapture = false, serverPort = null, type = null, sessionName = null) {
|
|
897
|
+
function addRecording(recordingId, isSession = false, isFrameCapture = false, serverPort = null, type = null, sessionName = null, isVideoMode = false) {
|
|
775
898
|
chrome.storage.local.get(['recordings'], (result) => {
|
|
776
899
|
const recordings = result.recordings || [];
|
|
777
900
|
recordings.unshift({
|
|
@@ -781,14 +904,77 @@ function addRecording(recordingId, isSession = false, isFrameCapture = false, se
|
|
|
781
904
|
isFrameCapture: isFrameCapture,
|
|
782
905
|
serverPort: serverPort,
|
|
783
906
|
type: type || (isSession ? 'session' : isFrameCapture ? 'recording' : 'recording'),
|
|
784
|
-
name: sessionName
|
|
907
|
+
name: sessionName,
|
|
908
|
+
isVideoMode: isVideoMode
|
|
785
909
|
}); // Add to beginning
|
|
786
910
|
chrome.storage.local.set({ recordings }, () => {
|
|
911
|
+
// Auto-switch to the relevant tab
|
|
912
|
+
if (isVideoMode) {
|
|
913
|
+
switchRecordingTab('video');
|
|
914
|
+
} else {
|
|
915
|
+
switchRecordingTab('screen');
|
|
916
|
+
}
|
|
787
917
|
updateRecordingsDisplay(recordings);
|
|
788
918
|
});
|
|
789
919
|
});
|
|
790
920
|
}
|
|
791
921
|
|
|
922
|
+
// Switch recording tab and update UI
|
|
923
|
+
function switchRecordingTab(tabName) {
|
|
924
|
+
recordingTabFilter = tabName;
|
|
925
|
+
|
|
926
|
+
// Update tab button styles
|
|
927
|
+
const allBtn = document.getElementById('tabAllRecordings');
|
|
928
|
+
const screenBtn = document.getElementById('tabScreenRecordings');
|
|
929
|
+
const videoBtn = document.getElementById('tabVideoRecordings');
|
|
930
|
+
|
|
931
|
+
[allBtn, screenBtn, videoBtn].forEach(btn => {
|
|
932
|
+
if (btn) {
|
|
933
|
+
btn.style.background = '#e0e0e0';
|
|
934
|
+
btn.style.color = '#333';
|
|
935
|
+
btn.classList.remove('active');
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
const activeBtn = tabName === 'all' ? allBtn : tabName === 'screen' ? screenBtn : videoBtn;
|
|
940
|
+
if (activeBtn) {
|
|
941
|
+
activeBtn.style.background = '#2196F3';
|
|
942
|
+
activeBtn.style.color = 'white';
|
|
943
|
+
activeBtn.classList.add('active');
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// Reload screen recordings with the new filter applied
|
|
947
|
+
loadScreenRecordings();
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// Switch workflow tab and update UI
|
|
951
|
+
function switchWorkflowTab(tabName) {
|
|
952
|
+
workflowTabFilter = tabName;
|
|
953
|
+
|
|
954
|
+
// Update tab button styles
|
|
955
|
+
const allBtn = document.getElementById('tabAllWorkflows');
|
|
956
|
+
const workflowBtn = document.getElementById('tabWorkflowOnly');
|
|
957
|
+
const screenshotBtn = document.getElementById('tabScreenshotsOnly');
|
|
958
|
+
|
|
959
|
+
[allBtn, workflowBtn, screenshotBtn].forEach(btn => {
|
|
960
|
+
if (btn) {
|
|
961
|
+
btn.style.background = '#e0e0e0';
|
|
962
|
+
btn.style.color = '#333';
|
|
963
|
+
btn.classList.remove('active');
|
|
964
|
+
}
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
const activeBtn = tabName === 'all' ? allBtn : tabName === 'workflow' ? workflowBtn : screenshotBtn;
|
|
968
|
+
if (activeBtn) {
|
|
969
|
+
activeBtn.style.background = '#9c27b0';
|
|
970
|
+
activeBtn.style.color = 'white';
|
|
971
|
+
activeBtn.classList.add('active');
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// Reload workflow recordings with the new filter applied
|
|
975
|
+
loadWorkflowRecordings();
|
|
976
|
+
}
|
|
977
|
+
|
|
792
978
|
function deleteRecording(recordingId) {
|
|
793
979
|
// First, tell the background script to delete from server
|
|
794
980
|
chrome.runtime.sendMessage({
|
|
@@ -806,23 +992,49 @@ function deleteRecording(recordingId) {
|
|
|
806
992
|
});
|
|
807
993
|
}
|
|
808
994
|
|
|
995
|
+
// Store all recordings for filtering
|
|
996
|
+
let allRecordings = [];
|
|
997
|
+
|
|
809
998
|
function updateRecordingsDisplay(recordings) {
|
|
810
999
|
const container = document.getElementById('recordingsContainer');
|
|
811
1000
|
const listDiv = document.getElementById('recordingsList');
|
|
812
1001
|
|
|
813
1002
|
if (!container || !listDiv) return;
|
|
814
1003
|
|
|
1004
|
+
// Store all recordings for tab filtering
|
|
1005
|
+
allRecordings = recordings;
|
|
1006
|
+
|
|
815
1007
|
if (recordings.length === 0) {
|
|
816
1008
|
listDiv.style.display = 'none';
|
|
817
1009
|
return;
|
|
818
1010
|
}
|
|
819
1011
|
|
|
820
1012
|
listDiv.style.display = 'block';
|
|
821
|
-
|
|
1013
|
+
|
|
1014
|
+
// Filter recordings based on selected tab
|
|
1015
|
+
let filteredRecordings = recordings;
|
|
1016
|
+
if (recordingTabFilter === 'video') {
|
|
1017
|
+
filteredRecordings = recordings.filter(r => normalizeIsVideoMode(r.isVideoMode));
|
|
1018
|
+
} else if (recordingTabFilter === 'screen') {
|
|
1019
|
+
filteredRecordings = recordings.filter(r => !normalizeIsVideoMode(r.isVideoMode));
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
if (filteredRecordings.length === 0) {
|
|
1023
|
+
container.innerHTML = '<div style="text-align: center; color: #999; padding: 20px; font-size: 11px;">No recordings in this category</div>';
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
container.innerHTML = filteredRecordings.map(recording => {
|
|
822
1028
|
const portInfo = recording.serverPort ? ` - Port: ${recording.serverPort}` : '';
|
|
823
1029
|
let typeInfo = '';
|
|
824
1030
|
let typeIcon = '';
|
|
825
1031
|
|
|
1032
|
+
// Determine recording type badge
|
|
1033
|
+
const isVideo = normalizeIsVideoMode(recording.isVideoMode);
|
|
1034
|
+
const typeBadge = isVideo
|
|
1035
|
+
? '<span style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; font-size: 8px; padding: 1px 4px; border-radius: 3px; margin-left: 4px;">VIDEO</span>'
|
|
1036
|
+
: '<span style="background: #4CAF50; color: white; font-size: 8px; padding: 1px 4px; border-radius: 3px; margin-left: 4px;">SCREEN</span>';
|
|
1037
|
+
|
|
826
1038
|
if (recording.type === 'snapshot') {
|
|
827
1039
|
typeInfo = ' (snapshot)';
|
|
828
1040
|
typeIcon = '📸 ';
|
|
@@ -841,7 +1053,7 @@ function updateRecordingsDisplay(recordings) {
|
|
|
841
1053
|
<div class="recording-info">
|
|
842
1054
|
<div class="recording-id" title="${recording.id}">
|
|
843
1055
|
<span class="recording-id-text">${typeIcon}${displayName}</span>
|
|
844
|
-
${typeInfo}${portInfo}
|
|
1056
|
+
${typeBadge}${typeInfo}${portInfo}
|
|
845
1057
|
</div>
|
|
846
1058
|
</div>
|
|
847
1059
|
<div class="recording-buttons">
|
|
@@ -1236,20 +1448,29 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1236
1448
|
'frameRate',
|
|
1237
1449
|
'imageQuality',
|
|
1238
1450
|
'frameFlash',
|
|
1451
|
+
'mouseTrackingEnabled',
|
|
1452
|
+
'mouseSampleInterval',
|
|
1239
1453
|
'workflowRecording',
|
|
1454
|
+
'screenshotRecording',
|
|
1240
1455
|
'includeLogsInExport',
|
|
1241
1456
|
'workflowScreenshotsEnabled',
|
|
1242
1457
|
'workflowScreenshotFormat',
|
|
1243
1458
|
'workflowScreenshotQuality',
|
|
1244
1459
|
'workflowScreenshotMaxWidth'
|
|
1245
1460
|
], (result) => {
|
|
1246
|
-
|
|
1461
|
+
|
|
1247
1462
|
// Restore workflow recording state
|
|
1248
1463
|
if (result.workflowRecording === true) {
|
|
1249
1464
|
isWorkflowRecording = true;
|
|
1250
1465
|
updateWorkflowRecordingUI();
|
|
1251
1466
|
}
|
|
1252
|
-
|
|
1467
|
+
|
|
1468
|
+
// Restore screenshot recording state
|
|
1469
|
+
if (result.screenshotRecording === true) {
|
|
1470
|
+
isScreenshotRecording = true;
|
|
1471
|
+
updateScreenshotRecordingUI();
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1253
1474
|
// Restore include logs checkbox state (default to true)
|
|
1254
1475
|
if (includeLogsCheckbox) {
|
|
1255
1476
|
includeLogsCheckbox.checked = result.includeLogsInExport !== false;
|
|
@@ -1284,6 +1505,20 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1284
1505
|
});
|
|
1285
1506
|
}
|
|
1286
1507
|
|
|
1508
|
+
// Restore mouse tracking settings (available for all users)
|
|
1509
|
+
const enableMouseTrackingCheckbox = document.getElementById('enableMouseTracking');
|
|
1510
|
+
const mouseSampleRateSelect = document.getElementById('mouseSampleRate');
|
|
1511
|
+
|
|
1512
|
+
if (enableMouseTrackingCheckbox && mouseSampleRateSelect) {
|
|
1513
|
+
// Mouse tracking is available for all users
|
|
1514
|
+
const mouseTrackingEnabled = result.mouseTrackingEnabled === true;
|
|
1515
|
+
const mouseSampleInterval = result.mouseSampleInterval || 100;
|
|
1516
|
+
|
|
1517
|
+
enableMouseTrackingCheckbox.checked = mouseTrackingEnabled;
|
|
1518
|
+
mouseSampleRateSelect.value = mouseSampleInterval;
|
|
1519
|
+
mouseSampleRateSelect.disabled = !mouseTrackingEnabled;
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1287
1522
|
// Restore workflow screenshot settings
|
|
1288
1523
|
const enableScreenshotsCheckbox = document.getElementById('enableScreenshotsCheckbox');
|
|
1289
1524
|
const screenshotFormat = document.getElementById('screenshotFormat');
|
|
@@ -1321,10 +1556,51 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1321
1556
|
// Load saved recordings (legacy)
|
|
1322
1557
|
loadRecordings();
|
|
1323
1558
|
|
|
1559
|
+
// Setup recording type tab handlers
|
|
1560
|
+
const tabAllBtn = document.getElementById('tabAllRecordings');
|
|
1561
|
+
const tabScreenBtn = document.getElementById('tabScreenRecordings');
|
|
1562
|
+
const tabVideoBtn = document.getElementById('tabVideoRecordings');
|
|
1563
|
+
|
|
1564
|
+
if (tabAllBtn) {
|
|
1565
|
+
tabAllBtn.addEventListener('click', () => switchRecordingTab('all'));
|
|
1566
|
+
}
|
|
1567
|
+
if (tabScreenBtn) {
|
|
1568
|
+
tabScreenBtn.addEventListener('click', () => switchRecordingTab('screen'));
|
|
1569
|
+
}
|
|
1570
|
+
if (tabVideoBtn) {
|
|
1571
|
+
tabVideoBtn.addEventListener('click', () => switchRecordingTab('video'));
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
// Setup workflow type tab handlers
|
|
1575
|
+
const tabAllWorkflowsBtn = document.getElementById('tabAllWorkflows');
|
|
1576
|
+
const tabWorkflowOnlyBtn = document.getElementById('tabWorkflowOnly');
|
|
1577
|
+
const tabScreenshotsOnlyBtn = document.getElementById('tabScreenshotsOnly');
|
|
1578
|
+
|
|
1579
|
+
if (tabAllWorkflowsBtn) {
|
|
1580
|
+
tabAllWorkflowsBtn.addEventListener('click', () => switchWorkflowTab('all'));
|
|
1581
|
+
}
|
|
1582
|
+
if (tabWorkflowOnlyBtn) {
|
|
1583
|
+
tabWorkflowOnlyBtn.addEventListener('click', () => switchWorkflowTab('workflow'));
|
|
1584
|
+
}
|
|
1585
|
+
if (tabScreenshotsOnlyBtn) {
|
|
1586
|
+
tabScreenshotsOnlyBtn.addEventListener('click', () => switchWorkflowTab('screenshot'));
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1324
1589
|
// Load screen and workflow recordings with saved preferences
|
|
1325
1590
|
loadScreenRecordings();
|
|
1326
1591
|
loadWorkflowRecordings();
|
|
1327
|
-
|
|
1592
|
+
|
|
1593
|
+
// Setup Delete All button event listeners
|
|
1594
|
+
const deleteAllWorkflowsBtn = document.getElementById('deleteAllWorkflowsBtn');
|
|
1595
|
+
if (deleteAllWorkflowsBtn) {
|
|
1596
|
+
deleteAllWorkflowsBtn.addEventListener('click', deleteAllWorkflowRecordings);
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
const deleteAllScreenRecordingsBtn = document.getElementById('deleteAllScreenRecordingsBtn');
|
|
1600
|
+
if (deleteAllScreenRecordingsBtn) {
|
|
1601
|
+
deleteAllScreenRecordingsBtn.addEventListener('click', deleteAllScreenRecordings);
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1328
1604
|
// Setup recording settings event listeners
|
|
1329
1605
|
const frameRateSelect = document.getElementById('frameRate');
|
|
1330
1606
|
const imageQualitySelect = document.getElementById('imageQuality');
|
|
@@ -1347,12 +1623,29 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1347
1623
|
chrome.storage.local.set({ frameFlash: frameFlashCheckbox.checked });
|
|
1348
1624
|
});
|
|
1349
1625
|
}
|
|
1350
|
-
|
|
1626
|
+
|
|
1627
|
+
// Setup mouse tracking event listeners (PRO-only)
|
|
1628
|
+
const enableMouseTrackingCheckbox = document.getElementById('enableMouseTracking');
|
|
1629
|
+
const mouseSampleRateSelect = document.getElementById('mouseSampleRate');
|
|
1630
|
+
|
|
1631
|
+
if (enableMouseTrackingCheckbox && mouseSampleRateSelect) {
|
|
1632
|
+
enableMouseTrackingCheckbox.addEventListener('change', () => {
|
|
1633
|
+
const isEnabled = enableMouseTrackingCheckbox.checked && IS_PRO_VERSION;
|
|
1634
|
+
mouseSampleRateSelect.disabled = !isEnabled;
|
|
1635
|
+
chrome.storage.local.set({ mouseTrackingEnabled: isEnabled });
|
|
1636
|
+
});
|
|
1637
|
+
|
|
1638
|
+
mouseSampleRateSelect.addEventListener('change', () => {
|
|
1639
|
+
chrome.storage.local.set({ mouseSampleInterval: parseInt(mouseSampleRateSelect.value) });
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1351
1643
|
// Check initial recording state from storage
|
|
1352
|
-
chrome.storage.local.get(['recordingActive', 'recordingStartTime', 'pendingRecording', 'pendingTabId'], (result) => {
|
|
1644
|
+
chrome.storage.local.get(['recordingActive', 'recordingStartTime', 'pendingRecording', 'pendingTabId', 'isVideoModeActive'], (result) => {
|
|
1353
1645
|
if (result.recordingActive) {
|
|
1354
1646
|
isRecording = true;
|
|
1355
1647
|
recordingStartTime = result.recordingStartTime || Date.now();
|
|
1648
|
+
isVideoModeActive = result.isVideoModeActive || false;
|
|
1356
1649
|
updateRecordingUI();
|
|
1357
1650
|
// console.log('Restored recording state from storage');
|
|
1358
1651
|
} else if (result.pendingRecording && result.pendingTabId) {
|
|
@@ -1457,6 +1750,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1457
1750
|
console.error('Error starting recording:', chrome.runtime.lastError);
|
|
1458
1751
|
recordingStatus.textContent = 'Error: ' + chrome.runtime.lastError.message;
|
|
1459
1752
|
recordBtn.textContent = 'Start Recording';
|
|
1753
|
+
// Reset Video Recording button on error
|
|
1754
|
+
const videoRecBtn = document.getElementById('startVideoRecordingBtn');
|
|
1755
|
+
if (videoRecBtn) {
|
|
1756
|
+
videoRecBtn.textContent = '🎥 Video Recording';
|
|
1757
|
+
videoRecBtn.disabled = false;
|
|
1758
|
+
videoRecBtn.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
|
|
1759
|
+
}
|
|
1460
1760
|
return;
|
|
1461
1761
|
}
|
|
1462
1762
|
|
|
@@ -1464,14 +1764,36 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1464
1764
|
// console.log('Recording started successfully');
|
|
1465
1765
|
isRecording = true;
|
|
1466
1766
|
recordingStartTime = Date.now();
|
|
1467
|
-
|
|
1767
|
+
|
|
1468
1768
|
// Capture current frame rate for countdown timer
|
|
1469
1769
|
currentFrameRate = settings.frameRate;
|
|
1470
|
-
|
|
1770
|
+
|
|
1771
|
+
// Send mouse tracking settings to content script (available for all users)
|
|
1772
|
+
chrome.storage.local.get(['mouseTrackingEnabled', 'mouseSampleInterval'], (mouseSettings) => {
|
|
1773
|
+
const mouseTrackingEnabled = mouseSettings.mouseTrackingEnabled === true;
|
|
1774
|
+
const mouseSampleInterval = mouseSettings.mouseSampleInterval || 100;
|
|
1775
|
+
|
|
1776
|
+
chrome.tabs.sendMessage(tab.id, {
|
|
1777
|
+
action: 'setMouseTrackingSettings',
|
|
1778
|
+
enabled: mouseTrackingEnabled,
|
|
1779
|
+
sampleInterval: mouseSampleInterval
|
|
1780
|
+
}).catch(err => {
|
|
1781
|
+
// Ignore errors if content script isn't ready yet
|
|
1782
|
+
// console.warn('[Mouse Tracking] Failed to send settings:', err);
|
|
1783
|
+
});
|
|
1784
|
+
});
|
|
1785
|
+
|
|
1471
1786
|
updateRecordingUI();
|
|
1472
1787
|
} else {
|
|
1473
1788
|
recordingStatus.textContent = 'Error: ' + (response?.error || 'Failed to start recording');
|
|
1474
1789
|
recordBtn.textContent = 'Start Recording';
|
|
1790
|
+
// Reset Video Recording button on error
|
|
1791
|
+
const videoRecBtn = document.getElementById('startVideoRecordingBtn');
|
|
1792
|
+
if (videoRecBtn) {
|
|
1793
|
+
videoRecBtn.textContent = '🎥 Video Recording';
|
|
1794
|
+
videoRecBtn.disabled = false;
|
|
1795
|
+
videoRecBtn.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
|
|
1796
|
+
}
|
|
1475
1797
|
}
|
|
1476
1798
|
});
|
|
1477
1799
|
} catch (error) {
|
|
@@ -1481,6 +1803,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1481
1803
|
}
|
|
1482
1804
|
recordBtn.disabled = false;
|
|
1483
1805
|
recordBtn.textContent = 'Start Recording';
|
|
1806
|
+
// Reset Video Recording button on error
|
|
1807
|
+
const videoRecBtn = document.getElementById('startVideoRecordingBtn');
|
|
1808
|
+
if (videoRecBtn) {
|
|
1809
|
+
videoRecBtn.textContent = '🎥 Video Recording';
|
|
1810
|
+
videoRecBtn.disabled = false;
|
|
1811
|
+
videoRecBtn.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
|
|
1812
|
+
}
|
|
1484
1813
|
}
|
|
1485
1814
|
} else if (isRecording && !isStoppingRecording) {
|
|
1486
1815
|
// Stop recording
|
|
@@ -1548,6 +1877,216 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1548
1877
|
});
|
|
1549
1878
|
}
|
|
1550
1879
|
|
|
1880
|
+
// Setup Video Recording button handler (optimized for video export)
|
|
1881
|
+
const startVideoRecordingBtn = document.getElementById('startVideoRecordingBtn');
|
|
1882
|
+
// isVideoModeActive is now a global variable defined at the top
|
|
1883
|
+
|
|
1884
|
+
if (startVideoRecordingBtn) {
|
|
1885
|
+
startVideoRecordingBtn.addEventListener('click', async (e) => {
|
|
1886
|
+
e.preventDefault();
|
|
1887
|
+
|
|
1888
|
+
// Immediate guard against double-clicks
|
|
1889
|
+
if (startVideoRecordingBtn.disabled) return;
|
|
1890
|
+
|
|
1891
|
+
// Handle stop action if video recording is active
|
|
1892
|
+
if (isVideoModeActive && isRecording) {
|
|
1893
|
+
try {
|
|
1894
|
+
startVideoRecordingBtn.disabled = true;
|
|
1895
|
+
startVideoRecordingBtn.textContent = '🎥 Stopping...';
|
|
1896
|
+
|
|
1897
|
+
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
1898
|
+
if (tab && tab.id) {
|
|
1899
|
+
await chrome.runtime.sendMessage({
|
|
1900
|
+
action: 'stopRecording',
|
|
1901
|
+
tabId: tab.id
|
|
1902
|
+
});
|
|
1903
|
+
|
|
1904
|
+
// Reset UI state
|
|
1905
|
+
isRecording = false;
|
|
1906
|
+
isVideoModeActive = false;
|
|
1907
|
+
|
|
1908
|
+
// Clear video mode and recording state from storage
|
|
1909
|
+
chrome.storage.local.set({
|
|
1910
|
+
isVideoModeActive: false,
|
|
1911
|
+
recordingActive: false
|
|
1912
|
+
});
|
|
1913
|
+
|
|
1914
|
+
// Use updateRecordingUI() for consistent button state management
|
|
1915
|
+
updateRecordingUI();
|
|
1916
|
+
|
|
1917
|
+
if (recordingStatus) {
|
|
1918
|
+
recordingStatus.textContent = 'Video recording stopped.';
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
} catch (error) {
|
|
1922
|
+
console.error('Error stopping video recording:', error);
|
|
1923
|
+
// Re-enable button on error (keep as Stop since recording may still be active)
|
|
1924
|
+
startVideoRecordingBtn.disabled = false;
|
|
1925
|
+
}
|
|
1926
|
+
return;
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
if (isRecording || isStoppingRecording) {
|
|
1930
|
+
// Already recording (non-video mode) - show message
|
|
1931
|
+
if (recordingStatus) {
|
|
1932
|
+
recordingStatus.textContent = 'Recording already in progress. Stop it first.';
|
|
1933
|
+
}
|
|
1934
|
+
return;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
try {
|
|
1938
|
+
// Check FREE tier usage limit before starting
|
|
1939
|
+
const usageCheck = await chrome.runtime.sendMessage({ action: 'checkUsageLimit' });
|
|
1940
|
+
if (usageCheck && !usageCheck.allowed) {
|
|
1941
|
+
if (recordingStatus) {
|
|
1942
|
+
recordingStatus.innerHTML = '<strong style="color: #f44336;">Daily limit reached</strong><br>' +
|
|
1943
|
+
`<small>${usageCheck.count}/${usageCheck.limit} recordings used today.<br>Upgrade to Pro for unlimited.</small>`;
|
|
1944
|
+
}
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
// Get the current active tab
|
|
1949
|
+
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
1950
|
+
|
|
1951
|
+
if (!tab || !tab.id) {
|
|
1952
|
+
console.error('No active tab found');
|
|
1953
|
+
if (recordingStatus) {
|
|
1954
|
+
recordingStatus.textContent = 'Error: No active tab';
|
|
1955
|
+
}
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
// Check if server is running
|
|
1960
|
+
const serverStatus = await checkServerStatus();
|
|
1961
|
+
const isBrowserOnlyMode = !serverStatus.connected;
|
|
1962
|
+
|
|
1963
|
+
// Set optimal video settings (4 FPS, 90% quality, mouse tracking ON)
|
|
1964
|
+
const videoSettings = {
|
|
1965
|
+
frameRate: 0.25, // 4 FPS for smooth video playback
|
|
1966
|
+
imageQuality: 90, // Best quality for promotional content
|
|
1967
|
+
frameFlash: false, // No flash during capture
|
|
1968
|
+
videoMode: true // Flag for content.js to hide indicator
|
|
1969
|
+
};
|
|
1970
|
+
|
|
1971
|
+
// Save settings temporarily for this session
|
|
1972
|
+
await chrome.storage.local.set({
|
|
1973
|
+
mouseTrackingEnabled: true, // Auto-enable mouse tracking for video mode
|
|
1974
|
+
mouseSampleInterval: 50 // Fast mouse tracking (50ms)
|
|
1975
|
+
});
|
|
1976
|
+
|
|
1977
|
+
// Update UI elements to reflect video mode settings
|
|
1978
|
+
const frameRateSelect = document.getElementById('frameRate');
|
|
1979
|
+
const imageQualitySelect = document.getElementById('imageQuality');
|
|
1980
|
+
const enableMouseTrackingCheckbox = document.getElementById('enableMouseTracking');
|
|
1981
|
+
const mouseSampleRateSelect = document.getElementById('mouseSampleRate');
|
|
1982
|
+
|
|
1983
|
+
if (frameRateSelect) frameRateSelect.value = '0.5'; // Closest available option
|
|
1984
|
+
if (imageQualitySelect) imageQualitySelect.value = '70'; // Closest available option
|
|
1985
|
+
if (enableMouseTrackingCheckbox) {
|
|
1986
|
+
enableMouseTrackingCheckbox.checked = true;
|
|
1987
|
+
if (mouseSampleRateSelect) {
|
|
1988
|
+
mouseSampleRateSelect.disabled = false;
|
|
1989
|
+
mouseSampleRateSelect.value = '50';
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
// Disable button while starting
|
|
1994
|
+
startVideoRecordingBtn.textContent = '🎥 Starting...';
|
|
1995
|
+
startVideoRecordingBtn.disabled = true;
|
|
1996
|
+
|
|
1997
|
+
// Start recording with video mode settings
|
|
1998
|
+
console.log('[Video Recording] Sending settings to background:', {
|
|
1999
|
+
frameRate: videoSettings.frameRate,
|
|
2000
|
+
imageQuality: videoSettings.imageQuality,
|
|
2001
|
+
frameFlash: videoSettings.frameFlash,
|
|
2002
|
+
videoMode: true,
|
|
2003
|
+
maxWidth: 1280,
|
|
2004
|
+
maxHeight: 720
|
|
2005
|
+
});
|
|
2006
|
+
const response = await new Promise((resolve) => {
|
|
2007
|
+
chrome.runtime.sendMessage({
|
|
2008
|
+
action: 'startRecording',
|
|
2009
|
+
tabId: tab.id,
|
|
2010
|
+
settings: {
|
|
2011
|
+
frameRate: videoSettings.frameRate,
|
|
2012
|
+
imageQuality: videoSettings.imageQuality,
|
|
2013
|
+
frameFlash: videoSettings.frameFlash,
|
|
2014
|
+
videoMode: true, // Tells content.js to hide indicator after countdown
|
|
2015
|
+
maxWidth: 1280, // HD resolution for video export
|
|
2016
|
+
maxHeight: 720
|
|
2017
|
+
},
|
|
2018
|
+
mode: isBrowserOnlyMode ? 'browser-only' : 'server'
|
|
2019
|
+
}, resolve);
|
|
2020
|
+
});
|
|
2021
|
+
|
|
2022
|
+
if (response && response.success) {
|
|
2023
|
+
console.log('[Video Recording] Recording started successfully, setting isVideoModeActive = true');
|
|
2024
|
+
isRecording = true;
|
|
2025
|
+
isVideoModeActive = true; // Track that we're in video mode
|
|
2026
|
+
recordingStartTime = Date.now();
|
|
2027
|
+
currentFrameRate = videoSettings.frameRate;
|
|
2028
|
+
|
|
2029
|
+
// Save video mode state to storage for popup reopen
|
|
2030
|
+
chrome.storage.local.set({
|
|
2031
|
+
isVideoModeActive: true,
|
|
2032
|
+
recordingActive: true,
|
|
2033
|
+
recordingStartTime: recordingStartTime
|
|
2034
|
+
});
|
|
2035
|
+
|
|
2036
|
+
// Send mouse tracking settings to content script
|
|
2037
|
+
chrome.tabs.sendMessage(tab.id, {
|
|
2038
|
+
action: 'setMouseTrackingSettings',
|
|
2039
|
+
enabled: true,
|
|
2040
|
+
sampleInterval: 50
|
|
2041
|
+
}).catch(err => {
|
|
2042
|
+
// Ignore errors if content script isn't ready yet
|
|
2043
|
+
});
|
|
2044
|
+
|
|
2045
|
+
console.log('[Video Recording] Calling updateRecordingUI with isVideoModeActive:', isVideoModeActive);
|
|
2046
|
+
// Use updateRecordingUI() for consistent button state management
|
|
2047
|
+
// This ensures all buttons are properly updated based on isVideoModeActive flag
|
|
2048
|
+
updateRecordingUI();
|
|
2049
|
+
|
|
2050
|
+
// ALSO explicitly set button states here as backup
|
|
2051
|
+
const recordBtn = document.getElementById('recordBtn');
|
|
2052
|
+
const videoRecBtn = document.getElementById('startVideoRecordingBtn');
|
|
2053
|
+
if (recordBtn) {
|
|
2054
|
+
recordBtn.textContent = 'Start Recording';
|
|
2055
|
+
recordBtn.disabled = true;
|
|
2056
|
+
recordBtn.style.background = '#999';
|
|
2057
|
+
}
|
|
2058
|
+
if (videoRecBtn) {
|
|
2059
|
+
videoRecBtn.textContent = '🎥 Stop Video Recording';
|
|
2060
|
+
videoRecBtn.style.background = '#f44336';
|
|
2061
|
+
videoRecBtn.disabled = false;
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
if (recordingStatus) {
|
|
2065
|
+
recordingStatus.innerHTML = '<strong style="color: #4CAF50;">🎥 Video Recording Active</strong><br>' +
|
|
2066
|
+
'<small>Click 🎥 Stop Video Recording to end.</small>';
|
|
2067
|
+
}
|
|
2068
|
+
} else {
|
|
2069
|
+
if (recordingStatus) {
|
|
2070
|
+
recordingStatus.textContent = 'Error: ' + (response?.error || 'Failed to start video recording');
|
|
2071
|
+
}
|
|
2072
|
+
// Reset state and UI on failure
|
|
2073
|
+
isRecording = false;
|
|
2074
|
+
isVideoModeActive = false;
|
|
2075
|
+
updateRecordingUI();
|
|
2076
|
+
}
|
|
2077
|
+
} catch (error) {
|
|
2078
|
+
console.error('Error starting video recording:', error);
|
|
2079
|
+
if (recordingStatus) {
|
|
2080
|
+
recordingStatus.textContent = 'Error: ' + error.message;
|
|
2081
|
+
}
|
|
2082
|
+
// Reset state and UI on error
|
|
2083
|
+
isRecording = false;
|
|
2084
|
+
isVideoModeActive = false;
|
|
2085
|
+
updateRecordingUI();
|
|
2086
|
+
}
|
|
2087
|
+
});
|
|
2088
|
+
}
|
|
2089
|
+
|
|
1551
2090
|
/*
|
|
1552
2091
|
* SNAPSHOT FEATURE DISABLED (2025-10-01)
|
|
1553
2092
|
* Snapshot button click handler disabled - see SNAPSHOT_FEATURE_DISABLED.md
|
|
@@ -1572,39 +2111,60 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1572
2111
|
workflowRecordBtn.addEventListener('click', async () => {
|
|
1573
2112
|
if (!isWorkflowRecording) {
|
|
1574
2113
|
// Start workflow recording
|
|
2114
|
+
console.log('[Workflow] Button clicked');
|
|
2115
|
+
|
|
2116
|
+
// Store original button text and disable button
|
|
2117
|
+
const originalButtonText = workflowRecordBtn.textContent;
|
|
2118
|
+
workflowRecordBtn.disabled = true;
|
|
2119
|
+
workflowRecordBtn.textContent = '⏳ Checking...';
|
|
2120
|
+
|
|
1575
2121
|
try {
|
|
1576
2122
|
// Check if server is running before starting workflow recording
|
|
1577
2123
|
const serverStatus = await checkServerStatus();
|
|
1578
2124
|
if (!serverStatus.connected) {
|
|
1579
|
-
console.error('Server not running');
|
|
2125
|
+
console.error('[Workflow] Server not running');
|
|
1580
2126
|
const workflowStatus = document.getElementById('workflowRecordingStatus');
|
|
1581
2127
|
if (workflowStatus) {
|
|
1582
2128
|
workflowStatus.innerHTML = '<strong style="color: #f44336;">Error: Server not running</strong><br>' +
|
|
1583
2129
|
'<small>Please start the Chrome Debug server first</small>';
|
|
1584
2130
|
}
|
|
2131
|
+
// Show toast notification for better visibility
|
|
2132
|
+
showToast('Server not running. Start Chrome Debug server first.', 4000, 'error');
|
|
1585
2133
|
// Clear error message after 5 seconds
|
|
1586
2134
|
setTimeout(() => {
|
|
1587
2135
|
if (workflowStatus) workflowStatus.textContent = '';
|
|
1588
2136
|
}, 5000);
|
|
2137
|
+
// Restore button state
|
|
2138
|
+
workflowRecordBtn.disabled = false;
|
|
2139
|
+
workflowRecordBtn.textContent = originalButtonText;
|
|
1589
2140
|
return;
|
|
1590
2141
|
}
|
|
1591
2142
|
|
|
1592
2143
|
// Check FREE tier usage limit before starting
|
|
1593
2144
|
const usageCheck = await chrome.runtime.sendMessage({ action: 'checkUsageLimit' });
|
|
1594
2145
|
if (usageCheck && !usageCheck.allowed) {
|
|
1595
|
-
|
|
2146
|
+
console.log('[Workflow] Daily limit reached:', usageCheck);
|
|
1596
2147
|
const workflowStatus = document.getElementById('workflowRecordingStatus');
|
|
1597
2148
|
if (workflowStatus) {
|
|
1598
2149
|
workflowStatus.innerHTML = '<strong style="color: #f44336;">Daily limit reached</strong><br>' +
|
|
1599
2150
|
`<small>${usageCheck.count}/${usageCheck.limit} recordings used today.<br>Upgrade to Pro for unlimited recordings.</small>`;
|
|
1600
2151
|
}
|
|
2152
|
+
// Show toast notification for better visibility
|
|
2153
|
+
showToast(`Daily limit reached (${usageCheck.count}/${usageCheck.limit}). Upgrade to Pro for unlimited recordings.`, 4000, 'error');
|
|
2154
|
+
// Restore button state
|
|
2155
|
+
workflowRecordBtn.disabled = false;
|
|
2156
|
+
workflowRecordBtn.textContent = originalButtonText;
|
|
1601
2157
|
return;
|
|
1602
2158
|
}
|
|
1603
2159
|
|
|
1604
2160
|
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
1605
2161
|
|
|
1606
2162
|
if (!tab || !tab.id) {
|
|
1607
|
-
console.error('No active tab found');
|
|
2163
|
+
console.error('[Workflow] No active tab found');
|
|
2164
|
+
showToast('No active tab found. Please try again.', 3000, 'error');
|
|
2165
|
+
// Restore button state
|
|
2166
|
+
workflowRecordBtn.disabled = false;
|
|
2167
|
+
workflowRecordBtn.textContent = originalButtonText;
|
|
1608
2168
|
return;
|
|
1609
2169
|
}
|
|
1610
2170
|
|
|
@@ -1666,13 +2226,21 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1666
2226
|
isWorkflowRecording = true;
|
|
1667
2227
|
// Save state and start time to storage
|
|
1668
2228
|
const startTime = Date.now();
|
|
1669
|
-
chrome.storage.local.set({
|
|
2229
|
+
chrome.storage.local.set({
|
|
1670
2230
|
workflowRecording: true,
|
|
1671
2231
|
workflowStartTime: startTime
|
|
1672
2232
|
});
|
|
1673
2233
|
workflowStartTime = startTime;
|
|
2234
|
+
// Re-enable button and update UI for recording state
|
|
2235
|
+
workflowRecordBtn.disabled = false;
|
|
1674
2236
|
updateWorkflowRecordingUI();
|
|
1675
2237
|
if (workflowRecordingsList) workflowRecordingsList.style.display = 'none';
|
|
2238
|
+
} else {
|
|
2239
|
+
// Retry failed
|
|
2240
|
+
console.error('[Workflow] Retry failed:', retryResponse);
|
|
2241
|
+
showToast('Failed to start workflow recording after content script injection.', 3000, 'error');
|
|
2242
|
+
workflowRecordBtn.disabled = false;
|
|
2243
|
+
workflowRecordBtn.textContent = originalButtonText;
|
|
1676
2244
|
}
|
|
1677
2245
|
});
|
|
1678
2246
|
}, 100);
|
|
@@ -1687,14 +2255,28 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1687
2255
|
workflowStartTime: startTime
|
|
1688
2256
|
});
|
|
1689
2257
|
workflowStartTime = startTime;
|
|
2258
|
+
// Re-enable button and update UI for recording state
|
|
2259
|
+
workflowRecordBtn.disabled = false;
|
|
1690
2260
|
updateWorkflowRecordingUI();
|
|
1691
2261
|
// Hide any previous results
|
|
1692
2262
|
if (workflowRecordingsList) workflowRecordingsList.style.display = 'none';
|
|
2263
|
+
} else {
|
|
2264
|
+
// Response received but not successful
|
|
2265
|
+
console.error('[Workflow] Failed to start recording:', response);
|
|
2266
|
+
showToast('Failed to start workflow recording. Please try again.', 3000, 'error');
|
|
2267
|
+
workflowRecordBtn.disabled = false;
|
|
2268
|
+
workflowRecordBtn.textContent = originalButtonText;
|
|
1693
2269
|
}
|
|
1694
2270
|
});
|
|
1695
2271
|
} catch (error) {
|
|
1696
|
-
console.error('Error starting workflow recording:', error);
|
|
2272
|
+
console.error('[Workflow] Error starting workflow recording:', error);
|
|
2273
|
+
showToast('Error starting workflow recording. Please try again.', 3000, 'error');
|
|
2274
|
+
// Restore button state on error
|
|
2275
|
+
workflowRecordBtn.disabled = false;
|
|
2276
|
+
workflowRecordBtn.textContent = originalButtonText;
|
|
1697
2277
|
}
|
|
2278
|
+
// Note: Button state is managed by updateWorkflowRecordingUI() on success
|
|
2279
|
+
// or restored explicitly in catch/early returns on failure
|
|
1698
2280
|
} else {
|
|
1699
2281
|
// Stop workflow recording
|
|
1700
2282
|
try {
|
|
@@ -1765,7 +2347,241 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1765
2347
|
chrome.storage.local.set({ includeLogsInExport: includeLogsCheckbox.checked });
|
|
1766
2348
|
});
|
|
1767
2349
|
}
|
|
1768
|
-
|
|
2350
|
+
|
|
2351
|
+
// Setup screenshot recording button
|
|
2352
|
+
const screenshotRecordBtn = document.getElementById('screenshotRecordBtn');
|
|
2353
|
+
|
|
2354
|
+
if (screenshotRecordBtn) {
|
|
2355
|
+
screenshotRecordBtn.addEventListener('click', async () => {
|
|
2356
|
+
if (!isScreenshotRecording) {
|
|
2357
|
+
// Start screenshot recording
|
|
2358
|
+
console.log('[Screenshot] Button clicked');
|
|
2359
|
+
|
|
2360
|
+
// Store original button text and disable button
|
|
2361
|
+
const originalButtonText = screenshotRecordBtn.textContent;
|
|
2362
|
+
screenshotRecordBtn.disabled = true;
|
|
2363
|
+
screenshotRecordBtn.textContent = '⏳ Checking...';
|
|
2364
|
+
|
|
2365
|
+
try {
|
|
2366
|
+
// Check if server is running before starting screenshot recording
|
|
2367
|
+
const serverStatus = await checkServerStatus();
|
|
2368
|
+
if (!serverStatus.connected) {
|
|
2369
|
+
console.error('[Screenshot] Server not running');
|
|
2370
|
+
const screenshotStatus = document.getElementById('screenshotRecordingStatus');
|
|
2371
|
+
if (screenshotStatus) {
|
|
2372
|
+
screenshotStatus.innerHTML = '<strong style="color: #f44336;">Error: Server not running</strong><br>' +
|
|
2373
|
+
'<small>Please start the Chrome Debug server first</small>';
|
|
2374
|
+
}
|
|
2375
|
+
// Show toast notification for better visibility
|
|
2376
|
+
showToast('Server not running. Start Chrome Debug server first.', 4000, 'error');
|
|
2377
|
+
// Clear error message after 5 seconds
|
|
2378
|
+
setTimeout(() => {
|
|
2379
|
+
if (screenshotStatus) screenshotStatus.textContent = '';
|
|
2380
|
+
}, 5000);
|
|
2381
|
+
// Restore button state
|
|
2382
|
+
screenshotRecordBtn.disabled = false;
|
|
2383
|
+
screenshotRecordBtn.textContent = originalButtonText;
|
|
2384
|
+
return;
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
// Check FREE tier usage limit before starting
|
|
2388
|
+
const usageCheck = await chrome.runtime.sendMessage({ action: 'checkUsageLimit' });
|
|
2389
|
+
if (usageCheck && !usageCheck.allowed) {
|
|
2390
|
+
console.log('[Screenshot] Daily limit reached:', usageCheck);
|
|
2391
|
+
const screenshotStatus = document.getElementById('screenshotRecordingStatus');
|
|
2392
|
+
if (screenshotStatus) {
|
|
2393
|
+
screenshotStatus.innerHTML = '<strong style="color: #f44336;">Daily limit reached</strong><br>' +
|
|
2394
|
+
`<small>${usageCheck.count}/${usageCheck.limit} recordings used today.<br>Upgrade to Pro for unlimited recordings.</small>`;
|
|
2395
|
+
}
|
|
2396
|
+
// Show toast notification for better visibility
|
|
2397
|
+
showToast(`Daily limit reached (${usageCheck.count}/${usageCheck.limit}). Upgrade to Pro for unlimited recordings.`, 4000, 'error');
|
|
2398
|
+
// Restore button state
|
|
2399
|
+
screenshotRecordBtn.disabled = false;
|
|
2400
|
+
screenshotRecordBtn.textContent = originalButtonText;
|
|
2401
|
+
return;
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
2405
|
+
|
|
2406
|
+
if (!tab || !tab.id) {
|
|
2407
|
+
console.error('[Screenshot] No active tab found');
|
|
2408
|
+
showToast('No active tab found. Please try again.', 3000, 'error');
|
|
2409
|
+
// Restore button state
|
|
2410
|
+
screenshotRecordBtn.disabled = false;
|
|
2411
|
+
screenshotRecordBtn.textContent = originalButtonText;
|
|
2412
|
+
return;
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
// Get screenshot recording settings
|
|
2416
|
+
const hideCursor = document.getElementById('hideMouseCursorCheckbox')?.checked ?? true;
|
|
2417
|
+
const countdown = parseInt(document.getElementById('screenshotCountdown')?.value) || 3;
|
|
2418
|
+
|
|
2419
|
+
const screenshotModeSettings = {
|
|
2420
|
+
hideCursor: hideCursor,
|
|
2421
|
+
countdown: countdown
|
|
2422
|
+
};
|
|
2423
|
+
|
|
2424
|
+
// Send message to background script to start screenshot recording
|
|
2425
|
+
chrome.runtime.sendMessage({
|
|
2426
|
+
action: 'startScreenshotRecording',
|
|
2427
|
+
tabId: tab.id,
|
|
2428
|
+
settings: screenshotModeSettings
|
|
2429
|
+
}, (response) => {
|
|
2430
|
+
if (chrome.runtime.lastError) {
|
|
2431
|
+
// Check if this tab allows content script injection
|
|
2432
|
+
if (!tab.url || tab.url.startsWith('chrome://') || tab.url.startsWith('chrome-extension://') || tab.url.startsWith('moz-extension://')) {
|
|
2433
|
+
const screenshotStatus = document.getElementById('screenshotRecordingStatus');
|
|
2434
|
+
if (screenshotStatus) {
|
|
2435
|
+
screenshotStatus.innerHTML = '<strong style="color: #f44336;">Cannot record on this page</strong>';
|
|
2436
|
+
}
|
|
2437
|
+
return;
|
|
2438
|
+
}
|
|
2439
|
+
// Inject content script if needed
|
|
2440
|
+
chrome.scripting.executeScript({
|
|
2441
|
+
target: { tabId: tab.id },
|
|
2442
|
+
files: ['content.js']
|
|
2443
|
+
}, () => {
|
|
2444
|
+
if (chrome.runtime.lastError) {
|
|
2445
|
+
console.error('Failed to inject content script:', chrome.runtime.lastError);
|
|
2446
|
+
return;
|
|
2447
|
+
}
|
|
2448
|
+
chrome.scripting.insertCSS({
|
|
2449
|
+
target: { tabId: tab.id },
|
|
2450
|
+
files: ['content.css']
|
|
2451
|
+
}, () => {
|
|
2452
|
+
if (chrome.runtime.lastError) {
|
|
2453
|
+
console.error('Failed to inject CSS:', chrome.runtime.lastError);
|
|
2454
|
+
return;
|
|
2455
|
+
}
|
|
2456
|
+
// Try again after injection
|
|
2457
|
+
setTimeout(() => {
|
|
2458
|
+
chrome.runtime.sendMessage({
|
|
2459
|
+
action: 'startScreenshotRecording',
|
|
2460
|
+
tabId: tab.id,
|
|
2461
|
+
settings: screenshotModeSettings
|
|
2462
|
+
}, (retryResponse) => {
|
|
2463
|
+
if (retryResponse && retryResponse.success) {
|
|
2464
|
+
isScreenshotRecording = true;
|
|
2465
|
+
// Save state and start time to storage
|
|
2466
|
+
const startTime = Date.now();
|
|
2467
|
+
chrome.storage.local.set({
|
|
2468
|
+
screenshotRecording: true,
|
|
2469
|
+
screenshotStartTime: startTime,
|
|
2470
|
+
screenshotSessionId: retryResponse.sessionId
|
|
2471
|
+
});
|
|
2472
|
+
screenshotStartTime = startTime;
|
|
2473
|
+
// Re-enable button and update UI for recording state
|
|
2474
|
+
screenshotRecordBtn.disabled = false;
|
|
2475
|
+
updateScreenshotRecordingUI();
|
|
2476
|
+
if (workflowRecordingsList) workflowRecordingsList.style.display = 'none';
|
|
2477
|
+
} else {
|
|
2478
|
+
// Retry failed
|
|
2479
|
+
console.error('[Screenshot] Retry failed:', retryResponse);
|
|
2480
|
+
showToast('Failed to start screenshot recording after content script injection.', 3000, 'error');
|
|
2481
|
+
screenshotRecordBtn.disabled = false;
|
|
2482
|
+
screenshotRecordBtn.textContent = originalButtonText;
|
|
2483
|
+
}
|
|
2484
|
+
});
|
|
2485
|
+
}, 100);
|
|
2486
|
+
});
|
|
2487
|
+
});
|
|
2488
|
+
} else if (response && response.success) {
|
|
2489
|
+
isScreenshotRecording = true;
|
|
2490
|
+
// Save state and start time to storage
|
|
2491
|
+
const startTime = Date.now();
|
|
2492
|
+
chrome.storage.local.set({
|
|
2493
|
+
screenshotRecording: true,
|
|
2494
|
+
screenshotStartTime: startTime,
|
|
2495
|
+
screenshotSessionId: response.sessionId
|
|
2496
|
+
});
|
|
2497
|
+
screenshotStartTime = startTime;
|
|
2498
|
+
// Re-enable button and update UI for recording state
|
|
2499
|
+
screenshotRecordBtn.disabled = false;
|
|
2500
|
+
updateScreenshotRecordingUI();
|
|
2501
|
+
// Hide any previous results
|
|
2502
|
+
if (workflowRecordingsList) workflowRecordingsList.style.display = 'none';
|
|
2503
|
+
} else {
|
|
2504
|
+
// Response received but not successful
|
|
2505
|
+
console.error('[Screenshot] Failed to start recording:', response);
|
|
2506
|
+
showToast('Failed to start screenshot recording. Please try again.', 3000, 'error');
|
|
2507
|
+
screenshotRecordBtn.disabled = false;
|
|
2508
|
+
screenshotRecordBtn.textContent = originalButtonText;
|
|
2509
|
+
}
|
|
2510
|
+
});
|
|
2511
|
+
} catch (error) {
|
|
2512
|
+
console.error('[Screenshot] Error starting screenshot recording:', error);
|
|
2513
|
+
showToast('Error starting screenshot recording. Please try again.', 3000, 'error');
|
|
2514
|
+
// Restore button state on error
|
|
2515
|
+
screenshotRecordBtn.disabled = false;
|
|
2516
|
+
screenshotRecordBtn.textContent = originalButtonText;
|
|
2517
|
+
}
|
|
2518
|
+
// Note: Button state is managed by updateScreenshotRecordingUI() on success
|
|
2519
|
+
// or restored explicitly in catch/early returns on failure
|
|
2520
|
+
} else {
|
|
2521
|
+
// Stop screenshot recording
|
|
2522
|
+
try {
|
|
2523
|
+
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
2524
|
+
|
|
2525
|
+
if (!tab || !tab.id) {
|
|
2526
|
+
console.error('No active tab found');
|
|
2527
|
+
return;
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
// Send message to background script to stop screenshot recording
|
|
2531
|
+
chrome.runtime.sendMessage({
|
|
2532
|
+
action: 'stopScreenshotRecording',
|
|
2533
|
+
tabId: tab.id
|
|
2534
|
+
}, async (response) => {
|
|
2535
|
+
if (chrome.runtime.lastError) {
|
|
2536
|
+
console.error('Error stopping screenshot recording:', chrome.runtime.lastError);
|
|
2537
|
+
// Still stop recording in UI even if there was an error
|
|
2538
|
+
isScreenshotRecording = false;
|
|
2539
|
+
chrome.storage.local.set({
|
|
2540
|
+
screenshotRecording: false,
|
|
2541
|
+
screenshotStartTime: null,
|
|
2542
|
+
screenshotSessionId: null
|
|
2543
|
+
});
|
|
2544
|
+
updateScreenshotRecordingUI();
|
|
2545
|
+
alert('Error stopping screenshot recording. The recording may have been lost.');
|
|
2546
|
+
return;
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
if (response && response.success) {
|
|
2550
|
+
isScreenshotRecording = false;
|
|
2551
|
+
// Clear state from storage
|
|
2552
|
+
chrome.storage.local.set({
|
|
2553
|
+
screenshotRecording: false,
|
|
2554
|
+
screenshotStartTime: null,
|
|
2555
|
+
screenshotSessionId: null
|
|
2556
|
+
});
|
|
2557
|
+
updateScreenshotRecordingUI();
|
|
2558
|
+
|
|
2559
|
+
// Show loading overlay over workflow recording section
|
|
2560
|
+
showSectionLoading('workflow-recording-section', 'Saving screenshot recording...');
|
|
2561
|
+
|
|
2562
|
+
try {
|
|
2563
|
+
// Single source of truth - load from server
|
|
2564
|
+
await loadWorkflowRecordings(true);
|
|
2565
|
+
|
|
2566
|
+
// Show success toast
|
|
2567
|
+
const screenshotCount = response.screenshotCount || 0;
|
|
2568
|
+
showToast(`Screenshot recording saved! ${screenshotCount} screenshots captured`, 3000, 'success');
|
|
2569
|
+
} catch (error) {
|
|
2570
|
+
console.error('Error loading workflow recordings:', error);
|
|
2571
|
+
showToast('Error loading recordings. Please refresh.', 3000, 'error');
|
|
2572
|
+
} finally {
|
|
2573
|
+
// Always hide loading overlay
|
|
2574
|
+
hideSectionLoading('workflow-recording-section');
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
});
|
|
2578
|
+
} catch (error) {
|
|
2579
|
+
console.error('Error stopping screenshot recording:', error);
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
});
|
|
2583
|
+
}
|
|
2584
|
+
|
|
1769
2585
|
// Setup save restore point button
|
|
1770
2586
|
const saveRestorePointBtn = document.getElementById('saveRestorePointBtn');
|
|
1771
2587
|
const restorePointsList = document.getElementById('restorePointsList');
|
|
@@ -2138,11 +2954,13 @@ async function loadWorkflowRecordings(autoShow = false) {
|
|
|
2138
2954
|
const ports = CONFIG_PORTS;
|
|
2139
2955
|
for (const port of ports) {
|
|
2140
2956
|
try {
|
|
2141
|
-
|
|
2957
|
+
// Add filter parameter based on workflowTabFilter
|
|
2958
|
+
const filterParam = workflowTabFilter !== 'all' ? `?mode=${workflowTabFilter}` : '';
|
|
2959
|
+
const response = await fetch(`http://localhost:${port}/chromedebug/workflow-recordings${filterParam}`);
|
|
2142
2960
|
if (response.ok) {
|
|
2143
2961
|
const data = await response.json();
|
|
2144
2962
|
// console.log('Loaded workflow recordings from port', port, ':', data);
|
|
2145
|
-
|
|
2963
|
+
|
|
2146
2964
|
// Handle both data.recordings and direct array response
|
|
2147
2965
|
const recordings = data.recordings || data;
|
|
2148
2966
|
|
|
@@ -2152,16 +2970,22 @@ async function loadWorkflowRecordings(autoShow = false) {
|
|
|
2152
2970
|
recordings.forEach(recording => {
|
|
2153
2971
|
const workflowItem = document.createElement('div');
|
|
2154
2972
|
workflowItem.className = 'recording-item';
|
|
2155
|
-
|
|
2973
|
+
|
|
2156
2974
|
const displayName = recording.name || `Workflow ${recording.session_id}`;
|
|
2157
2975
|
const date = new Date(recording.timestamp);
|
|
2158
2976
|
const formattedDate = date.toLocaleString();
|
|
2159
|
-
|
|
2977
|
+
|
|
2978
|
+
// Determine recording type badge
|
|
2979
|
+
const recordingMode = recording.recording_mode || 'workflow';
|
|
2980
|
+
const typeBadge = recordingMode === 'screenshot'
|
|
2981
|
+
? '<span style="background: #FF9800; color: white; font-size: 8px; padding: 1px 4px; border-radius: 3px; margin-left: 4px;">SCREENSHOT</span>'
|
|
2982
|
+
: '<span style="background: #9c27b0; color: white; font-size: 8px; padding: 1px 4px; border-radius: 3px; margin-left: 4px;">WORKFLOW</span>';
|
|
2983
|
+
|
|
2160
2984
|
workflowItem.innerHTML = `
|
|
2161
2985
|
<div class="recording-info">
|
|
2162
|
-
<div style="font-weight: bold; margin-bottom: 4px;">${displayName}</div>
|
|
2986
|
+
<div style="font-weight: bold; margin-bottom: 4px;">${displayName}${typeBadge}</div>
|
|
2163
2987
|
<div style="color: #666; font-size: 11px;">
|
|
2164
|
-
${recording.total_actions} actions • ${formattedDate}
|
|
2988
|
+
${recording.total_actions} ${recordingMode === 'screenshot' ? 'screenshots' : 'actions'} • ${formattedDate}
|
|
2165
2989
|
</div>
|
|
2166
2990
|
${recording.screenshot_settings ? '<div style="color: #2196F3; font-size: 10px;">📸 Screenshots included</div>' : ''}
|
|
2167
2991
|
</div>
|
|
@@ -2249,10 +3073,17 @@ async function loadWorkflowRecordings(autoShow = false) {
|
|
|
2249
3073
|
workflowRecordingsContainer.appendChild(workflowItem);
|
|
2250
3074
|
});
|
|
2251
3075
|
} else {
|
|
2252
|
-
// Show "No recordings found" message
|
|
3076
|
+
// Show "No recordings found" message with context about current filter
|
|
2253
3077
|
const noRecordingsMsg = document.createElement('div');
|
|
2254
3078
|
noRecordingsMsg.style.cssText = 'color: #666; font-size: 13px; text-align: center; padding: 20px;';
|
|
2255
|
-
|
|
3079
|
+
// Show filter-specific message when a category filter is selected
|
|
3080
|
+
if (workflowTabFilter === 'workflow') {
|
|
3081
|
+
noRecordingsMsg.textContent = 'No workflow recordings in this category';
|
|
3082
|
+
} else if (workflowTabFilter === 'screenshot') {
|
|
3083
|
+
noRecordingsMsg.textContent = 'No screenshot recordings in this category';
|
|
3084
|
+
} else {
|
|
3085
|
+
noRecordingsMsg.textContent = 'No workflow recordings found';
|
|
3086
|
+
}
|
|
2256
3087
|
workflowRecordingsContainer.appendChild(noRecordingsMsg);
|
|
2257
3088
|
}
|
|
2258
3089
|
break; // Found working server
|
|
@@ -2271,7 +3102,9 @@ async function loadWorkflowRecordings(autoShow = false) {
|
|
|
2271
3102
|
}
|
|
2272
3103
|
|
|
2273
3104
|
// Handle visibility based on recordings and user preference
|
|
2274
|
-
|
|
3105
|
+
// CRITICAL FIX: When a tab filter is selected (not 'all'), keep the section visible
|
|
3106
|
+
// so users can switch back to other tabs. Only hide if 'all' filter returns no results.
|
|
3107
|
+
if (!hasRecordings && workflowTabFilter === 'all') {
|
|
2275
3108
|
workflowRecordingsList.style.display = 'none';
|
|
2276
3109
|
} else {
|
|
2277
3110
|
// Get user's saved preference
|
|
@@ -2290,6 +3123,12 @@ async function loadWorkflowRecordings(autoShow = false) {
|
|
|
2290
3123
|
}
|
|
2291
3124
|
}
|
|
2292
3125
|
}
|
|
3126
|
+
|
|
3127
|
+
// Show/hide Delete All button based on recording count
|
|
3128
|
+
const deleteAllWorkflowsBtn = document.getElementById('deleteAllWorkflowsBtn');
|
|
3129
|
+
if (deleteAllWorkflowsBtn) {
|
|
3130
|
+
deleteAllWorkflowsBtn.style.display = hasRecordings ? 'block' : 'none';
|
|
3131
|
+
}
|
|
2293
3132
|
}
|
|
2294
3133
|
|
|
2295
3134
|
// Function to play a workflow
|
|
@@ -2437,6 +3276,190 @@ async function deleteWorkflowRecording(recordingId) {
|
|
|
2437
3276
|
}
|
|
2438
3277
|
}
|
|
2439
3278
|
|
|
3279
|
+
// Function to delete all workflow recordings
|
|
3280
|
+
async function deleteAllWorkflowRecordings() {
|
|
3281
|
+
try {
|
|
3282
|
+
// Get current count for confirmation
|
|
3283
|
+
const workflowRecordingsContainer = document.getElementById('workflowRecordingsContainer');
|
|
3284
|
+
const recordingItems = workflowRecordingsContainer?.querySelectorAll('.recording-item') || [];
|
|
3285
|
+
const recordingCount = recordingItems.length;
|
|
3286
|
+
|
|
3287
|
+
if (recordingCount === 0) {
|
|
3288
|
+
alert('No workflow recordings to delete');
|
|
3289
|
+
return;
|
|
3290
|
+
}
|
|
3291
|
+
|
|
3292
|
+
// Check if workflow recording is in progress
|
|
3293
|
+
const workflowRecordBtn = document.getElementById('workflowRecordBtn');
|
|
3294
|
+
if (workflowRecordBtn && workflowRecordBtn.textContent.includes('Stop')) {
|
|
3295
|
+
alert('Cannot delete all while workflow recording is in progress. Stop recording first.');
|
|
3296
|
+
return;
|
|
3297
|
+
}
|
|
3298
|
+
|
|
3299
|
+
// First confirmation dialog
|
|
3300
|
+
const confirmMessage = `Are you sure you want to delete ALL ${recordingCount} workflow recording(s)?\n\nThis will also delete:\n- All associated restore points\n- All recorded actions and logs\n\nThis action CANNOT be undone!`;
|
|
3301
|
+
if (!confirm(confirmMessage)) {
|
|
3302
|
+
return;
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3305
|
+
// Second confirmation for safety
|
|
3306
|
+
const finalConfirm = confirm('Final confirmation: Delete all workflow recordings permanently?');
|
|
3307
|
+
if (!finalConfirm) {
|
|
3308
|
+
return;
|
|
3309
|
+
}
|
|
3310
|
+
|
|
3311
|
+
// Disable button during deletion
|
|
3312
|
+
const deleteBtn = document.getElementById('deleteAllWorkflowsBtn');
|
|
3313
|
+
const originalText = deleteBtn.textContent;
|
|
3314
|
+
deleteBtn.disabled = true;
|
|
3315
|
+
deleteBtn.textContent = 'Deleting...';
|
|
3316
|
+
|
|
3317
|
+
// Delete from server
|
|
3318
|
+
const ports = CONFIG_PORTS;
|
|
3319
|
+
let deleted = false;
|
|
3320
|
+
|
|
3321
|
+
for (const port of ports) {
|
|
3322
|
+
try {
|
|
3323
|
+
const response = await fetch(`http://localhost:${port}/chromedebug/workflow-recordings/all`, {
|
|
3324
|
+
method: 'DELETE'
|
|
3325
|
+
});
|
|
3326
|
+
|
|
3327
|
+
if (response.ok) {
|
|
3328
|
+
const result = await response.json();
|
|
3329
|
+
deleted = true;
|
|
3330
|
+
alert(`Successfully deleted ${result.deletedCount} workflow recording(s)`);
|
|
3331
|
+
|
|
3332
|
+
// Reload the list
|
|
3333
|
+
loadWorkflowRecordings();
|
|
3334
|
+
break; // Found working server
|
|
3335
|
+
}
|
|
3336
|
+
} catch (error) {
|
|
3337
|
+
console.error(`Failed to delete all workflows from port ${port}:`, error);
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
|
|
3341
|
+
if (!deleted) {
|
|
3342
|
+
alert('Failed to connect to server. Please ensure Chrome Debug server is running.');
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3345
|
+
// Re-enable button
|
|
3346
|
+
deleteBtn.disabled = false;
|
|
3347
|
+
deleteBtn.textContent = originalText;
|
|
3348
|
+
|
|
3349
|
+
} catch (error) {
|
|
3350
|
+
console.error('Error deleting all workflow recordings:', error);
|
|
3351
|
+
alert('An error occurred while deleting recordings');
|
|
3352
|
+
// Re-enable button on error
|
|
3353
|
+
const deleteBtn = document.getElementById('deleteAllWorkflowsBtn');
|
|
3354
|
+
if (deleteBtn) {
|
|
3355
|
+
deleteBtn.disabled = false;
|
|
3356
|
+
deleteBtn.textContent = 'Delete All';
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3361
|
+
// Function to delete all screen recordings
|
|
3362
|
+
async function deleteAllScreenRecordings() {
|
|
3363
|
+
try {
|
|
3364
|
+
// Count recordings from the list
|
|
3365
|
+
const recordingsContainer = document.getElementById('recordingsContainer');
|
|
3366
|
+
const recordingItems = recordingsContainer?.querySelectorAll('.recording-item') || [];
|
|
3367
|
+
const recordingCount = recordingItems.length;
|
|
3368
|
+
|
|
3369
|
+
if (recordingCount === 0) {
|
|
3370
|
+
alert('No screen recordings to delete');
|
|
3371
|
+
return;
|
|
3372
|
+
}
|
|
3373
|
+
|
|
3374
|
+
// Check if screen recording is in progress
|
|
3375
|
+
const recordBtn = document.getElementById('recordBtn');
|
|
3376
|
+
if (recordBtn && recordBtn.textContent.includes('Stop')) {
|
|
3377
|
+
alert('Cannot delete all while screen recording is in progress. Stop recording first.');
|
|
3378
|
+
return;
|
|
3379
|
+
}
|
|
3380
|
+
|
|
3381
|
+
// First confirmation dialog
|
|
3382
|
+
const confirmMessage = `Are you sure you want to delete ALL ${recordingCount} screen recording(s)?\n\nThis includes both server and browser-only recordings.\n\nThis action CANNOT be undone!`;
|
|
3383
|
+
if (!confirm(confirmMessage)) {
|
|
3384
|
+
return;
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
// Second confirmation for safety
|
|
3388
|
+
const finalConfirm = confirm('Final confirmation: Delete all screen recordings permanently?');
|
|
3389
|
+
if (!finalConfirm) {
|
|
3390
|
+
return;
|
|
3391
|
+
}
|
|
3392
|
+
|
|
3393
|
+
// Disable button during deletion
|
|
3394
|
+
const deleteBtn = document.getElementById('deleteAllScreenRecordingsBtn');
|
|
3395
|
+
const originalText = deleteBtn.textContent;
|
|
3396
|
+
deleteBtn.disabled = true;
|
|
3397
|
+
deleteBtn.textContent = 'Deleting...';
|
|
3398
|
+
|
|
3399
|
+
let deletedCount = 0;
|
|
3400
|
+
|
|
3401
|
+
// Delete browser-only recordings first
|
|
3402
|
+
try {
|
|
3403
|
+
const result = await chrome.runtime.sendMessage({
|
|
3404
|
+
action: 'deleteAllBrowserRecordings'
|
|
3405
|
+
});
|
|
3406
|
+
|
|
3407
|
+
if (result && result.success) {
|
|
3408
|
+
deletedCount += result.deletedCount;
|
|
3409
|
+
}
|
|
3410
|
+
} catch (error) {
|
|
3411
|
+
console.error('Failed to delete browser-only recordings:', error);
|
|
3412
|
+
}
|
|
3413
|
+
|
|
3414
|
+
// Delete server recordings
|
|
3415
|
+
const ports = CONFIG_PORTS;
|
|
3416
|
+
for (const port of ports) {
|
|
3417
|
+
try {
|
|
3418
|
+
const response = await fetch(`http://localhost:${port}/chromedebug/recordings/all`, {
|
|
3419
|
+
method: 'DELETE'
|
|
3420
|
+
});
|
|
3421
|
+
|
|
3422
|
+
if (response.ok) {
|
|
3423
|
+
const result = await response.json();
|
|
3424
|
+
deletedCount += result.deletedCount;
|
|
3425
|
+
break; // Found working server
|
|
3426
|
+
}
|
|
3427
|
+
} catch (error) {
|
|
3428
|
+
console.error(`Failed to delete all recordings from port ${port}:`, error);
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3431
|
+
|
|
3432
|
+
if (deletedCount > 0) {
|
|
3433
|
+
alert(`Successfully deleted ${deletedCount} screen recording(s)`);
|
|
3434
|
+
// Reload the list
|
|
3435
|
+
loadScreenRecordings();
|
|
3436
|
+
} else {
|
|
3437
|
+
alert('No recordings were deleted. Please check server connection.');
|
|
3438
|
+
}
|
|
3439
|
+
|
|
3440
|
+
// Re-enable button
|
|
3441
|
+
deleteBtn.disabled = false;
|
|
3442
|
+
deleteBtn.textContent = originalText;
|
|
3443
|
+
|
|
3444
|
+
} catch (error) {
|
|
3445
|
+
console.error('Error deleting all screen recordings:', error);
|
|
3446
|
+
alert('An error occurred while deleting recordings');
|
|
3447
|
+
// Re-enable button on error
|
|
3448
|
+
const deleteBtn = document.getElementById('deleteAllScreenRecordingsBtn');
|
|
3449
|
+
if (deleteBtn) {
|
|
3450
|
+
deleteBtn.disabled = false;
|
|
3451
|
+
deleteBtn.textContent = 'Delete All';
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
3454
|
+
}
|
|
3455
|
+
|
|
3456
|
+
// Helper function to normalize isVideoMode to boolean
|
|
3457
|
+
function normalizeIsVideoMode(value) {
|
|
3458
|
+
if (value === true || value === 1 || value === 'true' || value === '1') return true;
|
|
3459
|
+
if (value === false || value === 0 || value === 'false' || value === '0' || value === null || value === undefined) return false;
|
|
3460
|
+
return Boolean(value);
|
|
3461
|
+
}
|
|
3462
|
+
|
|
2440
3463
|
// Function to load and display screen recordings
|
|
2441
3464
|
async function loadScreenRecordings(autoShow = false) {
|
|
2442
3465
|
const recordingsList = document.getElementById('recordingsList');
|
|
@@ -2466,6 +3489,11 @@ async function loadScreenRecordings(autoShow = false) {
|
|
|
2466
3489
|
|
|
2467
3490
|
// Display each browser-only recording
|
|
2468
3491
|
browserRecordings.forEach(recording => {
|
|
3492
|
+
// Apply tab filter - normalize isVideoMode to handle various types
|
|
3493
|
+
const isVideo = normalizeIsVideoMode(recording.isVideoMode);
|
|
3494
|
+
if (recordingTabFilter === 'video' && !isVideo) return;
|
|
3495
|
+
if (recordingTabFilter === 'screen' && isVideo) return;
|
|
3496
|
+
|
|
2469
3497
|
const recordingItem = document.createElement('div');
|
|
2470
3498
|
recordingItem.className = 'recording-item';
|
|
2471
3499
|
|
|
@@ -2473,10 +3501,17 @@ async function loadScreenRecordings(autoShow = false) {
|
|
|
2473
3501
|
const formattedDate = date.toLocaleString();
|
|
2474
3502
|
const displayName = recording.title || `Recording ${recording.sessionId}`;
|
|
2475
3503
|
|
|
3504
|
+
// Determine recording type badge for browser-only recordings
|
|
3505
|
+
const isVideoBrowser = normalizeIsVideoMode(recording.isVideoMode);
|
|
3506
|
+
const typeBadgeBrowser = isVideoBrowser
|
|
3507
|
+
? '<span style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; font-size: 8px; padding: 1px 4px; border-radius: 3px; margin-left: 4px;">VIDEO</span>'
|
|
3508
|
+
: '<span style="background: #4CAF50; color: white; font-size: 8px; padding: 1px 4px; border-radius: 3px; margin-left: 4px;">SCREEN</span>';
|
|
3509
|
+
|
|
2476
3510
|
recordingItem.innerHTML = `
|
|
2477
3511
|
<div class="recording-info">
|
|
2478
3512
|
<div class="recording-id" title="${recording.sessionId}">
|
|
2479
3513
|
<span class="recording-id-text">🌐 ${displayName}</span>
|
|
3514
|
+
${typeBadgeBrowser}
|
|
2480
3515
|
<span style="font-size: 10px; background: #ff9800; color: white; padding: 2px 6px; border-radius: 3px; margin-left: 4px;">BROWSER-ONLY</span>
|
|
2481
3516
|
<span style="color: #666; font-size: 11px; margin-left: 8px;">${recording.frameCount || 0} frames • ${formattedDate}</span>
|
|
2482
3517
|
</div>
|
|
@@ -2589,8 +3624,18 @@ async function loadScreenRecordings(autoShow = false) {
|
|
|
2589
3624
|
hasRecordings = true;
|
|
2590
3625
|
recordingsList.style.display = 'block';
|
|
2591
3626
|
|
|
3627
|
+
// Debug logging
|
|
3628
|
+
console.log('[Popup] Loaded recordings from server:', data.length, 'total');
|
|
3629
|
+
console.log('[Popup] Current tab filter:', recordingTabFilter);
|
|
3630
|
+
console.log('[Popup] Sample recording isVideoMode values:', data.slice(0, 3).map(r => ({ id: r.sessionId, isVideoMode: r.isVideoMode, type: typeof r.isVideoMode })));
|
|
3631
|
+
|
|
2592
3632
|
// Display each screen recording
|
|
2593
3633
|
data.forEach(recording => {
|
|
3634
|
+
// Apply tab filter - normalize isVideoMode to handle various types
|
|
3635
|
+
const isVideo = normalizeIsVideoMode(recording.isVideoMode);
|
|
3636
|
+
if (recordingTabFilter === 'video' && !isVideo) return;
|
|
3637
|
+
if (recordingTabFilter === 'screen' && isVideo) return;
|
|
3638
|
+
|
|
2594
3639
|
const recordingItem = document.createElement('div');
|
|
2595
3640
|
recordingItem.className = 'recording-item';
|
|
2596
3641
|
|
|
@@ -2603,10 +3648,16 @@ async function loadScreenRecordings(autoShow = false) {
|
|
|
2603
3648
|
const displayName = recording.name || `Recording ${recording.sessionId}`;
|
|
2604
3649
|
// console.log('Display name:', displayName);
|
|
2605
3650
|
|
|
3651
|
+
// Determine recording type badge (already computed above for filter)
|
|
3652
|
+
const typeBadge = isVideo
|
|
3653
|
+
? '<span style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; font-size: 8px; padding: 1px 4px; border-radius: 3px; margin-left: 4px;">VIDEO</span>'
|
|
3654
|
+
: '<span style="background: #4CAF50; color: white; font-size: 8px; padding: 1px 4px; border-radius: 3px; margin-left: 4px;">SCREEN</span>';
|
|
3655
|
+
|
|
2606
3656
|
recordingItem.innerHTML = `
|
|
2607
3657
|
<div class="recording-info">
|
|
2608
3658
|
<div class="recording-id" title="${recording.sessionId}">
|
|
2609
3659
|
<span class="recording-id-text">🎥 ${displayName}</span>
|
|
3660
|
+
${typeBadge}
|
|
2610
3661
|
<span style="color: #666; font-size: 11px; margin-left: 8px;">${recording.totalFrames} frames • ${formattedDate}</span>
|
|
2611
3662
|
</div>
|
|
2612
3663
|
</div>
|
|
@@ -2708,6 +3759,11 @@ async function loadScreenRecordings(autoShow = false) {
|
|
|
2708
3759
|
}
|
|
2709
3760
|
}
|
|
2710
3761
|
|
|
3762
|
+
// Check if all recordings were filtered out
|
|
3763
|
+
if (hasRecordings && recordingsContainer.children.length === 0) {
|
|
3764
|
+
recordingsContainer.innerHTML = `<div style="text-align: center; color: #999; padding: 20px; font-size: 11px;">No ${recordingTabFilter === 'all' ? '' : recordingTabFilter + ' '}recordings found</div>`;
|
|
3765
|
+
}
|
|
3766
|
+
|
|
2711
3767
|
// Handle visibility based on recordings and user preference
|
|
2712
3768
|
if (!hasRecordings) {
|
|
2713
3769
|
recordingsList.style.display = 'none';
|
|
@@ -2728,6 +3784,12 @@ async function loadScreenRecordings(autoShow = false) {
|
|
|
2728
3784
|
}
|
|
2729
3785
|
}
|
|
2730
3786
|
}
|
|
3787
|
+
|
|
3788
|
+
// Show/hide Delete All button based on recording count
|
|
3789
|
+
const deleteAllScreenRecordingsBtn = document.getElementById('deleteAllScreenRecordingsBtn');
|
|
3790
|
+
if (deleteAllScreenRecordingsBtn) {
|
|
3791
|
+
deleteAllScreenRecordingsBtn.style.display = hasRecordings ? 'block' : 'none';
|
|
3792
|
+
}
|
|
2731
3793
|
}
|
|
2732
3794
|
|
|
2733
3795
|
/*
|
|
@@ -2886,11 +3948,17 @@ async function loadSnapshots() {
|
|
|
2886
3948
|
// Listen for storage changes
|
|
2887
3949
|
chrome.storage.onChanged.addListener((changes, namespace) => {
|
|
2888
3950
|
if (namespace === 'local') {
|
|
3951
|
+
// Update isVideoModeActive from storage if it changed
|
|
3952
|
+
if (changes.isVideoModeActive) {
|
|
3953
|
+
isVideoModeActive = changes.isVideoModeActive.newValue === true;
|
|
3954
|
+
console.log('[Storage Listener] isVideoModeActive changed to:', isVideoModeActive);
|
|
3955
|
+
}
|
|
2889
3956
|
if (changes.recordingActive && !isStoppingRecording) {
|
|
2890
3957
|
isRecording = changes.recordingActive.newValue === true;
|
|
2891
3958
|
if (changes.recordingStartTime) {
|
|
2892
3959
|
recordingStartTime = changes.recordingStartTime.newValue;
|
|
2893
3960
|
}
|
|
3961
|
+
console.log('[Storage Listener] recordingActive changed, isRecording:', isRecording, 'isVideoModeActive:', isVideoModeActive);
|
|
2894
3962
|
updateRecordingUI();
|
|
2895
3963
|
}
|
|
2896
3964
|
if (changes.workflowRecording) {
|
|
@@ -2920,8 +3988,8 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
|
2920
3988
|
if (recordingStatus) {
|
|
2921
3989
|
recordingStatus.innerHTML = `<strong style="color: #4CAF50;">Recording saved!</strong>`;
|
|
2922
3990
|
}
|
|
2923
|
-
// Add to recordings list
|
|
2924
|
-
addRecording(request.recordingId);
|
|
3991
|
+
// Add to recordings list - use request.isVideoMode if available, otherwise false
|
|
3992
|
+
addRecording(request.recordingId, false, false, null, null, null, request.isVideoMode || false);
|
|
2925
3993
|
// Clear status after 3 seconds
|
|
2926
3994
|
setTimeout(() => {
|
|
2927
3995
|
if (recordingStatus) recordingStatus.textContent = '';
|