Update assets/js/script.js
Browse files- assets/js/script.js +514 -14
assets/js/script.js
CHANGED
@@ -1,4 +1,6 @@
|
|
|
|
1 |
// Sample Email Data
|
|
|
2 |
const emails = [
|
3 |
{
|
4 |
id: 1,
|
@@ -14,9 +16,81 @@ const emails = [
|
|
14 |
snippet: "Top companies are hiring right now — don't miss your shot at these exclusive opportunities!",
|
15 |
timestamp: "12:35 PM"
|
16 |
},
|
17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
];
|
19 |
|
|
|
|
|
|
|
20 |
class DebugManager {
|
21 |
constructor() {
|
22 |
this.statusEl = document.getElementById('debugStatus');
|
@@ -27,15 +101,91 @@ class DebugManager {
|
|
27 |
this.cameraStatusEl = document.getElementById('debugCameraStatus');
|
28 |
this.lastErrorEl = document.getElementById('debugLastError');
|
29 |
this.statusIndicator = document.getElementById('statusIndicator');
|
|
|
|
|
30 |
this.toggleButton = document.getElementById('debugToggle');
|
31 |
this.debugPanel = document.getElementById('debugPanel');
|
32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
}
|
34 |
|
35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
36 |
}
|
37 |
|
|
|
|
|
|
|
38 |
class UIManager {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
renderEmails() {
|
40 |
this.emailList.innerHTML = '';
|
41 |
this.emailElements = [];
|
@@ -62,12 +212,137 @@ class UIManager {
|
|
62 |
});
|
63 |
});
|
64 |
|
|
|
65 |
this.updateEmailPositions();
|
66 |
}
|
67 |
|
68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
69 |
}
|
70 |
|
|
|
|
|
|
|
71 |
class GestureDetector {
|
72 |
constructor(uiManager, debugManager) {
|
73 |
this.uiManager = uiManager;
|
@@ -75,24 +350,121 @@ class GestureDetector {
|
|
75 |
this.selectedEmailId = null;
|
76 |
this.gestureBuffer = [];
|
77 |
this.circlePoints = [];
|
78 |
-
this.circleThreshold = 8; // Reduced from 10
|
79 |
-
this.swipeThreshold = 30; // Reduced from 50
|
80 |
-
this.gestureCooldown = 1500;
|
81 |
this.lastGestureTime = 0;
|
|
|
|
|
|
|
82 |
this.setupMediaPipe();
|
83 |
}
|
84 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
85 |
detectGesture(landmarks) {
|
86 |
try {
|
|
|
87 |
const indexTip = landmarks[8];
|
88 |
const middleTip = landmarks[12];
|
89 |
const wrist = landmarks[0];
|
90 |
|
91 |
-
//
|
|
|
92 |
const screenX = indexTip.x * window.innerWidth;
|
93 |
-
const screenY = (1 - indexTip.y) * window.innerHeight;
|
94 |
|
95 |
-
if (
|
96 |
this.checkEmailSelection(screenX, screenY);
|
97 |
} else {
|
98 |
this.uiManager.clearSelection();
|
@@ -103,14 +475,20 @@ class GestureDetector {
|
|
103 |
this.debugManager.updateCircleCount(0);
|
104 |
}
|
105 |
|
106 |
-
if
|
|
|
|
|
|
|
107 |
|
108 |
-
//
|
|
|
109 |
const palmCenterX = (wrist.x + landmarks[9].x) / 2;
|
110 |
const palmCenterY = (wrist.y + landmarks[9].y) / 2;
|
111 |
|
|
|
112 |
this.gestureBuffer.push({x: palmCenterX, y: palmCenterY});
|
113 |
|
|
|
114 |
if (this.gestureBuffer.length > 2) {
|
115 |
const prev = this.gestureBuffer[this.gestureBuffer.length - 2];
|
116 |
const current = this.gestureBuffer[this.gestureBuffer.length - 1];
|
@@ -118,6 +496,7 @@ class GestureDetector {
|
|
118 |
const dx = (current.x - prev.x) * window.innerWidth;
|
119 |
const dy = (current.y - prev.y) * window.innerHeight;
|
120 |
|
|
|
121 |
if (Math.abs(dx) > this.swipeThreshold && Math.abs(dx) > Math.abs(dy) * 2) {
|
122 |
if (Date.now() - this.lastGestureTime > this.gestureCooldown) {
|
123 |
this.lastGestureTime = Date.now();
|
@@ -136,6 +515,7 @@ class GestureDetector {
|
|
136 |
}
|
137 |
}
|
138 |
|
|
|
139 |
this.circlePoints.push({x: palmCenterX, y: palmCenterY});
|
140 |
this.debugManager.updateCircleCount(this.circlePoints.length);
|
141 |
|
@@ -147,6 +527,7 @@ class GestureDetector {
|
|
147 |
const dy = endPoint.y - startPoint.y;
|
148 |
const distance = Math.sqrt(dx*dx + dy*dy) * window.innerWidth;
|
149 |
|
|
|
150 |
if (distance < 40 && Date.now() - this.lastGestureTime > this.gestureCooldown) {
|
151 |
this.lastGestureTime = Date.now();
|
152 |
this.debugManager.updateGestureType('Circle');
|
@@ -162,5 +543,124 @@ class GestureDetector {
|
|
162 |
}
|
163 |
}
|
164 |
|
165 |
-
|
166 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// ----------------------------
|
2 |
// Sample Email Data
|
3 |
+
// ----------------------------
|
4 |
const emails = [
|
5 |
{
|
6 |
id: 1,
|
|
|
16 |
snippet: "Top companies are hiring right now — don't miss your shot at these exclusive opportunities!",
|
17 |
timestamp: "12:35 PM"
|
18 |
},
|
19 |
+
{
|
20 |
+
id: 3,
|
21 |
+
sender: "Professor Smith",
|
22 |
+
subject: "Research Collaboration Proposal",
|
23 |
+
snippet: "I've reviewed your proposal draft. Let's schedule a call next week to discuss next steps.",
|
24 |
+
timestamp: "10:22 AM"
|
25 |
+
},
|
26 |
+
{
|
27 |
+
id: 4,
|
28 |
+
sender: "Friend",
|
29 |
+
subject: "Weekend Plans",
|
30 |
+
snippet: "Are we still on for the hiking trip this weekend? Let me know so I can book the gear.",
|
31 |
+
timestamp: "Yesterday"
|
32 |
+
},
|
33 |
+
{
|
34 |
+
id: 5,
|
35 |
+
sender: "Bank Support",
|
36 |
+
subject: "Monthly Statement",
|
37 |
+
snippet: "Your statement is ready. Minimum due: $120. Avoid penalties by paying before the 15th.",
|
38 |
+
timestamp: "Sep 5"
|
39 |
+
},
|
40 |
+
{
|
41 |
+
id: 6,
|
42 |
+
sender: "Netflix",
|
43 |
+
subject: "New Releases This Week!",
|
44 |
+
snippet: "Check out the hottest new shows and movies added to your watchlist this week.",
|
45 |
+
timestamp: "Sep 4"
|
46 |
+
},
|
47 |
+
{
|
48 |
+
id: 7,
|
49 |
+
sender: "Amazon",
|
50 |
+
subject: "Your Order #12345 Has Shipped",
|
51 |
+
snippet: "Your package is on the way! Track your delivery using the link below.",
|
52 |
+
timestamp: "Sep 3"
|
53 |
+
},
|
54 |
+
{
|
55 |
+
id: 8,
|
56 |
+
sender: "LinkedIn",
|
57 |
+
subject: "You have 5 new notifications",
|
58 |
+
snippet: "See who viewed your profile and new job recommendations.",
|
59 |
+
timestamp: "Sep 2"
|
60 |
+
},
|
61 |
+
{
|
62 |
+
id: 9,
|
63 |
+
sender: "Microsoft",
|
64 |
+
subject: "Your Office 365 Subscription Renewal",
|
65 |
+
snippet: "Your Office 365 subscription will expire in 7 days. Renew now to avoid service interruption.",
|
66 |
+
timestamp: "Sep 1"
|
67 |
+
},
|
68 |
+
{
|
69 |
+
id: 10,
|
70 |
+
sender: "TripAdvisor",
|
71 |
+
subject: "Your upcoming trip to Paris",
|
72 |
+
snippet: "Don't forget to check-in for your flight tomorrow. Here's your itinerary and hotel information.",
|
73 |
+
timestamp: "Aug 30"
|
74 |
+
},
|
75 |
+
{
|
76 |
+
id: 11,
|
77 |
+
sender: "GitHub",
|
78 |
+
subject: "New login to your account",
|
79 |
+
snippet: "We noticed a new login to your GitHub account from a Windows device in New York.",
|
80 |
+
timestamp: "Aug 29"
|
81 |
+
},
|
82 |
+
{
|
83 |
+
id: 12,
|
84 |
+
sender: "Spotify",
|
85 |
+
subject: "Your Weekly Discover Mix is ready",
|
86 |
+
snippet: "We've curated 30 songs just for you based on your listening habits. Listen now!",
|
87 |
+
timestamp: "Aug 28"
|
88 |
+
}
|
89 |
];
|
90 |
|
91 |
+
// ----------------------------
|
92 |
+
// Debug Utilities
|
93 |
+
// ----------------------------
|
94 |
class DebugManager {
|
95 |
constructor() {
|
96 |
this.statusEl = document.getElementById('debugStatus');
|
|
|
101 |
this.cameraStatusEl = document.getElementById('debugCameraStatus');
|
102 |
this.lastErrorEl = document.getElementById('debugLastError');
|
103 |
this.statusIndicator = document.getElementById('statusIndicator');
|
104 |
+
|
105 |
+
// Get debug toggle button
|
106 |
this.toggleButton = document.getElementById('debugToggle');
|
107 |
this.debugPanel = document.getElementById('debugPanel');
|
108 |
+
|
109 |
+
// Add click event listener
|
110 |
+
this.toggleButton.addEventListener('click', () => {
|
111 |
+
this.toggleDebugPanel();
|
112 |
+
});
|
113 |
+
}
|
114 |
+
|
115 |
+
updateStatus(status) {
|
116 |
+
this.statusEl.textContent = status;
|
117 |
+
}
|
118 |
+
|
119 |
+
updateSelectedEmail(emailId) {
|
120 |
+
if (emailId) {
|
121 |
+
const email = emails.find(e => e.id === emailId);
|
122 |
+
this.selectedEmailEl.textContent = email ? email.subject.substring(0, 20) + '...' : 'Unknown';
|
123 |
+
} else {
|
124 |
+
this.selectedEmailEl.textContent = 'None';
|
125 |
+
}
|
126 |
+
}
|
127 |
+
|
128 |
+
updateGestureType(gestureType) {
|
129 |
+
this.gestureTypeEl.textContent = gestureType;
|
130 |
+
}
|
131 |
+
|
132 |
+
updateBufferCount(count) {
|
133 |
+
this.bufferCountEl.textContent = `${count} points`;
|
134 |
+
}
|
135 |
+
|
136 |
+
updateCircleCount(count) {
|
137 |
+
this.circleCountEl.textContent = `${count} points`;
|
138 |
+
}
|
139 |
+
|
140 |
+
updateCameraStatus(status) {
|
141 |
+
this.cameraStatusEl.textContent = status;
|
142 |
+
}
|
143 |
+
|
144 |
+
logError(error) {
|
145 |
+
console.error("Gesture Detection Error:", error);
|
146 |
+
this.lastErrorEl.textContent = error.message.substring(0, 50) + (error.message.length > 50 ? '...' : '');
|
147 |
+
|
148 |
+
// Update status indicator to red
|
149 |
+
this.statusIndicator.className = 'status-indicator error';
|
150 |
+
}
|
151 |
+
|
152 |
+
setReady() {
|
153 |
+
this.updateStatus('Ready');
|
154 |
+
this.statusIndicator.className = 'status-indicator ready';
|
155 |
+
}
|
156 |
+
|
157 |
+
setProcessing() {
|
158 |
+
this.updateStatus('Processing...');
|
159 |
+
this.statusIndicator.className = 'status-indicator processing';
|
160 |
}
|
161 |
|
162 |
+
toggleDebugPanel() {
|
163 |
+
if (this.debugPanel.classList.contains('visible')) {
|
164 |
+
this.debugPanel.classList.remove('visible');
|
165 |
+
} else {
|
166 |
+
this.debugPanel.classList.add('visible');
|
167 |
+
}
|
168 |
+
}
|
169 |
}
|
170 |
|
171 |
+
// ----------------------------
|
172 |
+
// UI Management
|
173 |
+
// ----------------------------
|
174 |
class UIManager {
|
175 |
+
constructor() {
|
176 |
+
this.emailList = document.getElementById('emailList');
|
177 |
+
this.actionFeedback = document.getElementById('actionFeedback');
|
178 |
+
this.selectionHighlight = document.getElementById('selectionHighlight');
|
179 |
+
this.handLandmarks = document.getElementById('handLandmarks');
|
180 |
+
this.gesturePath = document.getElementById('gesturePath');
|
181 |
+
|
182 |
+
this.selectedEmail = null;
|
183 |
+
this.emailElements = [];
|
184 |
+
|
185 |
+
this.renderEmails();
|
186 |
+
this.setupEventListeners();
|
187 |
+
}
|
188 |
+
|
189 |
renderEmails() {
|
190 |
this.emailList.innerHTML = '';
|
191 |
this.emailElements = [];
|
|
|
212 |
});
|
213 |
});
|
214 |
|
215 |
+
// Update email positions
|
216 |
this.updateEmailPositions();
|
217 |
}
|
218 |
|
219 |
+
updateEmailPositions() {
|
220 |
+
this.emailElements.forEach(item => {
|
221 |
+
const rect = item.element.getBoundingClientRect();
|
222 |
+
item.rect = {
|
223 |
+
left: rect.left,
|
224 |
+
top: rect.top,
|
225 |
+
right: rect.right,
|
226 |
+
bottom: rect.bottom,
|
227 |
+
width: rect.width,
|
228 |
+
height: rect.height
|
229 |
+
};
|
230 |
+
});
|
231 |
+
}
|
232 |
+
|
233 |
+
selectEmail(emailId) {
|
234 |
+
// Remove previous selection
|
235 |
+
if (this.selectedEmail) {
|
236 |
+
const prevElement = this.emailElements.find(e => e.id === this.selectedEmail);
|
237 |
+
if (prevElement) {
|
238 |
+
prevElement.element.classList.remove('selected');
|
239 |
+
}
|
240 |
+
}
|
241 |
+
|
242 |
+
// Set new selection
|
243 |
+
this.selectedEmail = emailId;
|
244 |
+
const newElement = this.emailElements.find(e => e.id === emailId);
|
245 |
+
|
246 |
+
if (newElement) {
|
247 |
+
newElement.element.classList.add('selected');
|
248 |
+
this.showSelectionHighlight(newElement.rect);
|
249 |
+
}
|
250 |
+
|
251 |
+
return newElement ? newElement.rect : null;
|
252 |
+
}
|
253 |
+
|
254 |
+
showSelectionHighlight(rect) {
|
255 |
+
this.selectionHighlight.style.display = 'block';
|
256 |
+
this.selectionHighlight.style.left = `${rect.left}px`;
|
257 |
+
this.selectionHighlight.style.top = `${rect.top}px`;
|
258 |
+
this.selectionHighlight.style.width = `${rect.width}px`;
|
259 |
+
this.selectionHighlight.style.height = `${rect.height}px`;
|
260 |
+
}
|
261 |
+
|
262 |
+
hideSelectionHighlight() {
|
263 |
+
this.selectionHighlight.style.display = 'none';
|
264 |
+
}
|
265 |
+
|
266 |
+
showActionFeedback(message, type) {
|
267 |
+
this.actionFeedback.textContent = message;
|
268 |
+
this.actionFeedback.className = 'action-feedback';
|
269 |
+
|
270 |
+
if (type === 'delete') {
|
271 |
+
this.actionFeedback.classList.add('delete');
|
272 |
+
} else if (type === 'archive') {
|
273 |
+
this.actionFeedback.classList.add('archive');
|
274 |
+
} else {
|
275 |
+
this.actionFeedback.classList.add('summary');
|
276 |
+
}
|
277 |
+
|
278 |
+
this.actionFeedback.classList.add('show');
|
279 |
+
|
280 |
+
setTimeout(() => {
|
281 |
+
this.actionFeedback.classList.remove('show');
|
282 |
+
}, 2000);
|
283 |
+
}
|
284 |
+
|
285 |
+
clearSelection() {
|
286 |
+
if (this.selectedEmail) {
|
287 |
+
const element = this.emailElements.find(e => e.id === this.selectedEmail);
|
288 |
+
if (element) {
|
289 |
+
element.element.classList.remove('selected');
|
290 |
+
}
|
291 |
+
this.selectedEmail = null;
|
292 |
+
this.hideSelectionHighlight();
|
293 |
+
}
|
294 |
+
}
|
295 |
+
|
296 |
+
setupEventListeners() {
|
297 |
+
window.addEventListener('resize', () => {
|
298 |
+
this.updateEmailPositions();
|
299 |
+
if (this.selectedEmail) {
|
300 |
+
const element = this.emailElements.find(e => e.id === this.selectedEmail);
|
301 |
+
if (element) {
|
302 |
+
this.showSelectionHighlight(element.rect);
|
303 |
+
}
|
304 |
+
}
|
305 |
+
});
|
306 |
+
}
|
307 |
+
|
308 |
+
// For gesture visualization
|
309 |
+
updateHandLandmarks(landmarks) {
|
310 |
+
this.handLandmarks.innerHTML = '';
|
311 |
+
|
312 |
+
if (!landmarks || landmarks.length === 0) return;
|
313 |
+
|
314 |
+
landmarks.forEach((landmark, i) => {
|
315 |
+
const landmarkEl = document.createElement('div');
|
316 |
+
landmarkEl.className = 'landmark';
|
317 |
+
if (i === 8) { // Index finger tip
|
318 |
+
landmarkEl.classList.add('index-tip');
|
319 |
+
}
|
320 |
+
|
321 |
+
landmarkEl.style.left = `${landmark.x * 100}%`;
|
322 |
+
landmarkEl.style.top = `${landmark.y * 100}%`;
|
323 |
+
|
324 |
+
this.handLandmarks.appendChild(landmarkEl);
|
325 |
+
});
|
326 |
+
}
|
327 |
+
|
328 |
+
updateGesturePath(points) {
|
329 |
+
this.gesturePath.innerHTML = '';
|
330 |
+
|
331 |
+
if (!points || points.length === 0) return;
|
332 |
+
|
333 |
+
points.forEach(point => {
|
334 |
+
const pointEl = document.createElement('div');
|
335 |
+
pointEl.className = 'point';
|
336 |
+
pointEl.style.left = `${point.x * 100}%`;
|
337 |
+
pointEl.style.top = `${point.y * 100}%`;
|
338 |
+
this.gesturePath.appendChild(pointEl);
|
339 |
+
});
|
340 |
+
}
|
341 |
}
|
342 |
|
343 |
+
// ----------------------------
|
344 |
+
// Gesture Detection
|
345 |
+
// ----------------------------
|
346 |
class GestureDetector {
|
347 |
constructor(uiManager, debugManager) {
|
348 |
this.uiManager = uiManager;
|
|
|
350 |
this.selectedEmailId = null;
|
351 |
this.gestureBuffer = [];
|
352 |
this.circlePoints = [];
|
353 |
+
this.circleThreshold = 8; // Reduced from 10 for better circle detection
|
354 |
+
this.swipeThreshold = 30; // Reduced from 50 for more sensitive swipe detection
|
355 |
+
this.gestureCooldown = 1500; // 1.5 seconds
|
356 |
this.lastGestureTime = 0;
|
357 |
+
this.camera = null;
|
358 |
+
|
359 |
+
this.debugManager.updateStatus('Setting up MediaPipe...');
|
360 |
this.setupMediaPipe();
|
361 |
}
|
362 |
|
363 |
+
setupMediaPipe() {
|
364 |
+
try {
|
365 |
+
const hands = new Hands({locateFile: (file) => {
|
366 |
+
return `https://cdn.jsdelivr.net/npm/@mediapipe/[email protected]/${file}`;
|
367 |
+
}});
|
368 |
+
|
369 |
+
hands.setOptions({
|
370 |
+
maxNumHands: 1,
|
371 |
+
modelComplexity: 1,
|
372 |
+
minDetectionConfidence: 0.7,
|
373 |
+
minTrackingConfidence: 0.7
|
374 |
+
});
|
375 |
+
|
376 |
+
hands.onResults(results => {
|
377 |
+
this.processResults(results);
|
378 |
+
});
|
379 |
+
|
380 |
+
const videoElement = document.getElementById('webcam');
|
381 |
+
|
382 |
+
// Check if Camera is available
|
383 |
+
if (typeof Camera === 'undefined') {
|
384 |
+
this.debugManager.logError(new Error("Camera utils not loaded. Make sure camera_utils.js is included."));
|
385 |
+
return;
|
386 |
+
}
|
387 |
+
|
388 |
+
this.camera = new Camera(videoElement, {
|
389 |
+
onFrame: async () => {
|
390 |
+
try {
|
391 |
+
this.debugManager.setProcessing();
|
392 |
+
await hands.send({image: videoElement});
|
393 |
+
} catch (error) {
|
394 |
+
this.debugManager.logError(error);
|
395 |
+
}
|
396 |
+
},
|
397 |
+
width: 320,
|
398 |
+
height: 240
|
399 |
+
});
|
400 |
+
|
401 |
+
this.startCamera();
|
402 |
+
} catch (error) {
|
403 |
+
this.debugManager.logError(error);
|
404 |
+
console.error("MediaPipe setup error:", error);
|
405 |
+
}
|
406 |
+
}
|
407 |
+
|
408 |
+
async startCamera() {
|
409 |
+
try {
|
410 |
+
this.debugManager.updateCameraStatus('Starting...');
|
411 |
+
await this.camera.start();
|
412 |
+
this.debugManager.updateCameraStatus('Active');
|
413 |
+
this.debugManager.setReady();
|
414 |
+
console.log("Camera initialized successfully");
|
415 |
+
} catch (error) {
|
416 |
+
this.debugManager.updateCameraStatus('Error');
|
417 |
+
this.debugManager.logError(error);
|
418 |
+
|
419 |
+
// Try to get more specific error information
|
420 |
+
if (error.name === 'NotAllowedError') {
|
421 |
+
alert("Camera access denied. Please allow camera access in your browser settings.");
|
422 |
+
} else if (error.name === 'NotFoundError') {
|
423 |
+
alert("No camera found. Please connect a camera device.");
|
424 |
+
} else {
|
425 |
+
alert("Failed to start camera: " + error.message);
|
426 |
+
}
|
427 |
+
}
|
428 |
+
}
|
429 |
+
|
430 |
+
processResults(results) {
|
431 |
+
try {
|
432 |
+
// Update hand landmarks visualization
|
433 |
+
if (results.multiHandLandmarks && results.multiHandLandmarks.length > 0) {
|
434 |
+
this.uiManager.updateHandLandmarks(results.multiHandLandmarks[0]);
|
435 |
+
|
436 |
+
// Update gesture path for debugging
|
437 |
+
if (this.gestureBuffer.length > 0) {
|
438 |
+
this.uiManager.updateGesturePath(this.gestureBuffer);
|
439 |
+
}
|
440 |
+
|
441 |
+
this.detectGesture(results.multiHandLandmarks[0]);
|
442 |
+
} else {
|
443 |
+
this.uiManager.clearSelection();
|
444 |
+
this.gestureBuffer = [];
|
445 |
+
this.circlePoints = [];
|
446 |
+
this.debugManager.updateGestureType('None');
|
447 |
+
this.debugManager.updateBufferCount(0);
|
448 |
+
this.debugManager.updateCircleCount(0);
|
449 |
+
}
|
450 |
+
} catch (error) {
|
451 |
+
this.debugManager.logError(error);
|
452 |
+
}
|
453 |
+
}
|
454 |
+
|
455 |
detectGesture(landmarks) {
|
456 |
try {
|
457 |
+
// Convert to pixel coordinates for easier calculation
|
458 |
const indexTip = landmarks[8];
|
459 |
const middleTip = landmarks[12];
|
460 |
const wrist = landmarks[0];
|
461 |
|
462 |
+
// Pointing detection (index finger higher than middle)
|
463 |
+
// Fix: Proper Y-axis calculation (inverted in MediaPipe)
|
464 |
const screenX = indexTip.x * window.innerWidth;
|
465 |
+
const screenY = (1 - indexTip.y) * window.innerHeight;
|
466 |
|
467 |
+
if (indexTip.y < middleTip.y) {
|
468 |
this.checkEmailSelection(screenX, screenY);
|
469 |
} else {
|
470 |
this.uiManager.clearSelection();
|
|
|
475 |
this.debugManager.updateCircleCount(0);
|
476 |
}
|
477 |
|
478 |
+
// Only process gestures if an email is selected
|
479 |
+
if (this.selectedEmailId === null) {
|
480 |
+
return;
|
481 |
+
}
|
482 |
|
483 |
+
// Get palm center for gesture detection
|
484 |
+
// Fix: Better palm center calculation
|
485 |
const palmCenterX = (wrist.x + landmarks[9].x) / 2;
|
486 |
const palmCenterY = (wrist.y + landmarks[9].y) / 2;
|
487 |
|
488 |
+
// Add to gesture buffer
|
489 |
this.gestureBuffer.push({x: palmCenterX, y: palmCenterY});
|
490 |
|
491 |
+
// Check for swipe
|
492 |
if (this.gestureBuffer.length > 2) {
|
493 |
const prev = this.gestureBuffer[this.gestureBuffer.length - 2];
|
494 |
const current = this.gestureBuffer[this.gestureBuffer.length - 1];
|
|
|
496 |
const dx = (current.x - prev.x) * window.innerWidth;
|
497 |
const dy = (current.y - prev.y) * window.innerHeight;
|
498 |
|
499 |
+
// Check if it's a horizontal swipe
|
500 |
if (Math.abs(dx) > this.swipeThreshold && Math.abs(dx) > Math.abs(dy) * 2) {
|
501 |
if (Date.now() - this.lastGestureTime > this.gestureCooldown) {
|
502 |
this.lastGestureTime = Date.now();
|
|
|
515 |
}
|
516 |
}
|
517 |
|
518 |
+
// Check for circle
|
519 |
this.circlePoints.push({x: palmCenterX, y: palmCenterY});
|
520 |
this.debugManager.updateCircleCount(this.circlePoints.length);
|
521 |
|
|
|
527 |
const dy = endPoint.y - startPoint.y;
|
528 |
const distance = Math.sqrt(dx*dx + dy*dy) * window.innerWidth;
|
529 |
|
530 |
+
// Fix: Better circle detection with radius check
|
531 |
if (distance < 40 && Date.now() - this.lastGestureTime > this.gestureCooldown) {
|
532 |
this.lastGestureTime = Date.now();
|
533 |
this.debugManager.updateGestureType('Circle');
|
|
|
543 |
}
|
544 |
}
|
545 |
|
546 |
+
checkEmailSelection(x, y) {
|
547 |
+
try {
|
548 |
+
// Check which email is under the finger
|
549 |
+
for (let i = this.uiManager.emailElements.length - 1; i >= 0; i--) {
|
550 |
+
const email = this.uiManager.emailElements[i];
|
551 |
+
if (email.rect &&
|
552 |
+
x >= email.rect.left &&
|
553 |
+
x <= email.rect.right &&
|
554 |
+
y >= email.rect.top &&
|
555 |
+
y <= email.rect.bottom) {
|
556 |
+
|
557 |
+
// Only select if it's a different email
|
558 |
+
if (this.selectedEmailId !== email.id) {
|
559 |
+
this.selectedEmailId = email.id;
|
560 |
+
this.uiManager.selectEmail(email.id);
|
561 |
+
this.debugManager.updateSelectedEmail(email.id);
|
562 |
+
}
|
563 |
+
return;
|
564 |
+
}
|
565 |
+
}
|
566 |
+
|
567 |
+
// If no email is selected, clear selection
|
568 |
+
if (!this.uiManager.emailElements.some(email =>
|
569 |
+
email.rect &&
|
570 |
+
x >= email.rect.left &&
|
571 |
+
x <= email.rect.right &&
|
572 |
+
y >= email.rect.top &&
|
573 |
+
y <= email.rect.bottom)) {
|
574 |
+
this.uiManager.clearSelection();
|
575 |
+
this.selectedEmailId = null;
|
576 |
+
this.debugManager.updateSelectedEmail(null);
|
577 |
+
}
|
578 |
+
} catch (error) {
|
579 |
+
this.debugManager.logError(error);
|
580 |
+
}
|
581 |
+
}
|
582 |
+
|
583 |
+
handleGesture(gesture) {
|
584 |
+
try {
|
585 |
+
if (!this.selectedEmailId) return;
|
586 |
+
|
587 |
+
const email = emails.find(e => e.id === this.selectedEmailId);
|
588 |
+
if (!email) return;
|
589 |
+
|
590 |
+
switch (gesture) {
|
591 |
+
case 'swipe_left':
|
592 |
+
this.uiManager.showActionFeedback(`🗑️ Deleted: ${email.subject}`, 'delete');
|
593 |
+
// Remove from UI
|
594 |
+
const index = emails.findIndex(e => e.id === this.selectedEmailId);
|
595 |
+
if (index !== -1) emails.splice(index, 1);
|
596 |
+
this.uiManager.renderEmails();
|
597 |
+
this.selectedEmailId = null;
|
598 |
+
this.debugManager.updateSelectedEmail(null);
|
599 |
+
break;
|
600 |
+
|
601 |
+
case 'swipe_right':
|
602 |
+
this.uiManager.showActionFeedback(`✅ Archived: ${email.subject}`, 'archive');
|
603 |
+
// In a real app, we'd move to archive
|
604 |
+
const archiveIndex = emails.findIndex(e => e.id === this.selectedEmailId);
|
605 |
+
if (archiveIndex !== -1) emails.splice(archiveIndex, 1);
|
606 |
+
this.uiManager.renderEmails();
|
607 |
+
this.selectedEmailId = null;
|
608 |
+
this.debugManager.updateSelectedEmail(null);
|
609 |
+
break;
|
610 |
+
|
611 |
+
case 'circle':
|
612 |
+
const summary = `This email discusses ${email.subject.toLowerCase()}.`;
|
613 |
+
this.uiManager.showActionFeedback(`📝 Summary: ${summary}`, 'summary');
|
614 |
+
break;
|
615 |
+
}
|
616 |
+
} catch (error) {
|
617 |
+
this.debugManager.logError(error);
|
618 |
+
}
|
619 |
+
}
|
620 |
+
}
|
621 |
+
|
622 |
+
// ----------------------------
|
623 |
+
// Initialize App
|
624 |
+
// ----------------------------
|
625 |
+
document.addEventListener('DOMContentLoaded', () => {
|
626 |
+
// Initialize debug manager first
|
627 |
+
const debugManager = new DebugManager();
|
628 |
+
debugManager.updateStatus('Initializing UI...');
|
629 |
+
|
630 |
+
// Initialize UI
|
631 |
+
const uiManager = new UIManager();
|
632 |
+
debugManager.setReady();
|
633 |
+
|
634 |
+
// Request camera access
|
635 |
+
const videoElement = document.getElementById('webcam');
|
636 |
+
|
637 |
+
navigator.mediaDevices.getUserMedia({ video: true })
|
638 |
+
.then(stream => {
|
639 |
+
videoElement.srcObject = stream;
|
640 |
+
debugManager.updateCameraStatus('Initializing...');
|
641 |
+
|
642 |
+
// Initialize gesture detection after a short delay to ensure UI is ready
|
643 |
+
setTimeout(() => {
|
644 |
+
debugManager.updateStatus('Setting up gesture detection...');
|
645 |
+
try {
|
646 |
+
new GestureDetector(uiManager, debugManager);
|
647 |
+
} catch (error) {
|
648 |
+
debugManager.logError(error);
|
649 |
+
}
|
650 |
+
}, 1500);
|
651 |
+
})
|
652 |
+
.catch(err => {
|
653 |
+
debugManager.updateCameraStatus('Denied');
|
654 |
+
debugManager.logError(err);
|
655 |
+
console.error("Error accessing camera:", err);
|
656 |
+
|
657 |
+
// More specific error handling
|
658 |
+
if (err.name === 'NotAllowedError') {
|
659 |
+
alert("Camera access denied. Please allow camera access in your browser settings.");
|
660 |
+
} else if (err.name === 'NotFoundError') {
|
661 |
+
alert("No camera found. Please connect a camera device.");
|
662 |
+
} else {
|
663 |
+
alert("Camera error: " + err.message);
|
664 |
+
}
|
665 |
+
});
|
666 |
+
});
|