17 lines
1.7 KiB
JavaScript
17 lines
1.7 KiB
JavaScript
import {FEEDBACK} from './feedback-params.mjs';
|
|
export const HAPTIC_KEY='number-swipe:haptics:v1';
|
|
export class Haptics{
|
|
constructor(api,storage,{now=()=>Date.now(),later=setTimeout,cancel=clearTimeout,preview=false}={}){Object.assign(this,{api,storage,now,later,cancel,preview});this.enabled=true;this.last=-Infinity;this.generation=0;this.timers=new Set();this.graded=true;try{this.enabled=storage.getItem(HAPTIC_KEY)!=='off';}catch{}}
|
|
setEnabled(value){this.enabled=value;try{this.storage.setItem(HAPTIC_KEY,value?'on':'off');}catch{}if(!value)this.stop();}
|
|
stop(){this.generation++;for(const t of this.timers)this.cancel(t);this.timers.clear();}
|
|
pulse(type,force=false){
|
|
if(!this.enabled||this.preview||typeof this.api?.vibrateShort!=='function'||!force&&this.now()-this.last<FEEDBACK.hapticGapMs)return;
|
|
this.last=this.now();const generation=this.generation,request=this.request=(this.request||0)+1,sentAt=this.last;
|
|
const fallback=()=>{if(generation!==this.generation||request!==this.request||this.now()-sentAt>FEEDBACK.hapticGapMs||!this.enabled)return;this.graded=false;try{this.api.vibrateShort({fail(){}});}catch{}};
|
|
try{this.api.vibrateShort(this.graded?{type,fail:fallback}:{fail(){}});}catch{if(this.graded)fallback();}
|
|
}
|
|
play(kind){if(!['move','undo','checkpoint','complete'].includes(kind))return;this.pulse(kind==='move'||kind==='undo'?'light':'medium',kind!=='move'&&kind!=='undo');
|
|
if(kind==='complete'&&this.enabled&&this.graded&&!this.preview&&typeof this.api?.vibrateShort==='function'){const generation=this.generation;const timer=this.later(()=>{this.timers.delete(timer);if(generation===this.generation&&this.graded)this.pulse('light',true);},FEEDBACK.completeHapticDelayMs);this.timers.add(timer);}
|
|
}
|
|
}
|