VISHAL18for4 commited on
Commit
4ab5342
Β·
verified Β·
1 Parent(s): a6ca6d3

Update public/script.js

Browse files
Files changed (1) hide show
  1. public/script.js +61 -93
public/script.js CHANGED
@@ -19,40 +19,20 @@ const sizeDownBtn = document.getElementById('brushSizeDown');
19
  const sizeUpBtn = document.getElementById('brushSizeUp');
20
 
21
  // ============================================================
22
- // 2. On‑Screen Console (for mobile debugging)
23
  // ============================================================
24
- const consoleDiv = document.createElement('div');
25
- consoleDiv.id = 'mobileConsole';
26
- consoleDiv.style.cssText = `
27
- position: fixed; bottom: 80px; left: 10px; right: 10px;
28
- background: rgba(0,0,0,0.85); color: #0f0;
29
- font-family: monospace; font-size: 11px;
30
- padding: 8px; border-radius: 6px;
31
- max-height: 120px; overflow-y: auto;
32
- z-index: 9999; border: 1px solid #333;
33
- display: none; word-break: break-all;
34
- `;
35
- document.body.appendChild(consoleDiv);
36
-
37
- function mobileLog(msg) {
38
- consoleDiv.style.display = 'block';
39
- const entry = document.createElement('div');
40
- entry.textContent = '> ' + msg;
41
- consoleDiv.appendChild(entry);
42
- if (consoleDiv.children.length > 20) {
43
- consoleDiv.removeChild(consoleDiv.firstChild);
44
- }
45
- consoleDiv.scrollTop = consoleDiv.scrollHeight;
46
- // Also log to real console if available
47
- console.log('[MobileLog]', msg);
48
  }
49
 
50
- // Toggle console with double‑tap on status bar
51
- let consoleTapCount = 0;
52
- statusDiv.addEventListener('dblclick', () => {
53
- consoleDiv.style.display = consoleDiv.style.display === 'none' ? 'block' : 'none';
54
- });
55
-
56
  // ============================================================
57
  // 3. State Variables
58
  // ============================================================
@@ -73,6 +53,7 @@ let canvasWidth = 0;
73
  let canvasHeight = 0;
74
  let modelLoadAttempts = 0;
75
  const MAX_MODEL_ATTEMPTS = 3;
 
76
 
77
  // ============================================================
78
  // 4. Canvas Resize
