@@ -26,6 +26,16 @@ class EscDshotDirectionComponent {
26
26
this . _allMotorsAreSpinning = false ;
27
27
this . _spinDirectionToggleIsActive = true ;
28
28
this . _activationButtonTimeoutId = null ;
29
+ this . _isKeyboardControlEnabled = false ;
30
+ this . _spacebarPressed = false ;
31
+ this . _keyboardEventHandlerBound = false ;
32
+ this . _isWizardActive = false ;
33
+ this . _globalKeyboardActive = false ;
34
+
35
+ // Bind methods to preserve 'this' context - CRITICAL for event handlers
36
+ this . _handleWizardKeyDown = this . _handleWizardKeyDown . bind ( this ) ;
37
+ this . _handleWizardKeyUp = this . _handleWizardKeyUp . bind ( this ) ;
38
+ this . _handleGlobalKeyDown = this . _handleGlobalKeyDown . bind ( this ) ;
29
39
30
40
this . _contentDiv . load ( "./components/EscDshotDirection/Body.html" , ( ) => {
31
41
this . _initializeDialog ( ) ;
@@ -285,9 +295,193 @@ class EscDshotDirectionComponent {
285
295
}
286
296
}
287
297
298
+ _enableGlobalKeyboard ( ) {
299
+ if ( this . _globalKeyboardActive ) return ;
300
+
301
+ document . addEventListener ( "keydown" , this . _handleGlobalKeyDown , true ) ;
302
+ this . _globalKeyboardActive = true ;
303
+ }
304
+
305
+ _disableGlobalKeyboard ( ) {
306
+ document . removeEventListener ( "keydown" , this . _handleGlobalKeyDown , true ) ;
307
+ this . _globalKeyboardActive = false ;
308
+ }
309
+
310
+ _handleGlobalKeyDown ( event ) {
311
+ // Only handle spacebar for wizard workflow progression
312
+ if ( event . code !== "Space" || event . repeat ) {
313
+ return ;
314
+ }
315
+
316
+ // Only process keyboard input if the dialog is actually visible
317
+ // Check if either the warning content OR main content is visible
318
+ const dialogIsVisible =
319
+ ( this . _domWarningContentBlock && this . _domWarningContentBlock . is ( ":visible" ) ) ||
320
+ ( this . _domMainContentBlock && this . _domMainContentBlock . is ( ":visible" ) ) ;
321
+
322
+ if ( ! dialogIsVisible ) {
323
+ return ;
324
+ }
325
+
326
+ // Step 1: Check the safety checkbox if it's not checked and warning is visible
327
+ if ( this . _domWarningContentBlock . is ( ":visible" ) && ! this . _domAgreeSafetyCheckBox . is ( ":checked" ) ) {
328
+ event . preventDefault ( ) ;
329
+ event . stopPropagation ( ) ;
330
+ this . _domAgreeSafetyCheckBox . prop ( "checked" , true ) ;
331
+ this . _domAgreeSafetyCheckBox . trigger ( "change" ) ;
332
+ return ;
333
+ }
334
+
335
+ // Step 2: Start wizard if checkbox is checked and wizard isn't open yet
336
+ if ( this . _domWarningContentBlock . is ( ":visible" ) && this . _domAgreeSafetyCheckBox . is ( ":checked" ) ) {
337
+ event . preventDefault ( ) ;
338
+ event . stopPropagation ( ) ;
339
+ this . _onStartWizardButtonClicked ( ) ;
340
+ return ;
341
+ }
342
+
343
+ // Step 3: Spin motors if wizard is open but not spinning yet
344
+ if (
345
+ this . _domMainContentBlock . is ( ":visible" ) &&
346
+ this . _domSpinWizardButton . is ( ":visible" ) &&
347
+ ! this . _isWizardActive
348
+ ) {
349
+ event . preventDefault ( ) ;
350
+ event . stopPropagation ( ) ;
351
+ // Mark spacebar as pressed since we're transitioning to wizard control while key is down
352
+ this . _spacebarPressed = true ;
353
+ this . _onSpinWizardButtonClicked ( ) ;
354
+ return ;
355
+ }
356
+
357
+ // Step 4: If wizard is active, let the wizard keyboard handler take over
358
+ // (no action needed here, the _handleWizardKeyDown will handle it)
359
+ }
360
+
361
+ _enableKeyboardControl ( ) {
362
+ if ( this . _keyboardEventHandlerBound ) return ;
363
+
364
+ // CRITICAL: Use capture phase (third parameter = true) for reliable event handling
365
+ // This prevents other elements from stopping propagation before we handle the event
366
+ document . addEventListener ( "keydown" , this . _handleWizardKeyDown , true ) ;
367
+ document . addEventListener ( "keyup" , this . _handleWizardKeyUp , true ) ;
368
+
369
+ // SAFETY FEATURE: Stop motors if user switches windows while holding spacebar
370
+ window . addEventListener ( "blur" , ( ) => {
371
+ if ( this . _spacebarPressed ) {
372
+ this . _spacebarPressed = false ;
373
+ this . _handleSpacebarRelease ( ) ;
374
+ }
375
+ } ) ;
376
+
377
+ this . _keyboardEventHandlerBound = true ;
378
+ this . _isKeyboardControlEnabled = true ;
379
+ }
380
+
381
+ _disableKeyboardControl ( ) {
382
+ document . removeEventListener ( "keydown" , this . _handleWizardKeyDown , true ) ;
383
+ document . removeEventListener ( "keyup" , this . _handleWizardKeyUp , true ) ;
384
+ window . removeEventListener ( "blur" , this . _handleWizardKeyDown ) ;
385
+ this . _keyboardEventHandlerBound = false ;
386
+ this . _isKeyboardControlEnabled = false ;
387
+ this . _spacebarPressed = false ;
388
+ }
389
+
390
+ _handleWizardKeyDown ( event ) {
391
+ // Only handle events when keyboard control is active
392
+ if ( ! this . _isKeyboardControlEnabled || ! this . _isWizardActive ) {
393
+ return ;
394
+ }
395
+
396
+ // SPACEBAR: Spin all motors (hold to spin, release to stop)
397
+ if ( event . code === "Space" ) {
398
+ event . preventDefault ( ) ;
399
+ event . stopPropagation ( ) ;
400
+ // CRITICAL: Check !event.repeat to prevent multiple triggers when holding key
401
+ if ( ! this . _spacebarPressed && ! event . repeat ) {
402
+ this . _spacebarPressed = true ;
403
+ this . _handleSpacebarPress ( ) ;
404
+ }
405
+ return ;
406
+ }
407
+
408
+ // NUMBER KEYS 1-8: Toggle individual motor direction
409
+ if ( event . key >= "1" && event . key <= "8" && ! event . repeat ) {
410
+ event . preventDefault ( ) ;
411
+ event . stopPropagation ( ) ;
412
+ const motorIndex = parseInt ( event . key ) - 1 ;
413
+
414
+ if ( motorIndex < this . _numberOfMotors ) {
415
+ this . _toggleMotorDirection ( motorIndex ) ;
416
+ }
417
+ return ;
418
+ }
419
+ }
420
+
421
+ _handleWizardKeyUp ( event ) {
422
+ if ( ! this . _isKeyboardControlEnabled || ! this . _isWizardActive ) {
423
+ return ;
424
+ }
425
+
426
+ // SPACEBAR RELEASE: Stop motors immediately
427
+ if ( event . code === "Space" ) {
428
+ event . preventDefault ( ) ;
429
+ event . stopPropagation ( ) ;
430
+ if ( this . _spacebarPressed ) {
431
+ this . _spacebarPressed = false ;
432
+ this . _handleSpacebarRelease ( ) ;
433
+ }
434
+ }
435
+ }
436
+
437
+ _handleSpacebarPress ( ) {
438
+ this . _motorDriver . spinAllMotors ( ) ;
439
+ }
440
+
441
+ _handleSpacebarRelease ( ) {
442
+ this . _motorDriver . stopAllMotorsNow ( ) ;
443
+ }
444
+
445
+ _toggleMotorDirection ( motorIndex ) {
446
+ const button = this . _wizardMotorButtons [ motorIndex ] ;
447
+ const currentlyReversed = button . hasClass ( EscDshotDirectionComponent . PUSHED_BUTTON_CLASS ) ;
448
+
449
+ if ( currentlyReversed ) {
450
+ button . removeClass ( EscDshotDirectionComponent . PUSHED_BUTTON_CLASS ) ;
451
+ this . _motorDriver . setEscSpinDirection ( motorIndex , DshotCommand . dshotCommands_e . DSHOT_CMD_SPIN_DIRECTION_1 ) ;
452
+ } else {
453
+ button . addClass ( EscDshotDirectionComponent . PUSHED_BUTTON_CLASS ) ;
454
+ this . _motorDriver . setEscSpinDirection ( motorIndex , DshotCommand . dshotCommands_e . DSHOT_CMD_SPIN_DIRECTION_2 ) ;
455
+ }
456
+ }
457
+
458
+ open ( ) {
459
+ // Enable global keyboard when dialog is opened
460
+ this . _enableGlobalKeyboard ( ) ;
461
+ }
462
+
288
463
close ( ) {
464
+ // Disable keyboard handlers first to prevent any new input
465
+ this . _disableKeyboardControl ( ) ;
466
+ this . _disableGlobalKeyboard ( ) ;
467
+
468
+ // If wizard is active, deactivate buttons but DON'T clear the flag yet
469
+ // This ensures pending motor direction commands complete
470
+ if ( this . _isWizardActive ) {
471
+ this . _deactivateWizardMotorButtons ( ) ;
472
+ }
473
+
474
+ // Stop motors (this adds stop commands to the queue)
289
475
this . _motorDriver . stopAllMotorsNow ( ) ;
476
+
477
+ // Deactivate motor driver - this tells queue to stop AFTER processing current commands
478
+ // This is critical - it allows direction change + save commands to complete
290
479
this . _motorDriver . deactivate ( ) ;
480
+
481
+ // Clear wizard flag after motor driver deactivation
482
+ this . _isWizardActive = false ;
483
+
484
+ // Reset GUI last
291
485
this . _resetGui ( ) ;
292
486
}
293
487
@@ -363,13 +557,21 @@ class EscDshotDirectionComponent {
363
557
this . _motorDriver . spinAllMotors ( ) ;
364
558
365
559
this . _activateWizardMotorButtons ( 0 ) ;
560
+
561
+ // NEW: Enable keyboard shortcuts when wizard starts spinning
562
+ this . _isWizardActive = true ;
563
+ this . _enableKeyboardControl ( ) ;
366
564
}
367
565
368
566
_onStopWizardButtonClicked ( ) {
369
567
this . _domSpinWizardButton . toggle ( true ) ;
370
568
this . _domSpinningWizard . toggle ( false ) ;
371
569
this . _motorDriver . stopAllMotorsNow ( ) ;
372
570
this . _deactivateWizardMotorButtons ( ) ;
571
+
572
+ // NEW: Disable keyboard shortcuts when wizard stops
573
+ this . _disableKeyboardControl ( ) ;
574
+ this . _isWizardActive = false ;
373
575
}
374
576
375
577
_toggleMainContent ( value ) {
0 commit comments