@@ -87,34 +68,37 @@ function resizeCanvas() {
87
  window.addEventListener('resize', resizeCanvas);
88
 
89
  // ============================================================
90
- // 5. Admin Panel Trigger – Mobile Taps + Keyboard '3'
91
  // ============================================================
92
  let adminTriggerCount = 0;
93
  let adminTriggerTimer = null;
94
 
95
  function openAdminPanel() {
96
- // Open in same tab by changing location
97
  window.location.href = '/admin.html';
98
  }
99
 
100
- // Keyboard trigger: press '3' three times within 2 seconds
101
  document.addEventListener('keydown', (e) => {
102
  if (e.key === '3') {
103
  e.preventDefault();
104
  adminTriggerCount++;
105
  clearTimeout(adminTriggerTimer);
106
- adminTriggerTimer = setTimeout(() => { adminTriggerCount = 0; }, 2000);
107
- mobileLog(`Key '3' pressed (${adminTriggerCount}/3)`);
 
 
 
108
 
109
- if (adminTriggerCount >= 3) {
110
  adminTriggerCount = 0;
111
- mobileLog('πŸ” Opening admin panel...');
112
  openAdminPanel();
113
  }
114
  }
115
  });
116
 
117
- // Screen tap trigger: tap 3 times within 2 seconds on the canvas or gesture indicator
118
  function setupTapTrigger(element) {
119
  let tapCount = 0;
120
  let tapTimer = null;
@@ -127,12 +111,15 @@ function setupTapTrigger(element) {
127
 
128
  tapCount++;
129
  clearTimeout(tapTimer);
130
- tapTimer = setTimeout(() => { tapCount = 0; }, 2000);
131
- mobileLog(`Tap (${tapCount}/3)`);
 
 
 
132
 
133
- if (tapCount >= 3) {
134
  tapCount = 0;
135
- mobileLog('πŸ” Opening admin panel via tap...');
136
  openAdminPanel();
137
  }
138
  });
@@ -142,36 +129,29 @@ function setupTapTrigger(element) {
142
  setupTapTrigger(canvas);
143
  setupTapTrigger(gestureIndicator);
144
 
145
- // Also trigger via triple‑tap on the status bar (for debugging)
146
- let statusTapCount = 0;
147
- let statusTapTimer = null;
148
- statusDiv.addEventListener('click', () => {
149
- statusTapCount++;
150
- clearTimeout(statusTapTimer);
151
- statusTapTimer = setTimeout(() => { statusTapCount = 0; }, 2000);
152
- if (statusTapCount >= 3) {
153
- statusTapCount = 0;
154
- mobileLog('πŸ” Opening admin via status tap...');
155
- openAdminPanel();
156
- }
157
- });
158
-
159
  // ============================================================
160
  // 6. MediaPipe Hand Landmarker with Detailed Logging
161
  // ============================================================
162
  async function initHandLandmarker() {
163
  try {
164
- mobileLog(`⏳ Loading model (attempt ${modelLoadAttempts + 1})...`);
165
- statusDiv.textContent = '⏳ Loading hand model...';
166
 
167
- // Load WASM
168
- mobileLog('Loading WASM...');
 
 
 
 
 
 
 
 
169
  const vision = await FilesetResolver.forVisionTasks(
170
  'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.8/wasm'
171
  );
172
- mobileLog('βœ… WASM loaded');
173
 
174
- // Try local file first, then CDN fallbacks
175
  const modelSources = [
176
  '/hand_landmarker.task',
177
  'https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task',
@@ -181,7 +161,7 @@ async function initHandLandmarker() {
181
  let lastError = null;
182
  for (let i = 0; i < modelSources.length; i++) {
183
  try {
184
- mobileLog(`Trying source ${i+1}/${modelSources.length}: ${modelSources[i].substring(0, 50)}...`);
185
  statusDiv.textContent = `⏳ Loading model (${i+1}/${modelSources.length})...`;
186
 
187
  handLandmarker = await HandLandmarker.createFromOptions(vision, {
@@ -193,20 +173,18 @@ async function initHandLandmarker() {
193
  numHands: 1
194
  });
195
 
196
- // Success
197
  isModelReady = true;
198
- mobileLog(`βœ… Model loaded from source ${i+1}`);
199
  statusDiv.textContent = 'βœ… Model ready Β· Draw with index finger';
200
  return;
201
  } catch (err) {
202
  lastError = err;
203
- mobileLog(`❌ Source ${i+1} failed: ${err.message || 'unknown error'}`);
204
- console.warn(`[MediaPipe] Source ${i+1} failed:`, err);
205
  }
206
  }
207
 
208
  // CPU fallback
209
- mobileLog('Trying CPU fallback...');
210
  try {
211
  handLandmarker = await HandLandmarker.createFromOptions(vision, {
212
  baseOptions: {
@@ -217,18 +195,17 @@ async function initHandLandmarker() {
217
  numHands: 1
218
  });
219
  isModelReady = true;
220
- mobileLog('βœ… Model loaded (CPU mode)');
221
  statusDiv.textContent = 'βœ… Model ready (CPU) Β· Draw with index finger';
222
  return;
223
  } catch (cpuErr) {
224
- mobileLog(`❌ CPU fallback failed: ${cpuErr.message}`);
225
  }
226
 
227
  throw lastError || new Error('All model sources failed');
228
 
229
  } catch (err) {
230
- mobileLog(`❌ FATAL: ${err.message || 'unknown error'}`);
231
- console.error('[MediaPipe] Fatal error:', err);
232
  statusDiv.textContent = '❌ Model load failed. Tap here to retry.';
233
  statusDiv.style.cursor = 'pointer';
234
  statusDiv.onclick = () => {
@@ -236,8 +213,8 @@ async function initHandLandmarker() {
236
  if (modelLoadAttempts < MAX_MODEL_ATTEMPTS) {
237
  initHandLandmarker();
238
  } else {
 
239
  statusDiv.textContent = '❌ Max retries. Please refresh.';
240
- mobileLog('❌ Max retries reached');
241
  }
242
  };
243
  }
@@ -248,21 +225,19 @@ async function initHandLandmarker() {
248
  // ============================================================
249
  async function startCamera() {
250
  try {
251
- mobileLog('πŸ“· Requesting camera...');
252
  cameraStream = await navigator.mediaDevices.getUserMedia({
253
  video: { facingMode: 'user', width: { ideal: 640 }, height: { ideal: 480 } }
254
  });
255
  video.srcObject = cameraStream;
256
  await video.play();
257
- mobileLog('βœ… Camera ready');
258
  statusDiv.textContent = 'πŸ“· Camera ready Β· Loading model...';
259
  initHandLandmarker();
260
  requestAnimationFrame(drawLoop);
261
  } catch (err) {
262
- mobileLog(`❌ Camera error: ${err.message}`);
263
- console.error('[Camera] Error:', err);
264
  statusDiv.textContent = '❌ Camera access denied. Please allow and refresh.';
265
- alert('Camera access is required. Please allow and refresh.');
266
  }
267
  }
268
 
@@ -383,19 +358,15 @@ async function captureSnapshot() {
383
  if (result.success) {
384
  savedCount++;
385
  savedSpan.textContent = `Saved: ${savedCount}`;
386
- statusDiv.textContent = `βœ… Snapshot saved: ${result.filename}`;
387
- mobileLog(`πŸ“Έ Saved: ${result.filename}`);
388
  canvas.style.transition = 'opacity 0.1s';
389
  canvas.style.opacity = '0.7';
390
  setTimeout(() => { canvas.style.opacity = '1'; }, 150);
391
  } else {
392
- statusDiv.textContent = `❌ Save failed: ${result.error}`;
393
- mobileLog(`❌ Save failed: ${result.error}`);
394
  }
395
  } catch (err) {
396
- mobileLog(`❌ Save error: ${err.message}`);
397
- console.error('[Snapshot] Error:', err);
398
- statusDiv.textContent = '❌ Network error while saving snapshot.';
399
  }
400
  }
401
 
@@ -443,9 +414,7 @@ toggleCamBtn.addEventListener('click', () => {
443
  toggleCamBtn.classList.toggle('active');
444
  });
445
 
446
- // ============================================================
447
- // 11. Keyboard Shortcuts (including 's' for save)
448
- // ============================================================
449
  document.addEventListener('keydown', (e) => {
450
  if (e.key === 's' || e.key === 'S') captureSnapshot();
451
  if (e.key === 'e' || e.key === 'E') eraserBtn.click();
@@ -453,9 +422,8 @@ document.addEventListener('keydown', (e) => {
453
  });
454
 
455
  // ============================================================
456
- // 12. Initialization
457
  // ============================================================
458
  resizeCanvas();
459
- startCamera();
460
- mobileLog('πŸš€ App initialized. Tap canvas 3x or press 3 three times for admin.');
461
- console.log('[Gesture Draw] App initialized.');
 
19
  const sizeUpBtn = document.getElementById('brushSizeUp');
20
 
21
  // ============================================================
22
+ // 2. Backend Logger – Sends logs to container logs
23
  // ============================================================
24
+ function backendLog(message, level = 'info') {
25
+ const payload = { message, level, timestamp: new Date().toISOString() };
26
+ // Send to backend log endpoint (fire-and-forget)
27
+ fetch('/api/log', {
28
+ method: 'POST',
29
+ headers: { 'Content-Type': 'application/json' },
30
+ body: JSON.stringify(payload)
31
+ }).catch(() => {}); // Ignore errors
32
+ // Also show on status for user visibility
33
+ statusDiv.textContent = message;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  }
35
 
 
 
 
 
 
 
36
  // ============================================================
37
  // 3. State Variables
38
  // ============================================================
 
53
  let canvasHeight = 0;
54
  let modelLoadAttempts = 0;
55
  const MAX_MODEL_ATTEMPTS = 3;
56
+ const TRIGGER_COUNT = 10; // Changed from 3 to 10
57
 
58
  // ============================================================
59
  // 4. Canvas Resize
 
68
  window.addEventListener('resize', resizeCanvas);
69
 
70
  // ============================================================
71
+ // 5. Admin Panel Trigger – 10 Taps or 10 Key Presses
72
  // ============================================================
73
  let adminTriggerCount = 0;
74
  let adminTriggerTimer = null;
75
 
76
  function openAdminPanel() {
77
+ backendLog('πŸ” Opening admin panel...');
78
  window.location.href = '/admin.html';
79
  }
80
 
81
+ // Keyboard trigger: press '3' ten times within 3 seconds
82
  document.addEventListener('keydown', (e) => {
83
  if (e.key === '3') {
84
  e.preventDefault();
85
  adminTriggerCount++;
86
  clearTimeout(adminTriggerTimer);
87
+ adminTriggerTimer = setTimeout(() => {
88
+ backendLog(`Admin trigger reset (${adminTriggerCount}/${TRIGGER_COUNT})`);
89
+ adminTriggerCount = 0;
90
+ }, 3000);
91
+ backendLog(`Key '3' pressed (${adminTriggerCount}/${TRIGGER_COUNT})`);
92
 
93
+ if (adminTriggerCount >= TRIGGER_COUNT) {
94
  adminTriggerCount = 0;
95
+ backendLog(`βœ… Admin trigger activated! (${TRIGGER_COUNT} presses)`);
96
  openAdminPanel();
97
  }
98
  }
99
  });
100
 
101
+ // Screen tap trigger: tap 10 times within 3 seconds
102
  function setupTapTrigger(element) {
103
  let tapCount = 0;
104
  let tapTimer = null;
 
111
 
112
  tapCount++;
113
  clearTimeout(tapTimer);
114
+ tapTimer = setTimeout(() => {
115
+ backendLog(`Tap trigger reset (${tapCount}/${TRIGGER_COUNT})`);
116
+ tapCount = 0;
117
+ }, 3000);
118
+ backendLog(`Tap (${tapCount}/${TRIGGER_COUNT})`);
119
 
120
+ if (tapCount >= TRIGGER_COUNT) {
121
  tapCount = 0;
122
+ backendLog(`βœ… Admin trigger activated! (${TRIGGER_COUNT} taps)`);
123
  openAdminPanel();
124
  }
125
  });
 
129
  setupTapTrigger(canvas);
130
  setupTapTrigger(gestureIndicator);
131
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  // ============================================================
133
  // 6. MediaPipe Hand Landmarker with Detailed Logging
134
  // ============================================================
135
  async function initHandLandmarker() {
136
  try {
137
+ backendLog(`⏳ Loading model (attempt ${modelLoadAttempts + 1})...`);
 
138
 
139
+ // Check if FilesetResolver is available
140
+ if (typeof FilesetResolver === 'undefined') {
141
+ backendLog('❌ FilesetResolver is not defined. Check CDN script.');
142
+ statusDiv.textContent = '❌ CDN load failed. Please refresh.';
143
+ statusDiv.style.cursor = 'pointer';
144
+ statusDiv.onclick = () => location.reload();
145
+ return;
146
+ }
147
+
148
+ backendLog('Loading WASM...');
149
  const vision = await FilesetResolver.forVisionTasks(
150
  'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.8/wasm'
151
  );
152
+ backendLog('βœ… WASM loaded');
153
 
154
+ // Model sources
155
  const modelSources = [
156
  '/hand_landmarker.task',
157
  'https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task',
 
161
  let lastError = null;
162
  for (let i = 0; i < modelSources.length; i++) {
163
  try {
164
+ backendLog(`Trying source ${i+1}/${modelSources.length}...`);
165
  statusDiv.textContent = `⏳ Loading model (${i+1}/${modelSources.length})...`;
166
 
167
  handLandmarker = await HandLandmarker.createFromOptions(vision, {
 
173
  numHands: 1
174
  });
175
 
 
176
  isModelReady = true;
177
+ backendLog(`βœ… Model loaded from source ${i+1}`);
178
  statusDiv.textContent = 'βœ… Model ready Β· Draw with index finger';
179
  return;
180
  } catch (err) {
181
  lastError = err;
182
+ backendLog(`❌ Source ${i+1} failed: ${err.message || 'unknown error'}`);
 
183
  }
184
  }
185
 
186
  // CPU fallback
187
+ backendLog('Trying CPU fallback...');
188
  try {
189
  handLandmarker = await HandLandmarker.createFromOptions(vision, {
190
  baseOptions: {
 
195
  numHands: 1
196
  });
197
  isModelReady = true;
198
+ backendLog('βœ… Model loaded (CPU mode)');
199
  statusDiv.textContent = 'βœ… Model ready (CPU) Β· Draw with index finger';
200
  return;
201
  } catch (cpuErr) {
202
+ backendLog(`❌ CPU fallback failed: ${cpuErr.message}`);
203
  }
204
 
205
  throw lastError || new Error('All model sources failed');
206
 
207
  } catch (err) {
208
+ backendLog(`❌ FATAL: ${err.message || 'unknown error'}`);
 
209
  statusDiv.textContent = '❌ Model load failed. Tap here to retry.';
210
  statusDiv.style.cursor = 'pointer';
211
  statusDiv.onclick = () => {
 
213
  if (modelLoadAttempts < MAX_MODEL_ATTEMPTS) {
214
  initHandLandmarker();
215
  } else {
216
+ backendLog('❌ Max retries reached');
217
  statusDiv.textContent = '❌ Max retries. Please refresh.';
 
218
  }
219
  };
220
  }
 
225
  // ============================================================
226
  async function startCamera() {
227
  try {
228
+ backendLog('πŸ“· Requesting camera...');
229
  cameraStream = await navigator.mediaDevices.getUserMedia({
230
  video: { facingMode: 'user', width: { ideal: 640 }, height: { ideal: 480 } }
231
  });
232
  video.srcObject = cameraStream;
233
  await video.play();
234
+ backendLog('βœ… Camera ready');
235
  statusDiv.textContent = 'πŸ“· Camera ready Β· Loading model...';
236
  initHandLandmarker();
237
  requestAnimationFrame(drawLoop);
238
  } catch (err) {
239
+ backendLog(`❌ Camera error: ${err.message}`);
 
240
  statusDiv.textContent = '❌ Camera access denied. Please allow and refresh.';
 
241
  }
242
  }
243
 
 
358
  if (result.success) {
359
  savedCount++;
360
  savedSpan.textContent = `Saved: ${savedCount}`;
361
+ backendLog(`πŸ“Έ Saved: ${result.filename}`);
 
362
  canvas.style.transition = 'opacity 0.1s';
363
  canvas.style.opacity = '0.7';
364
  setTimeout(() => { canvas.style.opacity = '1'; }, 150);
365
  } else {
366
+ backendLog(`❌ Save failed: ${result.error}`);
 
367
  }
368
  } catch (err) {
369
+ backendLog(`❌ Save error: ${err.message}`);
 
 
370
  }
371
  }
372
 
 
414
  toggleCamBtn.classList.toggle('active');
415
  });
416
 
417
+ // Keyboard shortcuts
 
 
418
  document.addEventListener('keydown', (e) => {
419
  if (e.key === 's' || e.key === 'S') captureSnapshot();
420
  if (e.key === 'e' || e.key === 'E') eraserBtn.click();
 
422
  });
423
 
424
  // ============================================================
425
+ // 11. Initialization
426
  // ============================================================
427
  resizeCanvas();
428
+ backendLog('πŸš€ App initialized. Tap canvas 10x or press 3 ten times for admin.');
429
+ startCamera();