From d240de54be8665689ecba32a1c8028699b977b07 Mon Sep 17 00:00:00 2001 From: "jakob.stendahl" Date: Mon, 16 Jan 2023 09:44:22 +0100 Subject: :rocket: Deploy app v1.0.3 --- index.html | 2 +- main.cf2d245a.js | 10 ---------- main.cf2d245a.js.map | 1 - main.fa7f93a2.js | 10 ++++++++++ main.fa7f93a2.js.map | 1 + service-worker.js | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) delete mode 100644 main.cf2d245a.js delete mode 100644 main.cf2d245a.js.map create mode 100644 main.fa7f93a2.js create mode 100644 main.fa7f93a2.js.map diff --git a/index.html b/index.html index eefa1a0..a913286 100644 --- a/index.html +++ b/index.html @@ -1 +1 @@ -MICRO:BIT gamepad

MICRO:BIT gamepad

1.0.2

Settings

Please use landscape mode

 
\ No newline at end of file +MICRO:BIT gamepad

MICRO:BIT gamepad

1.0.3

Settings

Please use landscape mode

 
\ No newline at end of file diff --git a/main.cf2d245a.js b/main.cf2d245a.js deleted file mode 100644 index b0a0f91..0000000 --- a/main.cf2d245a.js +++ /dev/null @@ -1,10 +0,0 @@ -parcelRequire=function(e,r,t,n){var i,o="function"==typeof parcelRequire&&parcelRequire,u="function"==typeof require&&require;function f(t,n){if(!r[t]){if(!e[t]){var i="function"==typeof parcelRequire&&parcelRequire;if(!n&&i)return i(t,!0);if(o)return o(t,!0);if(u&&"string"==typeof t)return u(t);var c=new Error("Cannot find module '"+t+"'");throw c.code="MODULE_NOT_FOUND",c}p.resolve=function(r){return e[t][1][r]||r},p.cache={};var l=r[t]=new f.Module(t);e[t][0].call(l.exports,p,l,l.exports,this)}return r[t].exports;function p(e){return f(p.resolve(e))}}f.isParcelRequire=!0,f.Module=function(e){this.id=e,this.bundle=f,this.exports={}},f.modules=e,f.cache=r,f.parent=o,f.register=function(r,t){e[r]=[function(e,r){r.exports=t},{}]};for(var c=0;c=0;--n){var i=this.tryEntries[n],a=i.completion;if("root"===i.tryLoc)return r("end");if(i.tryLoc<=this.prev){var c=o.call(i,"catchLoc"),u=o.call(i,"finallyLoc");if(c&&u){if(this.prev=0;--r){var n=this.tryEntries[r];if(n.tryLoc<=this.prev&&o.call(n,"finallyLoc")&&this.prev=0;--e){var r=this.tryEntries[e];if(r.finallyLoc===t)return this.complete(r.completion,r.afterLoc),S(r),v}},catch:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var r=this.tryEntries[e];if(r.tryLoc===t){var n=r.completion;if("throw"===n.type){var o=n.arg;S(r)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(t,e,r){return this.delegate={iterator:x(t),resultName:e,nextLoc:r},"next"===this.method&&(this.arg=void 0),v}},r}function r(t,e,r,n,o,i,a){try{var c=t[i](a),u=c.value}catch(f){return void r(f)}c.done?e(u):Promise.resolve(u).then(n,o)}function n(t){return function(){var e=this,n=arguments;return new Promise(function(o,i){var a=t.apply(e,n);function c(t){r(a,o,i,c,u,"next",t)}function u(t){r(a,o,i,c,u,"throw",t)}c(void 0)})}}function o(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),Object.defineProperty(t,"prototype",{writable:!1}),e&&i(t,e)}function i(t,e){return(i=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,e){return t.__proto__=e,t})(t,e)}function a(t){var e=f();return function(){var r,n=s(t);if(e){var o=s(this).constructor;r=Reflect.construct(n,arguments,o)}else r=n.apply(this,arguments);return c(this,r)}}function c(e,r){if(r&&("object"===t(r)||"function"==typeof r))return r;if(void 0!==r)throw new TypeError("Derived constructors may only return object or undefined");return u(e)}function u(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}function f(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch(t){return!1}}function s(t){return(s=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(t){return t.__proto__||Object.getPrototypeOf(t)})(t)}function l(t,e){for(var r=0;rscreen.availWidth&&!document.body.classList.contains("ignore-landscape-warning"))return e=setInterval(function(){(screen.availHeight0&&t(n.pop())},1e3)},1e4)}else n.push(a)}function a(e){var n=document.createElement("div");n.className="notification-content";var a=document.createElement("p");a.innerHTML=e,n.appendChild(a);var i=document.createElement("i");i.className="alert fas fa-exclamation-triangle",n.appendChild(i),t([i,n])}function i(e){var n=document.createElement("div");n.className="notification-content";var a=document.createElement("p");a.innerHTML=e,n.appendChild(a);var i=document.createElement("i");i.className="warning fas fa-exclamation-triangle",n.appendChild(i),t([i,n])}function c(e){var n=document.createElement("div");n.className="notification-content";var a=document.createElement("p");a.innerHTML=e,n.appendChild(a);var i=document.createElement("i");i.className="info fas fa-info-circle",n.appendChild(i),t([i,n])}function o(e){var n=document.createElement("div");n.className="notification-content";var a=document.createElement("p");a.innerHTML=e,n.appendChild(a);var i=document.createElement("i");i.className="success fas fa-check-circle",n.appendChild(i),t([i,n])} -},{}],"W7Xq":[function(require,module,exports) { -"use strict";function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}function e(){return(e="undefined"!=typeof Reflect&&Reflect.get?Reflect.get.bind():function(t,e,n){var a=i(t,e);if(a){var s=Object.getOwnPropertyDescriptor(a,e);return s.get?s.get.call(arguments.length<3?t:n):s.value}}).apply(this,arguments)}function i(t,e){for(;!Object.prototype.hasOwnProperty.call(t,e)&&null!==(t=l(t)););return t}function n(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),Object.defineProperty(t,"prototype",{writable:!1}),e&&a(t,e)}function a(t,e){return(a=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,e){return t.__proto__=e,t})(t,e)}function s(t){var e=h();return function(){var i,n=l(t);if(e){var a=l(this).constructor;i=Reflect.construct(n,arguments,a)}else i=n.apply(this,arguments);return r(this,i)}}function r(e,i){if(i&&("object"===t(i)||"function"==typeof i))return i;if(void 0!==i)throw new TypeError("Derived constructors may only return object or undefined");return o(e)}function o(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}function h(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch(t){return!1}}function l(t){return(l=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(t){return t.__proto__||Object.getPrototypeOf(t)})(t)}function u(t,e){return f(t)||g(t,e)||d(t,e)||c()}function c(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function d(t,e){if(t){if("string"==typeof t)return y(t,e);var i=Object.prototype.toString.call(t).slice(8,-1);return"Object"===i&&t.constructor&&(i=t.constructor.name),"Map"===i||"Set"===i?Array.from(t):"Arguments"===i||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(i)?y(t,e):void 0}}function y(t,e){(null==e||e>t.length)&&(e=t.length);for(var i=0,n=new Array(e);i1&&void 0!==arguments[1])||arguments[1];["end","cancel"].includes(t.type)&&this.touchCount--;var i="start"==t.type;i!==this.isActive&&0==this.touchCount&&(this.isActive=i,e&&this.gamepad.handleTouchEventCallbacks(this.createTouchEventObject(this.isActive?"touchstart":"touchend"))),"start"==t.type&&this.touchCount++}},{key:"createTouchEventObject",value:function(t){return{id:this.id,action:t,type:this.type}}}]),i}(),L=function(t){n(i,D);var e=s(i);function i(t){var n;v(this,i);var a=Object.assign({keyboardButton:null,altText:null,altTextAlign:"left",shape:"round"},t);return k(o(n=e.call(this,t)),"shape",void 0),k(o(n),"altText",void 0),k(o(n),"altTextAlign",void 0),k(o(n),"type","GamepadButton"),n.keyboardButton=a.keyboardButton,n.shape=a.shape,n.altText=a.altText,n.altTextAlign=a.altTextAlign,n}return b(i,[{key:"init",value:function(){null!==this.keyboardButton&&this.gamepad.registerKeybinding(this.keyboardButton,this)}},{key:"draw",value:function(t){if(this.path=new Path2D,"round"===this.shape)this.path.arc(this.getX(t),this.getY(t),10*this.getScaleY(t),0,4*Math.PI,!0);else if("square"===this.shape){var e=20*this.getScaleY(t);this.path.rect(this.getX(t)-e/2,this.getY(t)-e/2,e,e)}this.isActive?t.fillStyle="rgba(80, 80, 80, 1)":t.fillStyle="rgba(100, 100, 100, 0.8)",t.fill(this.path);var i="".concat(Math.floor((8*this.getScaleY(t)).toString()),"px 'Press Start 2P'");if(t.font=i,t.textBaseline="middle",t.textAlign="center",t.fillStyle="rgba(255, 255, 255, 1)",t.fillText(this.id,this.getX(t),this.getY(t)),null!==this.altText&&this.gamepad.showAltText){t.beginPath(),t.font="".concat(Math.floor((3*this.getScaleY(t)).toString()),"px 'Press Start 2P'"),t.textBaseline="middle",t.textAlign="center",t.fillStyle="rgba(150, 150, 150, 1)";var n=this.getX(t),a=this.getY(t);switch(this.altTextAlign){case"left":n-=13*this.getScaleY(t);break;case"right":n+=13*this.getScaleY(t);break;case"top":a-=13*this.getScaleY(t);break;case"bottom":a+=13*this.getScaleY(t)}t.fillText(this.altText,n,a)}}}]),i}();exports.GamepadButton=L;var K=new WeakMap,q=new WeakMap,W=new WeakMap,G=function(t){n(a,D);var i=s(a);function a(t){var e;v(this,a);var n=Object.assign({lockX:!1,lockY:!1,autoCenter:!0,bindUp:null,bindLeft:null,bindRight:null,bindDown:null},t);return k(o(e=i.call(this,t)),"type","GamepadJoystick"),k(o(e),"mouseX",0),k(o(e),"mouseY",0),k(o(e),"cR",0),k(o(e),"cX",0),k(o(e),"cY",0),w(o(e),K,{writable:!0,value:void 0}),w(o(e),q,{writable:!0,value:void 0}),w(o(e),W,{writable:!0,value:{}}),X(o(e),K,n.lockX),X(o(e),q,n.lockY),e.bindUp=n.bindUp,e.bindLeft=n.bindLeft,e.bindRight=n.bindRight,e.bindDown=n.bindDown,e}return b(a,[{key:"init",value:function(){null!==this.bindUp&&this.gamepad.registerKeybinding(this.bindUp,this),null!==this.bindLeft&&this.gamepad.registerKeybinding(this.bindLeft,this),null!==this.bindRight&&this.gamepad.registerKeybinding(this.bindRight,this),null!==this.bindDown&&this.gamepad.registerKeybinding(this.bindDown,this)}},{key:"isKeyPressed",value:function(t){return null!==t&&Y(this,W).hasOwnProperty(t)&&Y(this,W)[t]>0}},{key:"createTouchEventObject",value:function(t){return{id:this.id,action:t,type:this.type,x:Math.round(this.mouseX/this.cR*100),y:Math.round(this.mouseY/this.cR*100)}}},{key:"setActive",value:function(t){e(l(a.prototype),"setActive",this).call(this,t,!1),t.hasOwnProperty("key")&&(Y(this,W).hasOwnProperty(t.key)||(Y(this,W)[t.key]=0),["start"].includes(t.type)&&Y(this,W)[t.key]++,["end","cancel"].includes(t.type)&&Y(this,W)[t.key]--);var i=this.cR;Y(this,K)||(t.hasOwnProperty("x")&&(this.mouseX=this.cX-this.gamepad.stage.screenToCanvasX(t.x),this.mouseX=Math.min(Math.abs(this.mouseX),i)*Math.sign(this.mouseX),this.mouseX*=-1),this.isKeyPressed(this.bindLeft)&&(this.mouseX=-i),this.isKeyPressed(this.bindRight)&&(this.mouseX=i),this.isKeyPressed(this.bindLeft)&&this.isKeyPressed(this.bindRight)&&(this.mouseX=0),this.isActive||(this.mouseX=0)),Y(this,q)||(t.hasOwnProperty("y")&&(this.mouseY=this.cY-this.gamepad.stage.screenToCanvasY(t.y),this.mouseY=Math.min(Math.abs(this.mouseY),i)*Math.sign(this.mouseY)),this.isKeyPressed(this.bindUp)&&(this.mouseY=i),this.isKeyPressed(this.bindDown)&&(this.mouseY=-i),this.isKeyPressed(this.bindUp)&&this.isKeyPressed(this.bindDown)&&(this.mouseY=0),this.isActive||(this.mouseY=0));var n="touchmove";this.isActive&&1==this.touchCount&&"start"===t.type&&(n="touchstart"),this.isActive||(n="touchend"),this.gamepad.handleTouchEventCallbacks(this.createTouchEventObject(n))}},{key:"draw",value:function(t){this.cX=this.getX(t),this.cY=this.getY(t),this.cR=25*this.getScaleY(t),this.path=new Path2D,this.path.arc(this.cX,this.cY,this.cR,0,4*Math.PI,!0),this.isActive?t.fillStyle="rgba(85, 85, 85, 0.8)":t.fillStyle="rgba(100, 100, 100, 0.8)",t.fill(this.path),t.beginPath(),t.arc(this.cX+this.mouseX,this.cY-this.mouseY,15*this.getScaleY(t),0,4*Math.PI,!0),t.fillStyle="rgba(130, 130, 130, 1)",t.fill()}}]),a}();exports.GamepadJoystick=G;var U=new WeakMap,_=new WeakMap,z=new WeakMap,V=new WeakMap,J=new WeakMap,H=new WeakMap,N=function(){function t(){v(this,t),k(this,"stage",void 0),w(this,U,{writable:!0,value:void 0}),w(this,_,{writable:!0,value:void 0}),w(this,z,{writable:!0,value:{}}),w(this,V,{writable:!0,value:{}}),w(this,J,{writable:!0,value:{}}),w(this,H,{writable:!0,value:[]}),k(this,"showDebug",!1),k(this,"showAltText",!0),k(this,"enableVibration",!0),this.stage=new I("GamePad",document.querySelector(".gamepad-wrapper")),this.addEventListeners()}return b(t,[{key:"addEventListeners",value:function(){var t=this,e=["keydown","keyup"];for(var i in e)document.addEventListener(e[i],function(e){return t.handleKeyEvent(e)},!1);for(var i in e=["touchstart","touchend","touchcancel","touchmove"])this.stage.canvas.addEventListener(e[i],function(e){return t.handleTouchEvent(e)},!1);for(var i in e=["mousedown","mouseup","mousemove"])this.stage.canvas.addEventListener(e[i],function(e){return t.handleMouseEvent(e)},!1)}},{key:"registerKeybinding",value:function(t,e){Y(this,V)[t]=e}},{key:"handleKeyEvent",value:function(t){if(Y(this,J).hasOwnProperty(t.keyCode)||(Y(this,J)[t.keyCode]={pressed:!1}),Y(this,V).hasOwnProperty(t.key)){var e="Key ".concat(t.key),i=Y(this,V)[t.key],n={touchId:e,key:t.key,type:{keydown:"start",keyup:"end"}[t.type]};switch(t.type){case"keydown":if(Y(this,J)[t.keyCode].pressed)return;Y(this,J)[t.keyCode].pressed=!0,Y(this,z)[e]={},Y(this,z)[e].target=i,Y(this,z)[e].hasOwnProperty("target")&&null!=Y(this,z)[e].target&&Y(this,z)[e].target.setActive(n);break;case"keyup":if(!Y(this,J)[t.keyCode].pressed)return;Y(this,J)[t.keyCode].pressed=!1,Y(this,z)[e].hasOwnProperty("target")&&null!=Y(this,z)[e].target&&Y(this,z)[e].target.setActive(n),delete Y(this,z)[e]}}this.stage.touches=Y(this,z),this.debugTouches()}},{key:"handleMouseEvent",value:function(t){this.processGamepadTouchEvent({x:t.clientX,y:t.clientY,touchId:"mouse",type:{mousedown:"start",mouseup:"end",mousemove:"move"}[t.type]})}},{key:"handleTouchEvent",value:function(t){t.preventDefault();for(var e={touchstart:"start",touchend:"end",touchcancel:"end",touchmove:"move"},i=0;ie.length)&&(t=e.length);for(var o=0,n=new Array(t);o screen.availWidth) && (!document.body.classList.contains(\"ignore-landscape-warning\"))){\n waiting_timer = setInterval(() => {\n if( (screen.availHeight < screen.availWidth) || (document.body.classList.contains(\"ignore-landscape-warning\"))){\n clearInterval(waiting_timer);\n waiting_timer = undefined;\n notif(notif_queue.pop());\n }\n }, 1000);\n notif_queue.push(notif_c);\n return;\n }\n\n let notif_elem = document.createElement(\"div\");\n notif_elem.className = \"notification\";\n notif_elem.appendChild(notif_c[0]);\n notif_elem.appendChild(notif_c[1]);\n\n notification_area.appendChild(notif_elem);\n\n notification_area.classList.add(\"show\");\n setTimeout(() => {\n notification_area.classList.remove(\"show\");\n notif_elem.querySelector(\"p\").style.opacity = \"0\";\n setTimeout(() => {\n notification_area.removeChild(notif_elem);\n if (notif_queue.length > 0) {\n notif(notif_queue.pop());\n }\n }, 1000);\n }, 10000);\n } else {\n notif_queue.push(notif_c);\n }\n}\n\nexport function notif_alert(alert_str) {\n let div = document.createElement(\"div\");\n div.className = \"notification-content\";\n\n let text = document.createElement(\"p\");\n text.innerHTML = alert_str;\n div.appendChild(text);\n\n let icon = document.createElement(\"i\");\n icon.className = \"alert fas fa-exclamation-triangle\";\n div.appendChild(icon);\n\n notif([icon, div]);\n}\n\nexport function notif_warn(alert_str) {\n let div = document.createElement(\"div\");\n div.className = \"notification-content\";\n\n let text = document.createElement(\"p\");\n text.innerHTML = alert_str;\n div.appendChild(text);\n\n let icon = document.createElement(\"i\");\n icon.className = \"warning fas fa-exclamation-triangle\";\n div.appendChild(icon);\n\n notif([icon, div]);\n}\n\nexport function notif_info(info_str) {\n let div = document.createElement(\"div\");\n div.className = \"notification-content\";\n\n let text = document.createElement(\"p\");\n text.innerHTML = info_str;\n div.appendChild(text);\n\n let icon = document.createElement(\"i\");\n icon.className = \"info fas fa-info-circle\";\n div.appendChild(icon);\n\n notif([icon, div]);\n}\n\nexport function notif_success(success_str) {\n let div = document.createElement(\"div\");\n div.className = \"notification-content\";\n\n let text = document.createElement(\"p\");\n text.innerHTML = success_str;\n div.appendChild(text);\n\n let icon = document.createElement(\"i\");\n icon.className = \"success fas fa-check-circle\";\n div.appendChild(icon);\n\n notif([icon, div]);\n}\n","class CanvasStage {\n canvas;\n #dpi = window.devicePixelRatio;\n #width;\n #height;\n #ctx;\n #elements = [];\n touches = {};\n showTouches = false;\n\n constructor(id, node) {\n this.canvas = document.createElement(\"canvas\");\n this.canvas.setAttribute(\"id\", id);\n node.appendChild(this.canvas);\n\n addEventListener(\"resize\", () => this.resize());\n this.resize();\n\n console.debug(\"Created canvas\", this.canvas);\n\n setInterval(() => {\n this.drawElements();\n }, 10);\n }\n\n /* Resizes the canvas to be the correct size for the current screen */\n resize() {\n this.#ctx = this.canvas.getContext(\"2d\");\n this.#height = +getComputedStyle(this.canvas).getPropertyValue(\"height\").slice(0, -2);\n this.#width = +getComputedStyle(this.canvas).getPropertyValue(\"width\").slice(0, -2);\n this.canvas.setAttribute('height', this.#height * this.#dpi);\n this.canvas.setAttribute('width', this.#width * this.#dpi);\n }\n\n /* Translate a screen x coordinate to a canvas x coordinate */\n screenToCanvasX(x) { return x * this.#dpi; }\n\n /* Translate a screen y coordinate to a canvas y coordinate */\n screenToCanvasY(y) { return y * this.#dpi; }\n\n /* Get target at position, i.e. the element that intersects said position */\n getTarget(x, y) {\n x *= this.#dpi;\n y *= this.#dpi;\n for (let i = 0; i < this.#elements.length; i++) {\n if ((this.#elements[i] instanceof TouchElement)\n && (this.#elements[i].collides(this.#ctx, x, y))) {\n return this.#elements[i];\n }\n }\n }\n\n /* Redraws all elements of the stage on the screen. */\n drawElements() {\n this.#ctx.clearRect(0, 0, this.#width * this.#dpi, this.#height * this.#dpi);\n for (let i = 0; i < this.#elements.length; i++) {\n this.#elements[i].draw(this.#ctx);\n }\n if (this.showTouches) {\n this.drawTouches();\n }\n }\n\n /* Draws all touches on the screen, used to debug */\n drawTouches(e) {\n const colors = [\"200, 0, 0\", \"0, 200, 0\", \"0, 0, 200\", \"200, 200, 0\", \"200, 200, 200\"]\n for (const [identifier, touch] of Object.entries(this.touches)) {\n this.#ctx.beginPath();\n this.#ctx.arc(touch.x * this.#dpi, touch.y * this.#dpi, 20 * this.#dpi, 0, 2*Math.PI, true);\n this.#ctx.fillStyle = `rgba(${colors[identifier]}, 0.2)`;\n this.#ctx.fill();\n\n this.#ctx.lineWidth = 2.0;\n this.#ctx.strokeStyle = `rgba(${colors[identifier]}, 0.8)`;\n this.#ctx.stroke();\n }\n }\n\n /* Add a element to the stage */\n addElement(element) {\n this.#elements.push(element);\n element.init();\n }\n\n /* Remove a element from the stage by id */\n removeElementById(id) {\n for (let i = 0; i < this.#elements.length; i++) {\n if (id === this.#elements[i].id) {\n this.#elements.splice(i, 1);\n return;\n }\n }\n }\n\n /* Wipe all elements from the stage */\n removeAllElements() {\n this.#elements.splice(0, this.#elements.length);\n }\n\n}\n\nclass Element {\n gamepad;\n id;\n x;\n y;\n alignX;\n alignY;\n path;\n isInside;\n isActive;\n type = \"Element\";\n \n constructor(opts, gamepad) {\n let _opts = Object.assign({\n id: null,\n x: 0,\n y: 0,\n alignX: null,\n alignY: null\n }, opts);\n this.id = _opts.id;\n this.x = _opts.x;\n this.y = _opts.y;\n this.alignX = _opts.alignX;\n this.alignY = _opts.alignY;\n this.gamepad = gamepad;\n }\n\n /* Used for initializing the element onto the stage */\n init() {}\n\n /* Get the x-axis scaling factor (currently unused, only the y scaling factor is in use) */\n getScaleX(ctx) { \n return ctx.canvas.width / 100;\n }\n\n /* Get the y-axis scaling factor */\n getScaleY(ctx) { \n return ctx.canvas.height / 100;\n }\n\n /* Get the canvas x position of this element, adjusted from the virtual canvas coordinates */\n getX(ctx) {\n let x = this.x * this.getScaleY(ctx);\n if (this.alignX === \"center\") {\n x = (ctx.canvas.width / 2) + x;\n }\n if (this.alignX === \"right\") {\n x = ctx.canvas.width - x;\n }\n return x;\n }\n\n /* Get the canvas y position of this element, adjusted from the virtual canvas coordinates */\n getY(ctx) {\n let y = this.y * this.getScaleY(ctx);\n if (this.alignY === \"center\") {\n y = (ctx.canvas.height / 2) + y;\n }\n if (this.alignY === \"bottom\") {\n y = ctx.canvas.height - y;\n }\n return y;\n }\n\n /* Used to draw the element onto a canvas context */\n draw(ctx) {}\n\n /* Used to check wether the coordinates is inside this element */\n collides(ctx, x, y) {\n this.isInside = ctx.isPointInPath(this.path, x, y);\n return this.isInside;\n }\n\n}\n\nexport class Square extends Element {\n draw(ctx) {\n this.path = new Path2D();\n let w = this.getScaleY(ctx) * 20;\n this.path.rect(this.getX(ctx) - (w/2), this.getY(ctx) - (w/2), w, w);\n ctx.fillStyle = `rgba(100, 100, 100, 0.8)`;\n ctx.fill(this.path);\n }\n}\n\nclass TouchElement extends Element {\n type = \"TouchElement\";\n touchCount = 0;\n\n setActive(e, doCallbacks = true) {\n if ([\"end\", \"cancel\"].includes(e.type)) { this.touchCount--; }\n let eState = e.type == \"start\";\n if ((eState !== this.isActive) && (this.touchCount == 0)) {\n this.isActive = eState;\n if (doCallbacks) {\n this.gamepad.handleTouchEventCallbacks(this.createTouchEventObject(\n this.isActive ? \"touchstart\" : \"touchend\"\n ));\n }\n }\n if (e.type == \"start\") { this.touchCount++; }\n }\n\n createTouchEventObject(action) {\n return {\n id: this.id,\n action: action,\n type: this.type\n }\n }\n\n}\n\nexport class GamepadButton extends TouchElement {\n shape;\n altText;\n altTextAlign;\n type = \"GamepadButton\";\n\n constructor(opts) {\n let _opts = Object.assign({\n keyboardButton: null,\n altText: null,\n altTextAlign: \"left\",\n shape: \"round\"\n }, opts);\n super(opts);\n this.keyboardButton = _opts.keyboardButton;\n this.shape = _opts.shape;\n this.altText = _opts.altText;\n this.altTextAlign = _opts.altTextAlign;\n }\n\n init() {\n if (this.keyboardButton !== null) {\n this.gamepad.registerKeybinding(this.keyboardButton, this);\n }\n }\n\n draw(ctx) {\n this.path = new Path2D();\n if (this.shape === \"round\") {\n this.path.arc(this.getX(ctx), this.getY(ctx), this.getScaleY(ctx) * 10, 0, 4*Math.PI, true);\n } else if (this.shape === \"square\") {\n let w = this.getScaleY(ctx) * 20;\n this.path.rect(this.getX(ctx) - (w/2), this.getY(ctx) - (w/2), w, w);\n }\n if (this.isActive) {\n ctx.fillStyle = `rgba(80, 80, 80, 1)`;\n } else {\n ctx.fillStyle = `rgba(100, 100, 100, 0.8)`;\n }\n ctx.fill(this.path);\n\n let s = `${Math.floor((this.getScaleY(ctx)*8).toString())}px 'Press Start 2P'`;\n ctx.font = s;\n ctx.textBaseline = \"middle\";\n ctx.textAlign = \"center\";\n ctx.fillStyle = `rgba(255, 255, 255, 1)`;\n ctx.fillText(this.id, this.getX(ctx), this.getY(ctx));\n\n if ((this.altText !== null) && (this.gamepad.showAltText)) {\n ctx.beginPath();\n ctx.font = `${Math.floor((this.getScaleY(ctx)*3).toString())}px 'Press Start 2P'`;\n ctx.textBaseline = \"middle\";\n ctx.textAlign = \"center\";\n ctx.fillStyle = `rgba(150, 150, 150, 1)`;\n let ax = this.getX(ctx);\n let ay = this.getY(ctx);\n switch (this.altTextAlign) {\n case \"left\":\n ax -= (this.getScaleY(ctx) * 13);\n break;\n case \"right\":\n ax += (this.getScaleY(ctx) * 13);\n break;\n case \"top\":\n ay -= (this.getScaleY(ctx) * 13);\n break;\n case \"bottom\":\n ay += (this.getScaleY(ctx) * 13);\n break;\n }\n ctx.fillText(this.altText, ax, ay);\n }\n }\n\n}\n\nexport class GamepadJoystick extends TouchElement {\n type = \"GamepadJoystick\";\n mouseX = 0;\n mouseY = 0;\n cR = 0;\n cX = 0;\n cY = 0;\n\n #lockX;\n #lockY;\n\n #pressedKeys = {};\n\n constructor(opts) {\n let _opts = Object.assign({\n lockX: false,\n lockY: false,\n autoCenter: true,\n bindUp: null,\n bindLeft: null,\n bindRight: null,\n bindDown: null\n }, opts);\n super(opts);\n this.#lockX = _opts.lockX;\n this.#lockY = _opts.lockY;\n this.bindUp = _opts.bindUp;\n this.bindLeft = _opts.bindLeft;\n this.bindRight = _opts.bindRight;\n this.bindDown = _opts.bindDown;\n }\n\n init() {\n if (this.bindUp !== null) {\n this.gamepad.registerKeybinding(this.bindUp, this);\n }\n if (this.bindLeft !== null) {\n this.gamepad.registerKeybinding(this.bindLeft, this);\n }\n if (this.bindRight !== null) {\n this.gamepad.registerKeybinding(this.bindRight, this);\n }\n if (this.bindDown !== null) {\n this.gamepad.registerKeybinding(this.bindDown, this);\n }\n }\n\n isKeyPressed(key) {\n return ((key !== null)\n && (this.#pressedKeys.hasOwnProperty(key))\n && (this.#pressedKeys[key] > 0));\n }\n\n createTouchEventObject(action) {\n return {\n id: this.id,\n action: action,\n type: this.type,\n x: Math.round((this.mouseX / this.cR) * 100),\n y: Math.round((this.mouseY / this.cR) * 100)\n }\n }\n\n setActive(e) {\n super.setActive(e, false);\n if (e.hasOwnProperty(\"key\")) {\n if (!this.#pressedKeys.hasOwnProperty(e.key)) {\n this.#pressedKeys[e.key] = 0;\n }\n if ([\"start\"].includes(e.type)) {\n this.#pressedKeys[e.key]++;\n }\n if ([\"end\", \"cancel\"].includes(e.type)) {\n this.#pressedKeys[e.key]--;\n }\n }\n\n let max = this.cR\n if (!this.#lockX) {\n if (e.hasOwnProperty(\"x\")) {\n this.mouseX = this.cX - this.gamepad.stage.screenToCanvasX(e.x);\n this.mouseX = Math.min(Math.abs(this.mouseX), max) * Math.sign(this.mouseX); \n this.mouseX *= -1;\n }\n if (this.isKeyPressed(this.bindLeft)) { this.mouseX = -max; } \n if (this.isKeyPressed(this.bindRight)) { this.mouseX = max; }\n if (this.isKeyPressed(this.bindLeft) && this.isKeyPressed(this.bindRight)) { this.mouseX = 0; }\n if (!this.isActive) { this.mouseX = 0; }\n }\n if (!this.#lockY) {\n if (e.hasOwnProperty(\"y\")) {\n this.mouseY = this.cY - this.gamepad.stage.screenToCanvasY(e.y);\n this.mouseY = Math.min(Math.abs(this.mouseY), max) * Math.sign(this.mouseY); \n } \n if (this.isKeyPressed(this.bindUp)) { this.mouseY = max; } \n if (this.isKeyPressed(this.bindDown)) { this.mouseY = -max; }\n if (this.isKeyPressed(this.bindUp) && this.isKeyPressed(this.bindDown)) { this.mouseY = 0; }\n if (!this.isActive) { this.mouseY = 0; }\n }\n\n let action = \"touchmove\";\n if (this.isActive && (this.touchCount == 1) && (e.type === \"start\")) {\n action = \"touchstart\";\n }\n if (!this.isActive) {\n action = \"touchend\";\n }\n this.gamepad.handleTouchEventCallbacks(this.createTouchEventObject(action));\n }\n\n draw(ctx) {\n this.cX = this.getX(ctx);\n this.cY = this.getY(ctx);\n this.cR = this.getScaleY(ctx) * 25;\n\n this.path = new Path2D();\n this.path.arc(this.cX, this.cY, this.cR, 0, 4*Math.PI, true);\n if (this.isActive) {\n ctx.fillStyle = `rgba(85, 85, 85, 0.8)`;\n } else {\n ctx.fillStyle = `rgba(100, 100, 100, 0.8)`;\n }\n ctx.fill(this.path);\n\n ctx.beginPath();\n ctx.arc(this.cX + this.mouseX, this.cY - this.mouseY, this.getScaleY(ctx) * 15, 0, 4*Math.PI, true);\n ctx.fillStyle = `rgba(130, 130, 130, 1)`;\n ctx.fill();\n\n }\n\n}\n\nexport class Gamepad {\n stage;\n #width;\n #height;\n\n #touches = {};\n #keybindings = {};\n #keystates = {};\n #touchEventCallbacks = [];\n\n showDebug = false;\n showAltText = true;\n enableVibration = true;\n\n constructor() {\n this.stage = new CanvasStage(\"GamePad\", document.querySelector(\".gamepad-wrapper\"));\n this.addEventListeners();\n }\n\n addEventListeners() {\n let ev = [\"keydown\", \"keyup\"];\n for(var e in ev) {\n document.addEventListener(ev[e], (e) => this.handleKeyEvent(e), false);\n }\n ev = [\"touchstart\", \"touchend\", \"touchcancel\", \"touchmove\"];\n for(var e in ev) {\n this.stage.canvas.addEventListener(ev[e], (e) => this.handleTouchEvent(e), false);\n }\n ev = [\"mousedown\", \"mouseup\", \"mousemove\"];\n for(var e in ev) {\n this.stage.canvas.addEventListener(ev[e], (e) => this.handleMouseEvent(e), false);\n }\n }\n\n /* Used by stage elements to register themselves with some keybinding */\n registerKeybinding(binding, element) {\n this.#keybindings[binding] = element;\n }\n\n /* Event handler for keyboard events */\n handleKeyEvent(e) {\n const typedict = {\"keydown\": \"start\", \"keyup\": \"end\"}\n if (!this.#keystates.hasOwnProperty(e.keyCode)) {\n this.#keystates[e.keyCode] = {pressed: false};\n }\n if (this.#keybindings.hasOwnProperty(e.key)) {\n let id = `Key ${e.key}`\n let target = this.#keybindings[e.key];\n let gtEvent = {\n touchId: id,\n key: e.key,\n type: typedict[e.type]\n };\n switch (e.type) {\n case \"keydown\":\n if (this.#keystates[e.keyCode].pressed) { return; }\n this.#keystates[e.keyCode].pressed = true;\n\n this.#touches[id] = {};\n this.#touches[id].target = target;\n if (this.#touches[id].hasOwnProperty(\"target\")\n && this.#touches[id].target != null) {\n this.#touches[id].target.setActive(gtEvent);\n }\n break;\n case \"keyup\":\n if (!this.#keystates[e.keyCode].pressed) { return; }\n this.#keystates[e.keyCode].pressed = false;\n\n if (this.#touches[id].hasOwnProperty(\"target\")\n && this.#touches[id].target != null) {\n this.#touches[id].target.setActive(gtEvent);\n }\n delete this.#touches[id];\n break;\n }\n }\n this.stage.touches = this.#touches;\n this.debugTouches();\n }\n\n /* Event handler for mouse events, will just translate the event to a more common form\n * before further processing. */\n handleMouseEvent(e) {\n const typedict = {\"mousedown\": \"start\", \"mouseup\": \"end\", \"mousemove\": \"move\"}\n this.processGamepadTouchEvent({\n x: e.clientX,\n y: e.clientY,\n touchId: \"mouse\",\n type: typedict[e.type]\n });\n }\n\n /* Event handler for touch events, will just translate the event to a more common form\n * before further processing. */\n handleTouchEvent(e) {\n e.preventDefault();\n const typedict = {\"touchstart\": \"start\", \"touchend\": \"end\", \"touchcancel\": \"end\", \"touchmove\": \"move\"}\n for (let i = 0; i < e.changedTouches.length; i++) {\n let touch = e.changedTouches[i];\n this.processGamepadTouchEvent({\n x: touch.clientX,\n y: touch.clientY,\n touchId: touch.identifier,\n type: typedict[e.type]\n });\n }\n }\n\n /* Event handler for processing standarized touch/mouse events. */\n processGamepadTouchEvent(gtEvent) {\n let target = this.stage.getTarget(gtEvent.x, gtEvent.y)\n switch (gtEvent.type) {\n case \"start\":\n this.#touches[gtEvent.touchId] = {};\n this.#touches[gtEvent.touchId].target = target;\n case \"move\":\n if (this.#touches.hasOwnProperty(gtEvent.touchId)) {\n this.#touches[gtEvent.touchId].x = gtEvent.x;\n this.#touches[gtEvent.touchId].y = gtEvent.y;\n\n if (this.#touches[gtEvent.touchId].hasOwnProperty(\"target\")\n && this.#touches[gtEvent.touchId].target != null) {\n this.#touches[gtEvent.touchId].target.setActive(gtEvent);\n }\n }\n break;\n\n case \"end\":\n case \"cancel\":\n if (this.#touches[gtEvent.touchId].hasOwnProperty(\"target\")\n && this.#touches[gtEvent.touchId].target != null) {\n this.#touches[gtEvent.touchId].target.setActive(gtEvent);\n }\n delete this.#touches[gtEvent.touchId];\n break;\n\n default:\n console.log(\"Unknown touch event\", gtEvent.type);\n }\n this.stage.touches = this.#touches;\n this.debugTouches();\n }\n\n /* Update the debug text with all current touches */\n debugTouches() {\n let s = \"\";\n if (this.showDebug) {\n for (const [i, t] of Object.entries(this.#touches)) {\n s += `[${i}] `\n if (t.hasOwnProperty(\"x\")) {\n s += `x: ${Math.round(t.x, 2)}, y: ${Math.round(t.y)},`\n }\n s += `target: ${t.target ? t.target.id : null}\\n`;\n }\n }\n document.querySelector(\".gamepad-touches\").innerHTML = s;\n }\n\n /* Used by elements to process callbacks on actions to outside the gamepad */\n handleTouchEventCallbacks(e) {\n if (this.enableVibration && [\"touchstart\", \"touchend\"].includes(e.action)) {\n try {\n window.navigator.vibrate(5);\n } catch (e) {\n console.error(e);\n }\n }\n for (let i = 0; i < this.#touchEventCallbacks.length; i++) {\n this.#touchEventCallbacks[i](e);\n }\n }\n\n /* Register a method as a callback for gamepad touch events */\n onTouchEvent(callback) {\n this.#touchEventCallbacks.push(callback);\n }\n\n /* Add a list of elements to the gamepad stage */\n addElements(elements) {\n for (let i = 0; i < elements.length; i++) {\n elements[i].gamepad = this;\n this.stage.addElement(elements[i]);\n }\n }\n\n /* Remove a list of elements from the gamepad stage by id */\n removeElementsById(elementIds) {\n for (let i = 0; i < elementIds.length; i++) {\n this.stage.removeElementById(elementIds[i]);\n }\n }\n\n /* Remove all elements from the gamepad stage */\n removeAllElements() {\n this.stage.removeAllElements();\n }\n\n /* Initialize gamepad with a predefined layout */\n setGamepadLayout(variant) {\n console.debug(`Setting the gamepad layout to ${variant}, deleting all current elements.`);\n this.removeAllElements();\n switch (variant) {\n case \"1\":\n this.addElements([\n new Square({id: \"filler1\", x: 40, y: 0, alignX: \"left\", alignY: \"center\"}),\n new GamepadButton({id: \"C\", x: 20, y: 0, alignX: \"left\", alignY: \"center\", shape: \"square\", keyboardButton: \"ArrowLeft\", altText: \"◀\", altTextAlign: \"right\"}),\n new GamepadButton({id: \"D\", x: 60, y: 0, alignX: \"left\", alignY: \"center\", shape: \"square\", keyboardButton: \"ArrowRight\",altText: \"▶\", altTextAlign: \"left\"}),\n new GamepadButton({id: \"A\", x: 40, y: -20, alignX: \"left\", alignY: \"center\", shape: \"square\", keyboardButton: \"ArrowUp\", altText: \"▲\", altTextAlign: \"bottom\"}),\n new GamepadButton({id: \"B\", x: 40, y: 20, alignX: \"left\", alignY: \"center\", shape: \"square\", keyboardButton: \"ArrowDown\", altText: \"▼\", altTextAlign: \"top\"}),\n new GamepadButton({id: \"3\", x: 20, y: 0, alignX: \"right\", alignY: \"center\", shape: \"round\", keyboardButton: \"3\", altText: \"3\", altTextAlign: \"left\"}),\n new GamepadButton({id: \"4\", x: 60, y: 0, alignX: \"right\", alignY: \"center\", shape: \"round\", keyboardButton: \"4\", altText: \"4\", altTextAlign: \"right\"}),\n new GamepadButton({id: \"1\", x: 40, y: -20, alignX: \"right\", alignY: \"center\", shape: \"round\", keyboardButton: \"1\", altText: \"1\", altTextAlign: \"bottom\"}),\n new GamepadButton({id: \"2\", x: 40, y: 20, alignX: \"right\", alignY: \"center\", shape: \"round\", keyboardButton: \"2\", altText: \"2\", altTextAlign: \"top\"}),\n ])\n break;\n case \"2\":\n this.addElements([\n new Square({id: \"filler2\", x: 40, y: 0, alignX: \"right\", alignY: \"center\"}),\n new Square({id: \"filler1\", x: 40, y: 0, alignX: \"left\", alignY: \"center\"}),\n new GamepadButton({id: \"C\", x: 20, y: 0, alignX: \"left\", alignY: \"center\", shape: \"square\", keyboardButton: \"ArrowLeft\", altText: \"◀\", altTextAlign: \"right\"}),\n new GamepadButton({id: \"D\", x: 60, y: 0, alignX: \"left\", alignY: \"center\", shape: \"square\", keyboardButton: \"ArrowRight\",altText: \"▶\", altTextAlign: \"left\"}),\n new GamepadButton({id: \"A\", x: 40, y: -20, alignX: \"left\", alignY: \"center\", shape: \"square\", keyboardButton: \"ArrowUp\", altText: \"▲\", altTextAlign: \"bottom\"}),\n new GamepadButton({id: \"B\", x: 40, y: 20, alignX: \"left\", alignY: \"center\", shape: \"square\", keyboardButton: \"ArrowDown\", altText: \"▼\", altTextAlign: \"top\"}),\n new GamepadButton({id: \"3\", x: 20, y: 0, alignX: \"right\", alignY: \"center\", shape: \"square\", keyboardButton: \"3\"}),\n new GamepadButton({id: \"4\", x: 60, y: 0, alignX: \"right\", alignY: \"center\", shape: \"square\", keyboardButton: \"4\"}),\n new GamepadButton({id: \"1\", x: 40, y: -20, alignX: \"right\", alignY: \"center\", shape: \"square\", keyboardButton: \"1\"}),\n new GamepadButton({id: \"2\", x: 40, y: 20, alignX: \"right\", alignY: \"center\", shape: \"square\", keyboardButton: \"2\"}),\n ])\n break;\n case \"9\":\n this.addElements([\n new GamepadJoystick({id: \"left\", x: 40, y: 0, alignX: \"left\", alignY: \"center\", lockX: true, bindUp: \"ArrowUp\", bindDown: \"ArrowDown\"}),\n new GamepadJoystick({id: \"right\", x: 40, y: 0, alignX: \"right\", alignY: \"center\", lockY: true, bindLeft: \"ArrowLeft\", bindRight: \"ArrowRight\"})\n ]);\n break;\n }\n }\n\n}\n","import { uBitBLE, MESEvents } from \"./uBit\";\nimport { notif_alert, notif_warn, notif_info, notif_success } from './notification';\nimport { Gamepad } from './gamepad';\n\n/* Attempt to install service worker */\nlet sw = \"service-worker.js\";\nif (navigator.serviceWorker) {\n navigator.serviceWorker.register(\n sw, {scope: '/microbit-gamepad/'}\n ).then(registration => {\n registration.onupdatefound = () => {\n const installingWorker = registration.installing;\n if (installingWorker == null) { return; }\n installingWorker.onstatechange = () => {\n if (installingWorker.state === \"installed\") {\n if (navigator.serviceWorker.controller) {\n notif_info(\"New content is available, relaunch the app to install it.\");\n } else {\n notif_success(\"Content is cached for offline use.\");\n }\n }\n };\n };\n registration.update();\n }).catch(error => {\n notif_warn(\"Could not install service worker...\");\n console.error(\"Error during service worker registration:\", error);\n });\n}\n\n/* Allow the ignore-landscape-warning button to work */\ndocument.getElementById(\"btn_ignore_landscape_warning\").addEventListener(\"click\", () => {\n document.body.classList.add(\"ignore-landscape-warning\");\n});\n\n/* Show a warning if bluetooth is unavailable in the browser. */\nif (!navigator.bluetooth) {\n //alert(\"Bluetooth not enabled in your browser, this won't work...\");\n console.error(\"You do not have a bluetooth enabled browser, you need to have a bluetooth enabled browser...\");\n notif_alert(\"Your browser does not seem to support bluetooth, try using Google Chrome or Microsoft Edge.\");\n}\n\n/* Define and initialize things */\nlet gamepad = new Gamepad();\nwindow.gamepad = gamepad;\nlet ubit = new uBitBLE();\nwindow.ubit = ubit;\n\n/* Setup storage and picker for the gamepad layout */\ndocument.querySelector(\".settings-dialog #layout\").addEventListener(\"change\", (v) => {\n gamepad.setGamepadLayout(v.target.value);\n localStorage.setItem(\"gamepadLayout\", v.target.value);\n document.querySelector(\".button-states pre\").innerHTML = \"No buttons pressed yet\";\n});\nif (localStorage.getItem(\"gamepadLayout\") === null) { localStorage.setItem(\"gamepadLayout\", \"1\"); }\ngamepad.setGamepadLayout(localStorage.getItem(\"gamepadLayout\"));\ndocument.querySelector(\".button-states pre\").innerHTML = \"No buttons pressed yet\";\ndocument.querySelector(\".settings-dialog #layout\").value = localStorage.getItem(\"gamepadLayout\");\n\n/* Setup storage for toggling touches */\ndocument.querySelector(\".settings-dialog #show-touches\").addEventListener(\"change\", (v) => {\n gamepad.stage.showTouches = v.target.checked;\n localStorage.setItem(\"showTouches\", v.target.checked);\n});\nif (localStorage.getItem(\"showTouches\") === null) { localStorage.setItem(\"showTouches\", false); }\ngamepad.stage.showTouches = localStorage.getItem(\"showTouches\") == \"true\";\ndocument.querySelector(\".settings-dialog #show-touches\").checked = localStorage.getItem(\"showTouches\") == \"true\";\n\n/* Setup storage for toggling alt text */\ndocument.querySelector(\".settings-dialog #show-gamepad-alt-text\").addEventListener(\"change\", (v) => {\n gamepad.showAltText = v.target.checked;\n localStorage.setItem(\"showAltText\", v.target.checked);\n});\nif (localStorage.getItem(\"showAltText\") === null) { localStorage.setItem(\"showAltText\", false); }\ngamepad.showAltText = localStorage.getItem(\"showAltText\") == \"true\";\ndocument.querySelector(\".settings-dialog #show-gamepad-alt-text\").checked = localStorage.getItem(\"showAltText\") == \"true\";\n\n/* Setup storage for toggling vibration/haptic feedback */\ndocument.querySelector(\".settings-dialog #enable-haptic\").addEventListener(\"change\", (v) => {\n gamepad.enableVibration = v.target.checked;\n localStorage.setItem(\"enableHaptic\", v.target.checked);\n});\nif (localStorage.getItem(\"enableHaptic\") === null) { localStorage.setItem(\"enableHaptic\", true); }\ngamepad.enableVibration = localStorage.getItem(\"enableHaptic\") == \"true\";\ndocument.querySelector(\".settings-dialog #enable-haptic\").checked = localStorage.getItem(\"enableHaptic\") == \"true\";\n\n/* Setup storage for toggling debug mode */\ndocument.querySelector(\".settings-dialog #enable-debug\").addEventListener(\"change\", (v) => {\n gamepad.showDebug = v.target.checked;\n if (v.target.checked) {\n document.body.classList.add(\"debug\");\n } else {\n document.body.classList.remove(\"debug\");\n }\n localStorage.setItem(\"enableDebug\", v.target.checked);\n});\nif (localStorage.getItem(\"enableDebug\") === null) { localStorage.setItem(\"enableDebug\", false); }\ngamepad.showDebug = localStorage.getItem(\"enableDebug\") == \"true\";\nif (localStorage.getItem(\"enableDebug\") === \"true\") {\n document.body.classList.add(\"debug\");\n} else {\n document.body.classList.remove(\"debug\");\n}\ndocument.querySelector(\".settings-dialog #enable-debug\").checked = localStorage.getItem(\"enableDebug\") == \"true\";\n\n/* Setup buttons for opening/closing settings panel */\ndocument.querySelector(\"#btn_show_settings\").addEventListener(\"click\", () => {\n document.querySelector(\".settings-dialog\").classList.add(\"shown\");\n});\ndocument.querySelector(\"#btn_hide_settings\").addEventListener(\"click\", () => {\n document.querySelector(\".settings-dialog\").classList.remove(\"shown\");\n});\n\n/* Setup actions for bluetooth connect/disconnect buttons */\ndocument.querySelector(\"#btn_disconnect\").addEventListener(\"click\", () => {\n ubit.disconnect();\n});\ndocument.getElementById(\"btn_connect\").addEventListener(\"click\", () => {\n if (!navigator.bluetooth) {\n notif_alert(\"You need a bluetooth enabled browser for this app to work, try chrome.\");\n }\n ubit.searchDevice();\n});\n\n/* Handle gamepad events */\nlet gamepadState = {};\ngamepad.onTouchEvent(e => {\n /* This is just for the debug data */\n if ([\"touchstart\", \"touchmove\"].includes(e.action)) {\n gamepadState[e.id] = {state: true, ...e};\n }\n if ([\"touchend\"].includes(e.action)) {\n gamepadState[e.id] = {state: false, ...e};\n }\n let debugString = \"\";\n for (const [key, value] of Object.entries(gamepadState)) {\n debugString += `${key}: ${value.state ? 'Pressed' : 'Not pressed'}`;\n if (value.hasOwnProperty(\"x\")) {\n debugString += ` (x: ${value.x}, y: ${value.y})`;\n }\n debugString += `\\n`;\n }\n document.querySelector(\".button-states pre\").innerHTML = debugString;\n});\n\ngamepad.onTouchEvent(e => {\n const event_type = MESEvents.MES_DPAD_CONTROLLER_ID;\n let event_value = null;\n if (e.action == \"touchstart\") {\n if (e.id == \"A\") {\n event_value = MESEvents.MES_DPAD_BUTTON_A_DOWN;\n } else if (e.id == \"B\") {\n event_value = MESEvents.MES_DPAD_BUTTON_B_DOWN;\n } else if (e.id == \"C\") {\n event_value = MESEvents.MES_DPAD_BUTTON_C_DOWN;\n } else if (e.id == \"D\") {\n event_value = MESEvents.MES_DPAD_BUTTON_D_DOWN;\n } else if (e.id == \"1\") {\n event_value = MESEvents.MES_DPAD_BUTTON_1_DOWN;\n } else if (e.id == \"2\") {\n event_value = MESEvents.MES_DPAD_BUTTON_2_DOWN;\n } else if (e.id == \"3\") {\n event_value = MESEvents.MES_DPAD_BUTTON_3_DOWN;\n } else if (e.id == \"4\") {\n event_value = MESEvents.MES_DPAD_BUTTON_4_DOWN;\n }\n } else if (e.action == \"touchend\") {\n if (e.id == \"A\") {\n event_value = MESEvents.MES_DPAD_BUTTON_A_UP;\n } else if (e.id == \"B\") {\n event_value = MESEvents.MES_DPAD_BUTTON_B_UP;\n } else if (e.id == \"C\") {\n event_value = MESEvents.MES_DPAD_BUTTON_C_UP;\n } else if (e.id == \"D\") {\n event_value = MESEvents.MES_DPAD_BUTTON_D_UP;\n } else if (e.id == \"1\") {\n event_value = MESEvents.MES_DPAD_BUTTON_1_UP;\n } else if (e.id == \"2\") {\n event_value = MESEvents.MES_DPAD_BUTTON_2_UP;\n } else if (e.id == \"3\") {\n event_value = MESEvents.MES_DPAD_BUTTON_3_UP;\n } else if (e.id == \"4\") {\n event_value = MESEvents.MES_DPAD_BUTTON_4_UP;\n }\n }\n if ((ubit.isConnected()) && (event_value != null)) {\n ubit.eventService.sendEvent(event_type, event_value);\n }\n});\n\n/* Setup handlers for ubit (bluetooth) events */\nubit.onConnect(() => {\n document.body.classList.add(\"connected\");\n});\n\nubit.onDisconnect(() => {\n document.body.classList.remove(\"connected\");\n});\n\n"]} \ No newline at end of file diff --git a/main.fa7f93a2.js b/main.fa7f93a2.js new file mode 100644 index 0000000..74b04a1 --- /dev/null +++ b/main.fa7f93a2.js @@ -0,0 +1,10 @@ +parcelRequire=function(e,r,t,n){var i,o="function"==typeof parcelRequire&&parcelRequire,u="function"==typeof require&&require;function f(t,n){if(!r[t]){if(!e[t]){var i="function"==typeof parcelRequire&&parcelRequire;if(!n&&i)return i(t,!0);if(o)return o(t,!0);if(u&&"string"==typeof t)return u(t);var c=new Error("Cannot find module '"+t+"'");throw c.code="MODULE_NOT_FOUND",c}p.resolve=function(r){return e[t][1][r]||r},p.cache={};var l=r[t]=new f.Module(t);e[t][0].call(l.exports,p,l,l.exports,this)}return r[t].exports;function p(e){return f(p.resolve(e))}}f.isParcelRequire=!0,f.Module=function(e){this.id=e,this.bundle=f,this.exports={}},f.modules=e,f.cache=r,f.parent=o,f.register=function(r,t){e[r]=[function(e,r){r.exports=t},{}]};for(var c=0;cscreen.availWidth&&!document.body.classList.contains("ignore-landscape-warning"))return e=setInterval(function(){(screen.availHeight0&&t(n.pop())},1e3)},1e4)}else n.push(a)}function a(e){var n=document.createElement("div");n.className="notification-content";var a=document.createElement("p");a.innerHTML=e,n.appendChild(a);var i=document.createElement("i");i.className="alert fas fa-exclamation-triangle",n.appendChild(i),t([i,n])}function i(e){var n=document.createElement("div");n.className="notification-content";var a=document.createElement("p");a.innerHTML=e,n.appendChild(a);var i=document.createElement("i");i.className="warning fas fa-exclamation-triangle",n.appendChild(i),t([i,n])}function c(e){var n=document.createElement("div");n.className="notification-content";var a=document.createElement("p");a.innerHTML=e,n.appendChild(a);var i=document.createElement("i");i.className="info fas fa-info-circle",n.appendChild(i),t([i,n])}function o(e){var n=document.createElement("div");n.className="notification-content";var a=document.createElement("p");a.innerHTML=e,n.appendChild(a);var i=document.createElement("i");i.className="success fas fa-check-circle",n.appendChild(i),t([i,n])} +},{}],"COPm":[function(require,module,exports) { +"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.uBitBLE=exports.MESEvents=void 0;var e=require("./notification");function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function n(e,t){r(e,t),t.add(e)}function r(e,t){if(t.has(e))throw new TypeError("Cannot initialize the same private elements twice on an object")}function o(e,t,n){if(!t.has(e))throw new TypeError("attempted to get private field on non-instance");return n}function i(){i=function(){return e};var e={},n=Object.prototype,r=n.hasOwnProperty,o=Object.defineProperty||function(e,t,n){e[t]=n.value},a="function"==typeof Symbol?Symbol:{},c=a.iterator||"@@iterator",u=a.asyncIterator||"@@asyncIterator",s=a.toStringTag||"@@toStringTag";function l(e,t,n){return Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}),e[t]}try{l({},"")}catch(C){l=function(e,t,n){return e[t]=n}}function f(e,t,n,r){var i=t&&t.prototype instanceof d?t:d,a=Object.create(i.prototype),c=new T(r||[]);return o(a,"_invoke",{value:S(e,n,c)}),a}function v(e,t,n){try{return{type:"normal",arg:e.call(t,n)}}catch(C){return{type:"throw",arg:C}}}e.wrap=f;var h={};function d(){}function p(){}function y(){}var g={};l(g,c,function(){return this});var b=Object.getPrototypeOf,_=b&&b(b(P([])));_&&_!==n&&r.call(_,c)&&(g=_);var w=y.prototype=d.prototype=Object.create(g);function m(e){["next","throw","return"].forEach(function(t){l(e,t,function(e){return this._invoke(t,e)})})}function E(e,n){var i;o(this,"_invoke",{value:function(o,a){function c(){return new n(function(i,c){!function o(i,a,c,u){var s=v(e[i],e,a);if("throw"!==s.type){var l=s.arg,f=l.value;return f&&"object"==t(f)&&r.call(f,"__await")?n.resolve(f.__await).then(function(e){o("next",e,c,u)},function(e){o("throw",e,c,u)}):n.resolve(f).then(function(e){l.value=e,c(l)},function(e){return o("throw",e,c,u)})}u(s.arg)}(o,a,i,c)})}return i=i?i.then(c,c):c()}})}function S(e,t,n){var r="suspendedStart";return function(o,i){if("executing"===r)throw new Error("Generator is already running");if("completed"===r){if("throw"===o)throw i;return U()}for(n.method=o,n.arg=i;;){var a=n.delegate;if(a){var c=x(a,n);if(c){if(c===h)continue;return c}}if("next"===n.method)n.sent=n._sent=n.arg;else if("throw"===n.method){if("suspendedStart"===r)throw r="completed",n.arg;n.dispatchException(n.arg)}else"return"===n.method&&n.abrupt("return",n.arg);r="executing";var u=v(e,t,n);if("normal"===u.type){if(r=n.done?"completed":"suspendedYield",u.arg===h)continue;return{value:u.arg,done:n.done}}"throw"===u.type&&(r="completed",n.method="throw",n.arg=u.arg)}}}function x(e,t){var n=t.method,r=e.iterator[n];if(void 0===r)return t.delegate=null,"throw"===n&&e.iterator.return&&(t.method="return",t.arg=void 0,x(e,t),"throw"===t.method)||"return"!==n&&(t.method="throw",t.arg=new TypeError("The iterator does not provide a '"+n+"' method")),h;var o=v(r,e.iterator,t.arg);if("throw"===o.type)return t.method="throw",t.arg=o.arg,t.delegate=null,h;var i=o.arg;return i?i.done?(t[e.resultName]=i.value,t.next=e.nextLoc,"return"!==t.method&&(t.method="next",t.arg=void 0),t.delegate=null,h):i:(t.method="throw",t.arg=new TypeError("iterator result is not an object"),t.delegate=null,h)}function D(e){var t={tryLoc:e[0]};1 in e&&(t.catchLoc=e[1]),2 in e&&(t.finallyLoc=e[2],t.afterLoc=e[3]),this.tryEntries.push(t)}function O(e){var t=e.completion||{};t.type="normal",delete t.arg,e.completion=t}function T(e){this.tryEntries=[{tryLoc:"root"}],e.forEach(D,this),this.reset(!0)}function P(e){if(e){var t=e[c];if(t)return t.call(e);if("function"==typeof e.next)return e;if(!isNaN(e.length)){var n=-1,o=function t(){for(;++n=0;--o){var i=this.tryEntries[o],a=i.completion;if("root"===i.tryLoc)return n("end");if(i.tryLoc<=this.prev){var c=r.call(i,"catchLoc"),u=r.call(i,"finallyLoc");if(c&&u){if(this.prev=0;--n){var o=this.tryEntries[n];if(o.tryLoc<=this.prev&&r.call(o,"finallyLoc")&&this.prev=0;--t){var n=this.tryEntries[t];if(n.finallyLoc===e)return this.complete(n.completion,n.afterLoc),O(n),h}},catch:function(e){for(var t=this.tryEntries.length-1;t>=0;--t){var n=this.tryEntries[t];if(n.tryLoc===e){var r=n.completion;if("throw"===r.type){var o=r.arg;O(n)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(e,t,n){return this.delegate={iterator:P(e),resultName:t,nextLoc:n},"next"===this.method&&(this.arg=void 0),h}},e}function a(e,t,n,r,o,i,a){try{var c=e[i](a),u=c.value}catch(s){return void n(s)}c.done?t(u):Promise.resolve(u).then(r,o)}function c(e){return function(){var t=this,n=arguments;return new Promise(function(r,o){var i=e.apply(t,n);function c(e){a(i,r,o,c,u,"next",e)}function u(e){a(i,r,o,c,u,"throw",e)}c(void 0)})}}function u(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),Object.defineProperty(e,"prototype",{writable:!1}),t&&s(e,t)}function s(e,t){return(s=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e})(e,t)}function l(e){var t=h();return function(){var n,r=d(e);if(t){var o=d(this).constructor;n=Reflect.construct(r,arguments,o)}else n=r.apply(this,arguments);return f(this,n)}}function f(e,n){if(n&&("object"===t(n)||"function"==typeof n))return n;if(void 0!==n)throw new TypeError("Derived constructors may only return object or undefined");return v(e)}function v(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function h(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch(e){return!1}}function d(e){return(d=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function p(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function y(e,t){for(var n=0;n1&&void 0!==o[1])||o[1],r=new TextEncoder,e.prev=2,e.next=5,this.uartRx.writeValue(r.encode(t));case 5:e.next=10;break;case 7:e.prev=7,e.t0=e.catch(2),n||console.error(e.t0);case 10:case"end":return e.stop()}},e,this,[[2,7]])}));return function(t){return e.apply(this,arguments)}}()},{key:"onUartTx",value:function(e){this.handlers.push(e)}}],[{key:"getService",value:function(){var e=c(i().mark(function e(t){var n,a,c,u=this;return i().wrap(function(e){for(;;)switch(e.prev=e.next){case 0:return console.debug("Getting UartService"),e.next=3,t.getPrimaryService(r.SERVICE_UUID);case 3:return n=e.sent,console.debug("Getting Uart characteristics"),e.next=7,n.getCharacteristic(E);case 7:return a=e.sent,e.next=10,a.startNotifications();case 10:return e.next=12,a.addEventListener("characteristicvaluechanged",function(e){o(u,N,A).call(u,e)});case 12:return e.next=14,n.getCharacteristic(S);case 14:return c=e.sent,e.abrupt("return",new r(n,a,c));case 16:case"end":return e.stop()}},e)}));return function(t){return e.apply(this,arguments)}}()}]),r}();function A(e){for(var t=0;tt.length)&&(e=t.length);for(var i=0,n=new Array(e);i1&&void 0!==arguments[1])||arguments[1];["end","cancel"].includes(t.type)&&this.touchCount--;var i="start"==t.type;i!==this.isActive&&0==this.touchCount&&(this.isActive=i,e&&this.gamepad.handleTouchEventCallbacks(this.createTouchEventObject(this.isActive?"touchstart":"touchend"))),"start"==t.type&&this.touchCount++}},{key:"createTouchEventObject",value:function(t){return{id:this.id,action:t,type:this.type}}}]),i}(),L=function(t){n(i,D);var e=s(i);function i(t){var n;v(this,i);var a=Object.assign({keyboardButton:null,altText:null,altTextAlign:"left",shape:"round"},t);return k(o(n=e.call(this,t)),"shape",void 0),k(o(n),"altText",void 0),k(o(n),"altTextAlign",void 0),k(o(n),"type","GamepadButton"),n.keyboardButton=a.keyboardButton,n.shape=a.shape,n.altText=a.altText,n.altTextAlign=a.altTextAlign,n}return b(i,[{key:"init",value:function(){null!==this.keyboardButton&&this.gamepad.registerKeybinding(this.keyboardButton,this)}},{key:"draw",value:function(t){if(this.path=new Path2D,"round"===this.shape)this.path.arc(this.getX(t),this.getY(t),10*this.getScaleY(t),0,4*Math.PI,!0);else if("square"===this.shape){var e=20*this.getScaleY(t);this.path.rect(this.getX(t)-e/2,this.getY(t)-e/2,e,e)}this.isActive?t.fillStyle="rgba(80, 80, 80, 1)":t.fillStyle="rgba(100, 100, 100, 0.8)",t.fill(this.path);var i="".concat(Math.floor((8*this.getScaleY(t)).toString()),"px 'Press Start 2P'");if(t.font=i,t.textBaseline="middle",t.textAlign="center",t.fillStyle="rgba(255, 255, 255, 1)",t.fillText(this.id,this.getX(t),this.getY(t)),null!==this.altText&&this.gamepad.showAltText){t.beginPath(),t.font="".concat(Math.floor((3*this.getScaleY(t)).toString()),"px 'Press Start 2P'"),t.textBaseline="middle",t.textAlign="center",t.fillStyle="rgba(150, 150, 150, 1)";var n=this.getX(t),a=this.getY(t);switch(this.altTextAlign){case"left":n-=13*this.getScaleY(t);break;case"right":n+=13*this.getScaleY(t);break;case"top":a-=13*this.getScaleY(t);break;case"bottom":a+=13*this.getScaleY(t)}t.fillText(this.altText,n,a)}}}]),i}();exports.GamepadButton=L;var K=new WeakMap,q=new WeakMap,W=new WeakMap,G=function(t){n(a,D);var i=s(a);function a(t){var e;v(this,a);var n=Object.assign({lockX:!1,lockY:!1,autoCenter:!0,bindUp:null,bindLeft:null,bindRight:null,bindDown:null},t);return k(o(e=i.call(this,t)),"type","GamepadJoystick"),k(o(e),"mouseX",0),k(o(e),"mouseY",0),k(o(e),"cR",0),k(o(e),"cX",0),k(o(e),"cY",0),w(o(e),K,{writable:!0,value:void 0}),w(o(e),q,{writable:!0,value:void 0}),w(o(e),W,{writable:!0,value:{}}),X(o(e),K,n.lockX),X(o(e),q,n.lockY),e.bindUp=n.bindUp,e.bindLeft=n.bindLeft,e.bindRight=n.bindRight,e.bindDown=n.bindDown,e}return b(a,[{key:"init",value:function(){null!==this.bindUp&&this.gamepad.registerKeybinding(this.bindUp,this),null!==this.bindLeft&&this.gamepad.registerKeybinding(this.bindLeft,this),null!==this.bindRight&&this.gamepad.registerKeybinding(this.bindRight,this),null!==this.bindDown&&this.gamepad.registerKeybinding(this.bindDown,this)}},{key:"isKeyPressed",value:function(t){return null!==t&&Y(this,W).hasOwnProperty(t)&&Y(this,W)[t]>0}},{key:"createTouchEventObject",value:function(t){return{id:this.id,action:t,type:this.type,x:Math.round(this.mouseX/this.cR*100),y:Math.round(this.mouseY/this.cR*100)}}},{key:"setActive",value:function(t){e(l(a.prototype),"setActive",this).call(this,t,!1),t.hasOwnProperty("key")&&(Y(this,W).hasOwnProperty(t.key)||(Y(this,W)[t.key]=0),["start"].includes(t.type)&&Y(this,W)[t.key]++,["end","cancel"].includes(t.type)&&Y(this,W)[t.key]--);var i=this.cR;Y(this,K)||(t.hasOwnProperty("x")&&(this.mouseX=this.cX-this.gamepad.stage.screenToCanvasX(t.x),this.mouseX=Math.min(Math.abs(this.mouseX),i)*Math.sign(this.mouseX),this.mouseX*=-1),this.isKeyPressed(this.bindLeft)&&(this.mouseX=-i),this.isKeyPressed(this.bindRight)&&(this.mouseX=i),this.isKeyPressed(this.bindLeft)&&this.isKeyPressed(this.bindRight)&&(this.mouseX=0),this.isActive||(this.mouseX=0)),Y(this,q)||(t.hasOwnProperty("y")&&(this.mouseY=this.cY-this.gamepad.stage.screenToCanvasY(t.y),this.mouseY=Math.min(Math.abs(this.mouseY),i)*Math.sign(this.mouseY)),this.isKeyPressed(this.bindUp)&&(this.mouseY=i),this.isKeyPressed(this.bindDown)&&(this.mouseY=-i),this.isKeyPressed(this.bindUp)&&this.isKeyPressed(this.bindDown)&&(this.mouseY=0),this.isActive||(this.mouseY=0));var n="touchmove";this.isActive&&1==this.touchCount&&"start"===t.type&&(n="touchstart"),this.isActive||(n="touchend"),this.gamepad.handleTouchEventCallbacks(this.createTouchEventObject(n))}},{key:"draw",value:function(t){this.cX=this.getX(t),this.cY=this.getY(t),this.cR=25*this.getScaleY(t),this.path=new Path2D,this.path.arc(this.cX,this.cY,this.cR,0,4*Math.PI,!0),this.isActive?t.fillStyle="rgba(85, 85, 85, 0.8)":t.fillStyle="rgba(100, 100, 100, 0.8)",t.fill(this.path),t.beginPath(),t.arc(this.cX+this.mouseX,this.cY-this.mouseY,15*this.getScaleY(t),0,4*Math.PI,!0),t.fillStyle="rgba(130, 130, 130, 1)",t.fill()}}]),a}();exports.GamepadJoystick=G;var U=new WeakMap,_=new WeakMap,z=new WeakMap,V=new WeakMap,J=new WeakMap,H=new WeakMap,N=function(){function t(){v(this,t),k(this,"stage",void 0),w(this,U,{writable:!0,value:void 0}),w(this,_,{writable:!0,value:void 0}),w(this,z,{writable:!0,value:{}}),w(this,V,{writable:!0,value:{}}),w(this,J,{writable:!0,value:{}}),w(this,H,{writable:!0,value:[]}),k(this,"showDebug",!1),k(this,"showAltText",!0),k(this,"enableVibration",!0),this.stage=new I("GamePad",document.querySelector(".gamepad-wrapper")),this.addEventListeners()}return b(t,[{key:"addEventListeners",value:function(){var t=this,e=["keydown","keyup"];for(var i in e)document.addEventListener(e[i],function(e){return t.handleKeyEvent(e)},!1);for(var i in e=["touchstart","touchend","touchcancel","touchmove"])this.stage.canvas.addEventListener(e[i],function(e){return t.handleTouchEvent(e)},!1);for(var i in e=["mousedown","mouseup","mousemove"])this.stage.canvas.addEventListener(e[i],function(e){return t.handleMouseEvent(e)},!1)}},{key:"registerKeybinding",value:function(t,e){Y(this,V)[t]=e}},{key:"handleKeyEvent",value:function(t){if(Y(this,J).hasOwnProperty(t.keyCode)||(Y(this,J)[t.keyCode]={pressed:!1}),Y(this,V).hasOwnProperty(t.key)){var e="Key ".concat(t.key),i=Y(this,V)[t.key],n={touchId:e,key:t.key,type:{keydown:"start",keyup:"end"}[t.type]};switch(t.type){case"keydown":if(Y(this,J)[t.keyCode].pressed)return;Y(this,J)[t.keyCode].pressed=!0,Y(this,z)[e]={},Y(this,z)[e].target=i,Y(this,z)[e].hasOwnProperty("target")&&null!=Y(this,z)[e].target&&Y(this,z)[e].target.setActive(n);break;case"keyup":if(!Y(this,J)[t.keyCode].pressed)return;Y(this,J)[t.keyCode].pressed=!1,Y(this,z)[e].hasOwnProperty("target")&&null!=Y(this,z)[e].target&&Y(this,z)[e].target.setActive(n),delete Y(this,z)[e]}}this.stage.touches=Y(this,z),this.debugTouches()}},{key:"handleMouseEvent",value:function(t){this.processGamepadTouchEvent({x:t.clientX,y:t.clientY,touchId:"mouse",type:{mousedown:"start",mouseup:"end",mousemove:"move"}[t.type]})}},{key:"handleTouchEvent",value:function(t){t.preventDefault();for(var e={touchstart:"start",touchend:"end",touchcancel:"end",touchmove:"move"},i=0;it.length)&&(e=t.length);for(var r=0,n=new Array(e);r=0;--o){var a=this.tryEntries[o],i=a.completion;if("root"===a.tryLoc)return n("end");if(a.tryLoc<=this.prev){var c=r.call(a,"catchLoc"),u=r.call(a,"finallyLoc");if(c&&u){if(this.prev=0;--n){var o=this.tryEntries[n];if(o.tryLoc<=this.prev&&r.call(o,"finallyLoc")&&this.prev=0;--e){var r=this.tryEntries[e];if(r.finallyLoc===t)return this.complete(r.completion,r.afterLoc),L(r),h}},catch:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var r=this.tryEntries[e];if(r.tryLoc===t){var n=r.completion;if("throw"===n.type){var o=n.arg;L(r)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(t,e,r){return this.delegate={iterator:P(t),resultName:e,nextLoc:r},"next"===this.method&&(this.arg=void 0),h}},t}function y(t,e,r,n,o,a,i){try{var c=t[a](i),u=c.value}catch(l){return void r(l)}c.done?e(u):Promise.resolve(u).then(n,o)}function p(t){return function(){var e=this,r=arguments;return new Promise(function(n,o){var a=t.apply(e,r);function i(t){y(a,n,o,i,c,"next",t)}function c(t){y(a,n,o,i,c,"throw",t)}i(void 0)})}}var m="service-worker.js";navigator.serviceWorker&&navigator.serviceWorker.register(m,{scope:"/microbit-gamepad/"}).then(function(t){t.onupdatefound=function(){var r=t.installing;null!=r&&(r.onstatechange=function(){"installed"===r.state&&(navigator.serviceWorker.controller?(0,e.notif_info)("New content is available, relaunch the app to install it."):(0,e.notif_success)("Content is cached for offline use."))})},t.update()}).catch(function(t){(0,e.notif_warn)("Could not install service worker..."),console.error("Error during service worker registration:",t)}),document.getElementById("btn_ignore_landscape_warning").addEventListener("click",function(){document.body.classList.add("ignore-landscape-warning")}),navigator.bluetooth||(console.error("You do not have a bluetooth enabled browser, you need to have a bluetooth enabled browser..."),(0,e.notif_alert)("Your browser does not seem to support bluetooth, try using Google Chrome or Microsoft Edge."));var b=new r.Gamepad;window.gamepad=b;var w=new t.uBitBLE;window.ubit=w,document.querySelector(".settings-dialog #layout").addEventListener("change",function(t){b.setGamepadLayout(t.target.value),localStorage.setItem("gamepadLayout",t.target.value),document.querySelector(".button-states pre").innerHTML="No buttons pressed yet"}),null===localStorage.getItem("gamepadLayout")&&localStorage.setItem("gamepadLayout","1"),b.setGamepadLayout(localStorage.getItem("gamepadLayout")),document.querySelector(".button-states pre").innerHTML="No buttons pressed yet",document.querySelector(".settings-dialog #layout").value=localStorage.getItem("gamepadLayout"),document.querySelector(".settings-dialog #show-touches").addEventListener("change",function(t){b.stage.showTouches=t.target.checked,localStorage.setItem("showTouches",t.target.checked)}),null===localStorage.getItem("showTouches")&&localStorage.setItem("showTouches",!1),b.stage.showTouches="true"==localStorage.getItem("showTouches"),document.querySelector(".settings-dialog #show-touches").checked="true"==localStorage.getItem("showTouches"),document.querySelector(".settings-dialog #show-gamepad-alt-text").addEventListener("change",function(t){b.showAltText=t.target.checked,localStorage.setItem("showAltText",t.target.checked)}),null===localStorage.getItem("showAltText")&&localStorage.setItem("showAltText",!1),b.showAltText="true"==localStorage.getItem("showAltText"),document.querySelector(".settings-dialog #show-gamepad-alt-text").checked="true"==localStorage.getItem("showAltText"),document.querySelector(".settings-dialog #enable-haptic").addEventListener("change",function(t){b.enableVibration=t.target.checked,localStorage.setItem("enableHaptic",t.target.checked)}),null===localStorage.getItem("enableHaptic")&&localStorage.setItem("enableHaptic",!0),b.enableVibration="true"==localStorage.getItem("enableHaptic"),document.querySelector(".settings-dialog #enable-haptic").checked="true"==localStorage.getItem("enableHaptic"),document.querySelector(".settings-dialog #enable-debug").addEventListener("change",function(t){b.showDebug=t.target.checked,t.target.checked?document.body.classList.add("debug"):document.body.classList.remove("debug"),localStorage.setItem("enableDebug",t.target.checked)}),null===localStorage.getItem("enableDebug")&&localStorage.setItem("enableDebug",!1),b.showDebug="true"==localStorage.getItem("enableDebug"),"true"===localStorage.getItem("enableDebug")?document.body.classList.add("debug"):document.body.classList.remove("debug"),document.querySelector(".settings-dialog #enable-debug").checked="true"==localStorage.getItem("enableDebug"),document.querySelector("#btn_show_settings").addEventListener("click",function(){document.querySelector(".settings-dialog").classList.add("shown")}),document.querySelector("#btn_hide_settings").addEventListener("click",function(){document.querySelector(".settings-dialog").classList.remove("shown")}),document.querySelector("#btn_disconnect").addEventListener("click",function(){w.disconnect()}),document.getElementById("btn_connect").addEventListener("click",p(v().mark(function t(){return v().wrap(function(t){for(;;)switch(t.prev=t.next){case 0:return navigator.bluetooth||(0,e.notif_alert)("You need a bluetooth enabled browser for this app to work, try chrome."),t.prev=1,t.next=4,w.searchDevice();case 4:t.next=9;break;case 6:t.prev=6,t.t0=t.catch(1),(0,e.notif_alert)("Could not connect to device: ".concat(t.t0,"."));case 9:case"end":return t.stop()}},t,null,[[1,6]])})));var S={};b.onTouchEvent(function(t){["touchstart","touchmove"].includes(t.action)&&(S[t.id]=d({state:!0},t)),["touchend"].includes(t.action)&&(S[t.id]=d({state:!1},t));for(var e="",r=0,n=Object.entries(S);r screen.availWidth) && (!document.body.classList.contains(\"ignore-landscape-warning\"))){\n waiting_timer = setInterval(() => {\n if( (screen.availHeight < screen.availWidth) || (document.body.classList.contains(\"ignore-landscape-warning\"))){\n clearInterval(waiting_timer);\n waiting_timer = undefined;\n notif(notif_queue.pop());\n }\n }, 1000);\n notif_queue.push(notif_c);\n return;\n }\n\n let notif_elem = document.createElement(\"div\");\n notif_elem.className = \"notification\";\n notif_elem.appendChild(notif_c[0]);\n notif_elem.appendChild(notif_c[1]);\n\n notification_area.appendChild(notif_elem);\n\n notification_area.classList.add(\"show\");\n setTimeout(() => {\n notification_area.classList.remove(\"show\");\n notif_elem.querySelector(\"p\").style.opacity = \"0\";\n setTimeout(() => {\n notification_area.removeChild(notif_elem);\n if (notif_queue.length > 0) {\n notif(notif_queue.pop());\n }\n }, 1000);\n }, 10000);\n } else {\n notif_queue.push(notif_c);\n }\n}\n\nexport function notif_alert(alert_str) {\n let div = document.createElement(\"div\");\n div.className = \"notification-content\";\n\n let text = document.createElement(\"p\");\n text.innerHTML = alert_str;\n div.appendChild(text);\n\n let icon = document.createElement(\"i\");\n icon.className = \"alert fas fa-exclamation-triangle\";\n div.appendChild(icon);\n\n notif([icon, div]);\n}\n\nexport function notif_warn(alert_str) {\n let div = document.createElement(\"div\");\n div.className = \"notification-content\";\n\n let text = document.createElement(\"p\");\n text.innerHTML = alert_str;\n div.appendChild(text);\n\n let icon = document.createElement(\"i\");\n icon.className = \"warning fas fa-exclamation-triangle\";\n div.appendChild(icon);\n\n notif([icon, div]);\n}\n\nexport function notif_info(info_str) {\n let div = document.createElement(\"div\");\n div.className = \"notification-content\";\n\n let text = document.createElement(\"p\");\n text.innerHTML = info_str;\n div.appendChild(text);\n\n let icon = document.createElement(\"i\");\n icon.className = \"info fas fa-info-circle\";\n div.appendChild(icon);\n\n notif([icon, div]);\n}\n\nexport function notif_success(success_str) {\n let div = document.createElement(\"div\");\n div.className = \"notification-content\";\n\n let text = document.createElement(\"p\");\n text.innerHTML = success_str;\n div.appendChild(text);\n\n let icon = document.createElement(\"i\");\n icon.className = \"success fas fa-check-circle\";\n div.appendChild(icon);\n\n notif([icon, div]);\n}\n","import { notif_alert, notif_warn, notif_info, notif_success } from './notification';\n/*\n * This code is written with a lot of help from these resources:\n * https://github.com/antefact/microBit.js/blob/master/src/microBit.js\n * https://gist.github.com/kotobuki/7c67f8b9361e08930da1a5cfcfb0653f\n * https://lancaster-university.github.io/microbit-docs/resources/bluetooth/bluetooth_profile.html\n */\nconst UART_SERVICE_UUID = \"6e400001-b5a3-f393-e0a9-e50e24dcca9e\";\n/* Used for reading UART data from micro bit */\nconst UART_TX_CHARACTERISTIC_UUID = \"6e400002-b5a3-f393-e0a9-e50e24dcca9e\";\n/* Used for writing UART data to micro bit */\nconst UART_RX_CHARACTERISTIC_UUID = \"6e400003-b5a3-f393-e0a9-e50e24dcca9e\";\n/* The event service characteristic (which extends the uBit message bus over bluetooth) */\nconst EVENT_SERVICE_UUID = \"e95d93af-251d-470a-a062-fa1922dfa9a8\";\n/* This should be read once connected, as the ubit will advertise which events it wants to subscribe to */\nconst UBIT_REQUIREMENT_CHARACTERISTIC_UUID = \"e95db84c-251d-470a-a062-fa1922dfa9a8\";\n/* The characteristic where we should write the events we wish to be informed of from the microbit */\nconst CLIENTREQUIREMENTS_CHARACTERISTIC_UUID = \"e95d23c4-251d-470a-a062-fa1922dfa9a8\"\n/* The characteristic used for reading EventService messages */\nconst UBITEVENT_CHARACTERISTIC_UUID = \"e95d9775-251d-470a-a062-fa1922dfa9a8\";\n/* The characteristic used for writing EventService messages */\nconst CLIENTEVENT_CHARACTERISTIC_UUID = \"e95d5404-251d-470a-a062-fa1922dfa9a8\";\n\n/* This table is retrieved from this site:\n * https://github.com/lancaster-university/microbit-dal/blob/master/inc/bluetooth/MESEvents.h */\nexport const MESEvents = {\n MES_DPAD_CONTROLLER_ID: 1104,\n MES_DPAD_BUTTON_A_DOWN: 1,\n MES_DPAD_BUTTON_A_UP: 2,\n MES_DPAD_BUTTON_B_DOWN: 3,\n MES_DPAD_BUTTON_B_UP: 4,\n MES_DPAD_BUTTON_C_DOWN: 5,\n MES_DPAD_BUTTON_C_UP: 6,\n MES_DPAD_BUTTON_D_DOWN: 7,\n MES_DPAD_BUTTON_D_UP: 8,\n MES_DPAD_BUTTON_1_DOWN: 9,\n MES_DPAD_BUTTON_1_UP: 10,\n MES_DPAD_BUTTON_2_DOWN: 11,\n MES_DPAD_BUTTON_2_UP: 12,\n MES_DPAD_BUTTON_3_DOWN: 13,\n MES_DPAD_BUTTON_3_UP: 14,\n MES_DPAD_BUTTON_4_DOWN: 15,\n MES_DPAD_BUTTON_4_UP: 16\n}\n\nclass BluetoothService {\n static gattEventQueue = [];\n SERVICE_UUID = null;\n\n static doGattEvent() {\n if (BluetoothService.gattEventQueue <= 0) { return; }\n BluetoothService.gattEventQueue.pop()();\n }\n}\n\nclass EventService extends BluetoothService {\n /* Implements methods for interacting with microbit EventService */\n static SERVICE_UUID = EVENT_SERVICE_UUID;\n service;\n\n constructor(service, ubitEvent) {\n super();\n this.service = service;\n this.ubitEvent = ubitEvent;\n console.debug(\"EventService initialized.\");\n }\n\n sendEvent(event_type, event_value) {\n BluetoothService.gattEventQueue.push(() => {\n this.ubitEvent.writeValue(\n new Uint16Array([event_type, event_value])\n );\n });\n }\n\n static async getService(gattServer) {\n console.debug(\"Getting EventService\");\n let service = await gattServer.getPrimaryService(EventService.SERVICE_UUID);\n console.debug(\"Getting ClientEvent characteristic\");\n let ubitEventCharacteristic = await service.getCharacteristic(CLIENTEVENT_CHARACTERISTIC_UUID);\n return new EventService(service, ubitEventCharacteristic);\n }\n}\n\nclass UartService extends BluetoothService {\n /* Implements methods for interacting with microbit UartService */\n static SERVICE_UUID = UART_SERVICE_UUID;\n handlers = [];\n\n constructor(service, uartTx, uartRx) {\n super();\n this.service = service;\n this.uartTx = uartTx;\n this.uartRx = uartRx;\n console.debug(\"UartService initialized.\");\n }\n\n async sendUart(str, isVolatile=true) {\n let encoder = new TextEncoder();\n try {\n await this.uartRx.writeValue(\n encoder.encode(str)\n )\n } catch (e) {\n if (!isVolatile) {\n console.error(e);\n }\n }\n }\n\n #onUartTx(e) {\n for (let i = 0; i < this.handlers.length; i++) {\n this.handlers[i]();\n }\n }\n\n onUartTx(callback) {\n this.handlers.push(callback);\n }\n\n static async getService(gattServer) {\n console.debug(\"Getting UartService\");\n let service = await gattServer.getPrimaryService(UartService.SERVICE_UUID);\n\n console.debug(\"Getting Uart characteristics\");\n \n let uartTxCharacteristic = await service.getCharacteristic(UART_TX_CHARACTERISTIC_UUID);\n await uartTxCharacteristic.startNotifications();\n await uartTxCharacteristic.addEventListener(\"characteristicvaluechanged\", (e) => {\n this.#onUartTx(e);\n });\n\n let uartRxCharacteristic = await service.getCharacteristic(UART_RX_CHARACTERISTIC_UUID);\n\n return new UartService(service, uartTxCharacteristic, uartRxCharacteristic);\n }\n\n\n}\n\nexport class uBitBLE {\n eventService;\n eventServiceAvailable = false;\n uartService;\n uartServiceAvailable = false;\n uartTxHandlers = [];\n device;\n\n constructor() {\n this.onConnectCallback = [];\n this.onDisconnectCallback = [];\n this.pushInterval = setInterval(BluetoothService.doGattEvent, 40);\n }\n\n #onDisconnect(e) {\n console.debug(\"Device disconnected\", e);\n for (let i = 0; i < this.onDisconnectCallback.length; i++) {\n this.onDisconnectCallback[i]();\n }\n }\n\n #onConnect() {\n console.debug(\"Device connected\");\n for (let i = 0; i < this.onConnectCallback.length; i++) {\n this.onConnectCallback[i]();\n }\n }\n\n onConnect(callbackFunction) {\n this.onConnectCallback.push(callbackFunction);\n }\n\n onDisconnect(callbackFunction) {\n this.onDisconnectCallback.push(callbackFunction);\n }\n\n isConnected() {\n if (this.device) {\n return this.device.gatt.connected;\n } else {\n return false;\n }\n }\n\n disconnect() {\n if (this.isConnected()) {\n this.device.gatt.disconnect();\n }\n }\n\n async searchDevice() {\n this.device = await navigator.bluetooth.requestDevice({\n filters: [{namePrefix: \"BBC micro:bit\"}],\n optionalServices: [EVENT_SERVICE_UUID, UART_SERVICE_UUID]\n });\n this.device.addEventListener('gattserverdisconnected', (e) => this.#onDisconnect(e));\n console.log(\"Connected to new device\", this.device.name, this.device.id);\n\n console.debug(\"Connection to GATT server...\");\n const server = await this.device.gatt.connect()\n\n this.#onConnect();\n\n console.debug(\"Getting services...\");\n\n try {\n const eventService = await EventService.getService(server);\n this.eventService = eventService;\n this.eventServiceAvailable = true;\n } catch (e) {\n this.eventServiceAvailable = false;\n console.debug(\"Could not get EventService\");\n notif_warn(\"Connected device's firmware does not support bluetooth EventService, gamepad will not work.\");\n }\n\n try {\n const uartService = await UartService.getService(server);\n this.uartService = uartService;\n for (let i = 0; i < this.uartTxHandlers.length; i++) {\n this.uartService.onUartTx(this.uartTxHandlers[i]);\n }\n this.uartServiceAvailable = true;\n } catch (e) {\n this.uartServiceAvailable = false;\n console.debug(\"Could not get UartService\", e)\n notif_info(\"Connected device's firmware does not support bluetooth UartService, joysticks won't work.\");\n }\n }\n\n sendEvent(event_type, event_value) {\n if (this.isConnected() && this.eventServiceAvailable) {\n this.eventService.sendEvent(event_type, event_value);\n } else {\n console.debug(`Could not send event {${event_type}, ${event_value}}, because: ${this.isConnected() ? \"Device does not have EventService characteristic\" : \"No device connected\"}.`);\n }\n }\n\n sendUart(str) {\n if (this.isConnected() && this.uartServiceAvailable) {\n this.uartService.sendUart(str);\n } else {\n console.debug(`Could not send uart data, because: ${this.isConnected() ? \"Device does not have UartService characteristic\" : \"No device connected\"}.`);\n }\n }\n\n onUartTx(callback) {\n this.uartTxHandlers.push(callback);\n if (this.uartServiceAvailable) {\n this.uartService.onUartTx(callback);\n }\n }\n\n}\n\nfunction getSupportedProperties(characteristic) {\n let supportedProperties = [];\n for (const p in characteristic.properties) {\n if (characteristic.properties[p] === true) {\n supportedProperties.push(p.toUpperCase());\n }\n }\n return '[' + supportedProperties.join(', ') + ']';\n}\n\nfunction eventByteArrayToString(event) {\n let receivedData = [];\n for (var i = 0; i < event.target.value.byteLength; i++) {\n receivedData[i] = event.target.value.getUint8(i);\n }\n return String.fromCharCode.apply(null, receivedData);\n}\n","class CanvasStage {\n canvas;\n #dpi = window.devicePixelRatio;\n #width;\n #height;\n #ctx;\n #elements = [];\n touches = {};\n showTouches = false;\n\n constructor(id, node) {\n this.canvas = document.createElement(\"canvas\");\n this.canvas.setAttribute(\"id\", id);\n node.appendChild(this.canvas);\n\n addEventListener(\"resize\", () => this.resize());\n this.resize();\n\n console.debug(\"Created canvas\", this.canvas);\n\n setInterval(() => {\n this.drawElements();\n }, 10);\n }\n\n /* Resizes the canvas to be the correct size for the current screen */\n resize() {\n this.#ctx = this.canvas.getContext(\"2d\");\n this.#height = +getComputedStyle(this.canvas).getPropertyValue(\"height\").slice(0, -2);\n this.#width = +getComputedStyle(this.canvas).getPropertyValue(\"width\").slice(0, -2);\n this.canvas.setAttribute('height', this.#height * this.#dpi);\n this.canvas.setAttribute('width', this.#width * this.#dpi);\n }\n\n /* Translate a screen x coordinate to a canvas x coordinate */\n screenToCanvasX(x) { return x * this.#dpi; }\n\n /* Translate a screen y coordinate to a canvas y coordinate */\n screenToCanvasY(y) { return y * this.#dpi; }\n\n /* Get target at position, i.e. the element that intersects said position */\n getTarget(x, y) {\n x *= this.#dpi;\n y *= this.#dpi;\n for (let i = 0; i < this.#elements.length; i++) {\n if ((this.#elements[i] instanceof TouchElement)\n && (this.#elements[i].collides(this.#ctx, x, y))) {\n return this.#elements[i];\n }\n }\n }\n\n /* Redraws all elements of the stage on the screen. */\n drawElements() {\n this.#ctx.clearRect(0, 0, this.#width * this.#dpi, this.#height * this.#dpi);\n for (let i = 0; i < this.#elements.length; i++) {\n this.#elements[i].draw(this.#ctx);\n }\n if (this.showTouches) {\n this.drawTouches();\n }\n }\n\n /* Draws all touches on the screen, used to debug */\n drawTouches(e) {\n const colors = [\"200, 0, 0\", \"0, 200, 0\", \"0, 0, 200\", \"200, 200, 0\", \"200, 200, 200\"]\n for (const [identifier, touch] of Object.entries(this.touches)) {\n this.#ctx.beginPath();\n this.#ctx.arc(touch.x * this.#dpi, touch.y * this.#dpi, 20 * this.#dpi, 0, 2*Math.PI, true);\n this.#ctx.fillStyle = `rgba(${colors[identifier]}, 0.2)`;\n this.#ctx.fill();\n\n this.#ctx.lineWidth = 2.0;\n this.#ctx.strokeStyle = `rgba(${colors[identifier]}, 0.8)`;\n this.#ctx.stroke();\n }\n }\n\n /* Add a element to the stage */\n addElement(element) {\n this.#elements.push(element);\n element.init();\n }\n\n /* Remove a element from the stage by id */\n removeElementById(id) {\n for (let i = 0; i < this.#elements.length; i++) {\n if (id === this.#elements[i].id) {\n this.#elements.splice(i, 1);\n return;\n }\n }\n }\n\n /* Wipe all elements from the stage */\n removeAllElements() {\n this.#elements.splice(0, this.#elements.length);\n }\n\n}\n\nclass Element {\n gamepad;\n id;\n x;\n y;\n alignX;\n alignY;\n path;\n isInside;\n isActive;\n type = \"Element\";\n \n constructor(opts, gamepad) {\n let _opts = Object.assign({\n id: null,\n x: 0,\n y: 0,\n alignX: null,\n alignY: null\n }, opts);\n this.id = _opts.id;\n this.x = _opts.x;\n this.y = _opts.y;\n this.alignX = _opts.alignX;\n this.alignY = _opts.alignY;\n this.gamepad = gamepad;\n }\n\n /* Used for initializing the element onto the stage */\n init() {}\n\n /* Get the x-axis scaling factor (currently unused, only the y scaling factor is in use) */\n getScaleX(ctx) { \n return ctx.canvas.width / 100;\n }\n\n /* Get the y-axis scaling factor */\n getScaleY(ctx) { \n return ctx.canvas.height / 100;\n }\n\n /* Get the canvas x position of this element, adjusted from the virtual canvas coordinates */\n getX(ctx) {\n let x = this.x * this.getScaleY(ctx);\n if (this.alignX === \"center\") {\n x = (ctx.canvas.width / 2) + x;\n }\n if (this.alignX === \"right\") {\n x = ctx.canvas.width - x;\n }\n return x;\n }\n\n /* Get the canvas y position of this element, adjusted from the virtual canvas coordinates */\n getY(ctx) {\n let y = this.y * this.getScaleY(ctx);\n if (this.alignY === \"center\") {\n y = (ctx.canvas.height / 2) + y;\n }\n if (this.alignY === \"bottom\") {\n y = ctx.canvas.height - y;\n }\n return y;\n }\n\n /* Used to draw the element onto a canvas context */\n draw(ctx) {}\n\n /* Used to check wether the coordinates is inside this element */\n collides(ctx, x, y) {\n this.isInside = ctx.isPointInPath(this.path, x, y);\n return this.isInside;\n }\n\n}\n\nexport class Square extends Element {\n draw(ctx) {\n this.path = new Path2D();\n let w = this.getScaleY(ctx) * 20;\n this.path.rect(this.getX(ctx) - (w/2), this.getY(ctx) - (w/2), w, w);\n ctx.fillStyle = `rgba(100, 100, 100, 0.8)`;\n ctx.fill(this.path);\n }\n}\n\nclass TouchElement extends Element {\n type = \"TouchElement\";\n touchCount = 0;\n\n setActive(e, doCallbacks = true) {\n if ([\"end\", \"cancel\"].includes(e.type)) { this.touchCount--; }\n let eState = e.type == \"start\";\n if ((eState !== this.isActive) && (this.touchCount == 0)) {\n this.isActive = eState;\n if (doCallbacks) {\n this.gamepad.handleTouchEventCallbacks(this.createTouchEventObject(\n this.isActive ? \"touchstart\" : \"touchend\"\n ));\n }\n }\n if (e.type == \"start\") { this.touchCount++; }\n }\n\n createTouchEventObject(action) {\n return {\n id: this.id,\n action: action,\n type: this.type\n }\n }\n\n}\n\nexport class GamepadButton extends TouchElement {\n shape;\n altText;\n altTextAlign;\n type = \"GamepadButton\";\n\n constructor(opts) {\n let _opts = Object.assign({\n keyboardButton: null,\n altText: null,\n altTextAlign: \"left\",\n shape: \"round\"\n }, opts);\n super(opts);\n this.keyboardButton = _opts.keyboardButton;\n this.shape = _opts.shape;\n this.altText = _opts.altText;\n this.altTextAlign = _opts.altTextAlign;\n }\n\n init() {\n if (this.keyboardButton !== null) {\n this.gamepad.registerKeybinding(this.keyboardButton, this);\n }\n }\n\n draw(ctx) {\n this.path = new Path2D();\n if (this.shape === \"round\") {\n this.path.arc(this.getX(ctx), this.getY(ctx), this.getScaleY(ctx) * 10, 0, 4*Math.PI, true);\n } else if (this.shape === \"square\") {\n let w = this.getScaleY(ctx) * 20;\n this.path.rect(this.getX(ctx) - (w/2), this.getY(ctx) - (w/2), w, w);\n }\n if (this.isActive) {\n ctx.fillStyle = `rgba(80, 80, 80, 1)`;\n } else {\n ctx.fillStyle = `rgba(100, 100, 100, 0.8)`;\n }\n ctx.fill(this.path);\n\n let s = `${Math.floor((this.getScaleY(ctx)*8).toString())}px 'Press Start 2P'`;\n ctx.font = s;\n ctx.textBaseline = \"middle\";\n ctx.textAlign = \"center\";\n ctx.fillStyle = `rgba(255, 255, 255, 1)`;\n ctx.fillText(this.id, this.getX(ctx), this.getY(ctx));\n\n if ((this.altText !== null) && (this.gamepad.showAltText)) {\n ctx.beginPath();\n ctx.font = `${Math.floor((this.getScaleY(ctx)*3).toString())}px 'Press Start 2P'`;\n ctx.textBaseline = \"middle\";\n ctx.textAlign = \"center\";\n ctx.fillStyle = `rgba(150, 150, 150, 1)`;\n let ax = this.getX(ctx);\n let ay = this.getY(ctx);\n switch (this.altTextAlign) {\n case \"left\":\n ax -= (this.getScaleY(ctx) * 13);\n break;\n case \"right\":\n ax += (this.getScaleY(ctx) * 13);\n break;\n case \"top\":\n ay -= (this.getScaleY(ctx) * 13);\n break;\n case \"bottom\":\n ay += (this.getScaleY(ctx) * 13);\n break;\n }\n ctx.fillText(this.altText, ax, ay);\n }\n }\n\n}\n\nexport class GamepadJoystick extends TouchElement {\n type = \"GamepadJoystick\";\n mouseX = 0;\n mouseY = 0;\n cR = 0;\n cX = 0;\n cY = 0;\n\n #lockX;\n #lockY;\n\n #pressedKeys = {};\n\n constructor(opts) {\n let _opts = Object.assign({\n lockX: false,\n lockY: false,\n autoCenter: true,\n bindUp: null,\n bindLeft: null,\n bindRight: null,\n bindDown: null\n }, opts);\n super(opts);\n this.#lockX = _opts.lockX;\n this.#lockY = _opts.lockY;\n this.bindUp = _opts.bindUp;\n this.bindLeft = _opts.bindLeft;\n this.bindRight = _opts.bindRight;\n this.bindDown = _opts.bindDown;\n }\n\n init() {\n if (this.bindUp !== null) {\n this.gamepad.registerKeybinding(this.bindUp, this);\n }\n if (this.bindLeft !== null) {\n this.gamepad.registerKeybinding(this.bindLeft, this);\n }\n if (this.bindRight !== null) {\n this.gamepad.registerKeybinding(this.bindRight, this);\n }\n if (this.bindDown !== null) {\n this.gamepad.registerKeybinding(this.bindDown, this);\n }\n }\n\n isKeyPressed(key) {\n return ((key !== null)\n && (this.#pressedKeys.hasOwnProperty(key))\n && (this.#pressedKeys[key] > 0));\n }\n\n createTouchEventObject(action) {\n return {\n id: this.id,\n action: action,\n type: this.type,\n x: Math.round((this.mouseX / this.cR) * 100),\n y: Math.round((this.mouseY / this.cR) * 100)\n }\n }\n\n setActive(e) {\n super.setActive(e, false);\n if (e.hasOwnProperty(\"key\")) {\n if (!this.#pressedKeys.hasOwnProperty(e.key)) {\n this.#pressedKeys[e.key] = 0;\n }\n if ([\"start\"].includes(e.type)) {\n this.#pressedKeys[e.key]++;\n }\n if ([\"end\", \"cancel\"].includes(e.type)) {\n this.#pressedKeys[e.key]--;\n }\n }\n\n let max = this.cR\n if (!this.#lockX) {\n if (e.hasOwnProperty(\"x\")) {\n this.mouseX = this.cX - this.gamepad.stage.screenToCanvasX(e.x);\n this.mouseX = Math.min(Math.abs(this.mouseX), max) * Math.sign(this.mouseX); \n this.mouseX *= -1;\n }\n if (this.isKeyPressed(this.bindLeft)) { this.mouseX = -max; } \n if (this.isKeyPressed(this.bindRight)) { this.mouseX = max; }\n if (this.isKeyPressed(this.bindLeft) && this.isKeyPressed(this.bindRight)) { this.mouseX = 0; }\n if (!this.isActive) { this.mouseX = 0; }\n }\n if (!this.#lockY) {\n if (e.hasOwnProperty(\"y\")) {\n this.mouseY = this.cY - this.gamepad.stage.screenToCanvasY(e.y);\n this.mouseY = Math.min(Math.abs(this.mouseY), max) * Math.sign(this.mouseY); \n } \n if (this.isKeyPressed(this.bindUp)) { this.mouseY = max; } \n if (this.isKeyPressed(this.bindDown)) { this.mouseY = -max; }\n if (this.isKeyPressed(this.bindUp) && this.isKeyPressed(this.bindDown)) { this.mouseY = 0; }\n if (!this.isActive) { this.mouseY = 0; }\n }\n\n let action = \"touchmove\";\n if (this.isActive && (this.touchCount == 1) && (e.type === \"start\")) {\n action = \"touchstart\";\n }\n if (!this.isActive) {\n action = \"touchend\";\n }\n this.gamepad.handleTouchEventCallbacks(this.createTouchEventObject(action));\n }\n\n draw(ctx) {\n this.cX = this.getX(ctx);\n this.cY = this.getY(ctx);\n this.cR = this.getScaleY(ctx) * 25;\n\n this.path = new Path2D();\n this.path.arc(this.cX, this.cY, this.cR, 0, 4*Math.PI, true);\n if (this.isActive) {\n ctx.fillStyle = `rgba(85, 85, 85, 0.8)`;\n } else {\n ctx.fillStyle = `rgba(100, 100, 100, 0.8)`;\n }\n ctx.fill(this.path);\n\n ctx.beginPath();\n ctx.arc(this.cX + this.mouseX, this.cY - this.mouseY, this.getScaleY(ctx) * 15, 0, 4*Math.PI, true);\n ctx.fillStyle = `rgba(130, 130, 130, 1)`;\n ctx.fill();\n\n }\n\n}\n\nexport class Gamepad {\n stage;\n #width;\n #height;\n\n #touches = {};\n #keybindings = {};\n #keystates = {};\n #touchEventCallbacks = [];\n\n showDebug = false;\n showAltText = true;\n enableVibration = true;\n\n constructor() {\n this.stage = new CanvasStage(\"GamePad\", document.querySelector(\".gamepad-wrapper\"));\n this.addEventListeners();\n }\n\n addEventListeners() {\n let ev = [\"keydown\", \"keyup\"];\n for(var e in ev) {\n document.addEventListener(ev[e], (e) => this.handleKeyEvent(e), false);\n }\n ev = [\"touchstart\", \"touchend\", \"touchcancel\", \"touchmove\"];\n for(var e in ev) {\n this.stage.canvas.addEventListener(ev[e], (e) => this.handleTouchEvent(e), false);\n }\n ev = [\"mousedown\", \"mouseup\", \"mousemove\"];\n for(var e in ev) {\n this.stage.canvas.addEventListener(ev[e], (e) => this.handleMouseEvent(e), false);\n }\n }\n\n /* Used by stage elements to register themselves with some keybinding */\n registerKeybinding(binding, element) {\n this.#keybindings[binding] = element;\n }\n\n /* Event handler for keyboard events */\n handleKeyEvent(e) {\n const typedict = {\"keydown\": \"start\", \"keyup\": \"end\"}\n if (!this.#keystates.hasOwnProperty(e.keyCode)) {\n this.#keystates[e.keyCode] = {pressed: false};\n }\n if (this.#keybindings.hasOwnProperty(e.key)) {\n let id = `Key ${e.key}`\n let target = this.#keybindings[e.key];\n let gtEvent = {\n touchId: id,\n key: e.key,\n type: typedict[e.type]\n };\n switch (e.type) {\n case \"keydown\":\n if (this.#keystates[e.keyCode].pressed) { return; }\n this.#keystates[e.keyCode].pressed = true;\n\n this.#touches[id] = {};\n this.#touches[id].target = target;\n if (this.#touches[id].hasOwnProperty(\"target\")\n && this.#touches[id].target != null) {\n this.#touches[id].target.setActive(gtEvent);\n }\n break;\n case \"keyup\":\n if (!this.#keystates[e.keyCode].pressed) { return; }\n this.#keystates[e.keyCode].pressed = false;\n\n if (this.#touches[id].hasOwnProperty(\"target\")\n && this.#touches[id].target != null) {\n this.#touches[id].target.setActive(gtEvent);\n }\n delete this.#touches[id];\n break;\n }\n }\n this.stage.touches = this.#touches;\n this.debugTouches();\n }\n\n /* Event handler for mouse events, will just translate the event to a more common form\n * before further processing. */\n handleMouseEvent(e) {\n const typedict = {\"mousedown\": \"start\", \"mouseup\": \"end\", \"mousemove\": \"move\"}\n this.processGamepadTouchEvent({\n x: e.clientX,\n y: e.clientY,\n touchId: \"mouse\",\n type: typedict[e.type]\n });\n }\n\n /* Event handler for touch events, will just translate the event to a more common form\n * before further processing. */\n handleTouchEvent(e) {\n e.preventDefault();\n const typedict = {\"touchstart\": \"start\", \"touchend\": \"end\", \"touchcancel\": \"end\", \"touchmove\": \"move\"}\n for (let i = 0; i < e.changedTouches.length; i++) {\n let touch = e.changedTouches[i];\n this.processGamepadTouchEvent({\n x: touch.clientX,\n y: touch.clientY,\n touchId: touch.identifier,\n type: typedict[e.type]\n });\n }\n }\n\n /* Event handler for processing standarized touch/mouse events. */\n processGamepadTouchEvent(gtEvent) {\n let target = this.stage.getTarget(gtEvent.x, gtEvent.y)\n switch (gtEvent.type) {\n case \"start\":\n this.#touches[gtEvent.touchId] = {};\n this.#touches[gtEvent.touchId].target = target;\n case \"move\":\n if (this.#touches.hasOwnProperty(gtEvent.touchId)) {\n this.#touches[gtEvent.touchId].x = gtEvent.x;\n this.#touches[gtEvent.touchId].y = gtEvent.y;\n\n if (this.#touches[gtEvent.touchId].hasOwnProperty(\"target\")\n && this.#touches[gtEvent.touchId].target != null) {\n this.#touches[gtEvent.touchId].target.setActive(gtEvent);\n }\n }\n break;\n\n case \"end\":\n case \"cancel\":\n if (this.#touches[gtEvent.touchId].hasOwnProperty(\"target\")\n && this.#touches[gtEvent.touchId].target != null) {\n this.#touches[gtEvent.touchId].target.setActive(gtEvent);\n }\n delete this.#touches[gtEvent.touchId];\n break;\n\n default:\n console.log(\"Unknown touch event\", gtEvent.type);\n }\n this.stage.touches = this.#touches;\n this.debugTouches();\n }\n\n /* Update the debug text with all current touches */\n debugTouches() {\n let s = \"\";\n if (this.showDebug) {\n for (const [i, t] of Object.entries(this.#touches)) {\n s += `[${i}] `\n if (t.hasOwnProperty(\"x\")) {\n s += `x: ${Math.round(t.x, 2)}, y: ${Math.round(t.y)},`\n }\n s += `target: ${t.target ? t.target.id : null}\\n`;\n }\n }\n document.querySelector(\".gamepad-touches\").innerHTML = s;\n }\n\n /* Used by elements to process callbacks on actions to outside the gamepad */\n handleTouchEventCallbacks(e) {\n if (this.enableVibration && [\"touchstart\", \"touchend\"].includes(e.action)) {\n try {\n window.navigator.vibrate(5);\n } catch (e) {\n console.error(e);\n }\n }\n for (let i = 0; i < this.#touchEventCallbacks.length; i++) {\n this.#touchEventCallbacks[i](e);\n }\n }\n\n /* Register a method as a callback for gamepad touch events */\n onTouchEvent(callback) {\n this.#touchEventCallbacks.push(callback);\n }\n\n /* Add a list of elements to the gamepad stage */\n addElements(elements) {\n for (let i = 0; i < elements.length; i++) {\n elements[i].gamepad = this;\n this.stage.addElement(elements[i]);\n }\n }\n\n /* Remove a list of elements from the gamepad stage by id */\n removeElementsById(elementIds) {\n for (let i = 0; i < elementIds.length; i++) {\n this.stage.removeElementById(elementIds[i]);\n }\n }\n\n /* Remove all elements from the gamepad stage */\n removeAllElements() {\n this.stage.removeAllElements();\n }\n\n /* Initialize gamepad with a predefined layout */\n setGamepadLayout(variant) {\n console.debug(`Setting the gamepad layout to ${variant}, deleting all current elements.`);\n this.removeAllElements();\n switch (variant) {\n case \"1\":\n this.addElements([\n new Square({id: \"filler1\", x: 40, y: 0, alignX: \"left\", alignY: \"center\"}),\n new GamepadButton({id: \"C\", x: 20, y: 0, alignX: \"left\", alignY: \"center\", shape: \"square\", keyboardButton: \"ArrowLeft\", altText: \"◀\", altTextAlign: \"right\"}),\n new GamepadButton({id: \"D\", x: 60, y: 0, alignX: \"left\", alignY: \"center\", shape: \"square\", keyboardButton: \"ArrowRight\",altText: \"▶\", altTextAlign: \"left\"}),\n new GamepadButton({id: \"A\", x: 40, y: -20, alignX: \"left\", alignY: \"center\", shape: \"square\", keyboardButton: \"ArrowUp\", altText: \"▲\", altTextAlign: \"bottom\"}),\n new GamepadButton({id: \"B\", x: 40, y: 20, alignX: \"left\", alignY: \"center\", shape: \"square\", keyboardButton: \"ArrowDown\", altText: \"▼\", altTextAlign: \"top\"}),\n new GamepadButton({id: \"3\", x: 20, y: 0, alignX: \"right\", alignY: \"center\", shape: \"round\", keyboardButton: \"3\", altText: \"3\", altTextAlign: \"left\"}),\n new GamepadButton({id: \"4\", x: 60, y: 0, alignX: \"right\", alignY: \"center\", shape: \"round\", keyboardButton: \"4\", altText: \"4\", altTextAlign: \"right\"}),\n new GamepadButton({id: \"1\", x: 40, y: -20, alignX: \"right\", alignY: \"center\", shape: \"round\", keyboardButton: \"1\", altText: \"1\", altTextAlign: \"bottom\"}),\n new GamepadButton({id: \"2\", x: 40, y: 20, alignX: \"right\", alignY: \"center\", shape: \"round\", keyboardButton: \"2\", altText: \"2\", altTextAlign: \"top\"}),\n ])\n break;\n case \"2\":\n this.addElements([\n new Square({id: \"filler2\", x: 40, y: 0, alignX: \"right\", alignY: \"center\"}),\n new Square({id: \"filler1\", x: 40, y: 0, alignX: \"left\", alignY: \"center\"}),\n new GamepadButton({id: \"C\", x: 20, y: 0, alignX: \"left\", alignY: \"center\", shape: \"square\", keyboardButton: \"ArrowLeft\", altText: \"◀\", altTextAlign: \"right\"}),\n new GamepadButton({id: \"D\", x: 60, y: 0, alignX: \"left\", alignY: \"center\", shape: \"square\", keyboardButton: \"ArrowRight\",altText: \"▶\", altTextAlign: \"left\"}),\n new GamepadButton({id: \"A\", x: 40, y: -20, alignX: \"left\", alignY: \"center\", shape: \"square\", keyboardButton: \"ArrowUp\", altText: \"▲\", altTextAlign: \"bottom\"}),\n new GamepadButton({id: \"B\", x: 40, y: 20, alignX: \"left\", alignY: \"center\", shape: \"square\", keyboardButton: \"ArrowDown\", altText: \"▼\", altTextAlign: \"top\"}),\n new GamepadButton({id: \"3\", x: 20, y: 0, alignX: \"right\", alignY: \"center\", shape: \"square\", keyboardButton: \"3\"}),\n new GamepadButton({id: \"4\", x: 60, y: 0, alignX: \"right\", alignY: \"center\", shape: \"square\", keyboardButton: \"4\"}),\n new GamepadButton({id: \"1\", x: 40, y: -20, alignX: \"right\", alignY: \"center\", shape: \"square\", keyboardButton: \"1\"}),\n new GamepadButton({id: \"2\", x: 40, y: 20, alignX: \"right\", alignY: \"center\", shape: \"square\", keyboardButton: \"2\"}),\n ])\n break;\n case \"9\":\n this.addElements([\n new GamepadJoystick({id: \"left\", x: 40, y: 0, alignX: \"left\", alignY: \"center\", lockX: true, bindUp: \"ArrowUp\", bindDown: \"ArrowDown\"}),\n new GamepadJoystick({id: \"right\", x: 40, y: 0, alignX: \"right\", alignY: \"center\", lockY: true, bindLeft: \"ArrowLeft\", bindRight: \"ArrowRight\"})\n ]);\n break;\n }\n }\n\n}\n","import { uBitBLE, MESEvents } from \"./uBit\";\nimport { notif_alert, notif_warn, notif_info, notif_success } from './notification';\nimport { Gamepad } from './gamepad';\n\n/* Attempt to install service worker */\nlet sw = \"service-worker.js\";\nif (navigator.serviceWorker) {\n navigator.serviceWorker.register(\n sw, {scope: '/microbit-gamepad/'}\n ).then(registration => {\n registration.onupdatefound = () => {\n const installingWorker = registration.installing;\n if (installingWorker == null) { return; }\n installingWorker.onstatechange = () => {\n if (installingWorker.state === \"installed\") {\n if (navigator.serviceWorker.controller) {\n notif_info(\"New content is available, relaunch the app to install it.\");\n } else {\n notif_success(\"Content is cached for offline use.\");\n }\n }\n };\n };\n registration.update();\n }).catch(error => {\n notif_warn(\"Could not install service worker...\");\n console.error(\"Error during service worker registration:\", error);\n });\n}\n\n/* Allow the ignore-landscape-warning button to work */\ndocument.getElementById(\"btn_ignore_landscape_warning\").addEventListener(\"click\", () => {\n document.body.classList.add(\"ignore-landscape-warning\");\n});\n\n/* Show a warning if bluetooth is unavailable in the browser. */\nif (!navigator.bluetooth) {\n //alert(\"Bluetooth not enabled in your browser, this won't work...\");\n console.error(\"You do not have a bluetooth enabled browser, you need to have a bluetooth enabled browser...\");\n notif_alert(\"Your browser does not seem to support bluetooth, try using Google Chrome or Microsoft Edge.\");\n}\n\n/* Define and initialize things */\nlet gamepad = new Gamepad();\nwindow.gamepad = gamepad;\nlet ubit = new uBitBLE();\nwindow.ubit = ubit;\n\n/* Setup storage and picker for the gamepad layout */\ndocument.querySelector(\".settings-dialog #layout\").addEventListener(\"change\", (v) => {\n gamepad.setGamepadLayout(v.target.value);\n localStorage.setItem(\"gamepadLayout\", v.target.value);\n document.querySelector(\".button-states pre\").innerHTML = \"No buttons pressed yet\";\n});\nif (localStorage.getItem(\"gamepadLayout\") === null) { localStorage.setItem(\"gamepadLayout\", \"1\"); }\ngamepad.setGamepadLayout(localStorage.getItem(\"gamepadLayout\"));\ndocument.querySelector(\".button-states pre\").innerHTML = \"No buttons pressed yet\";\ndocument.querySelector(\".settings-dialog #layout\").value = localStorage.getItem(\"gamepadLayout\");\n\n/* Setup storage for toggling touches */\ndocument.querySelector(\".settings-dialog #show-touches\").addEventListener(\"change\", (v) => {\n gamepad.stage.showTouches = v.target.checked;\n localStorage.setItem(\"showTouches\", v.target.checked);\n});\nif (localStorage.getItem(\"showTouches\") === null) { localStorage.setItem(\"showTouches\", false); }\ngamepad.stage.showTouches = localStorage.getItem(\"showTouches\") == \"true\";\ndocument.querySelector(\".settings-dialog #show-touches\").checked = localStorage.getItem(\"showTouches\") == \"true\";\n\n/* Setup storage for toggling alt text */\ndocument.querySelector(\".settings-dialog #show-gamepad-alt-text\").addEventListener(\"change\", (v) => {\n gamepad.showAltText = v.target.checked;\n localStorage.setItem(\"showAltText\", v.target.checked);\n});\nif (localStorage.getItem(\"showAltText\") === null) { localStorage.setItem(\"showAltText\", false); }\ngamepad.showAltText = localStorage.getItem(\"showAltText\") == \"true\";\ndocument.querySelector(\".settings-dialog #show-gamepad-alt-text\").checked = localStorage.getItem(\"showAltText\") == \"true\";\n\n/* Setup storage for toggling vibration/haptic feedback */\ndocument.querySelector(\".settings-dialog #enable-haptic\").addEventListener(\"change\", (v) => {\n gamepad.enableVibration = v.target.checked;\n localStorage.setItem(\"enableHaptic\", v.target.checked);\n});\nif (localStorage.getItem(\"enableHaptic\") === null) { localStorage.setItem(\"enableHaptic\", true); }\ngamepad.enableVibration = localStorage.getItem(\"enableHaptic\") == \"true\";\ndocument.querySelector(\".settings-dialog #enable-haptic\").checked = localStorage.getItem(\"enableHaptic\") == \"true\";\n\n/* Setup storage for toggling debug mode */\ndocument.querySelector(\".settings-dialog #enable-debug\").addEventListener(\"change\", (v) => {\n gamepad.showDebug = v.target.checked;\n if (v.target.checked) {\n document.body.classList.add(\"debug\");\n } else {\n document.body.classList.remove(\"debug\");\n }\n localStorage.setItem(\"enableDebug\", v.target.checked);\n});\nif (localStorage.getItem(\"enableDebug\") === null) { localStorage.setItem(\"enableDebug\", false); }\ngamepad.showDebug = localStorage.getItem(\"enableDebug\") == \"true\";\nif (localStorage.getItem(\"enableDebug\") === \"true\") {\n document.body.classList.add(\"debug\");\n} else {\n document.body.classList.remove(\"debug\");\n}\ndocument.querySelector(\".settings-dialog #enable-debug\").checked = localStorage.getItem(\"enableDebug\") == \"true\";\n\n/* Setup buttons for opening/closing settings panel */\ndocument.querySelector(\"#btn_show_settings\").addEventListener(\"click\", () => {\n document.querySelector(\".settings-dialog\").classList.add(\"shown\");\n});\ndocument.querySelector(\"#btn_hide_settings\").addEventListener(\"click\", () => {\n document.querySelector(\".settings-dialog\").classList.remove(\"shown\");\n});\n\n/* Setup actions for bluetooth connect/disconnect buttons */\ndocument.querySelector(\"#btn_disconnect\").addEventListener(\"click\", () => {\n ubit.disconnect();\n});\ndocument.getElementById(\"btn_connect\").addEventListener(\"click\", async () => {\n if (!navigator.bluetooth) {\n notif_alert(\"You need a bluetooth enabled browser for this app to work, try chrome.\");\n }\n try {\n await ubit.searchDevice();\n } catch (e) {\n notif_alert(`Could not connect to device: ${e}.`);\n }\n});\n\n/* Handle gamepad events */\nlet gamepadState = {};\ngamepad.onTouchEvent(e => {\n /* This is just for the debug data */\n if ([\"touchstart\", \"touchmove\"].includes(e.action)) {\n gamepadState[e.id] = {state: true, ...e};\n }\n if ([\"touchend\"].includes(e.action)) {\n gamepadState[e.id] = {state: false, ...e};\n }\n let debugString = \"\";\n for (const [key, value] of Object.entries(gamepadState)) {\n debugString += `${key}: ${value.state ? 'Pressed' : 'Not pressed'}`;\n if (value.hasOwnProperty(\"x\")) {\n debugString += ` (x: ${value.x}, y: ${value.y})`;\n }\n debugString += `\\n`;\n }\n document.querySelector(\".button-states pre\").innerHTML = debugString;\n});\n\ngamepad.onTouchEvent(e => {\n const event_type = MESEvents.MES_DPAD_CONTROLLER_ID;\n let event_value = null;\n if (e.action == \"touchstart\") {\n if (e.id == \"A\") {\n event_value = MESEvents.MES_DPAD_BUTTON_A_DOWN;\n } else if (e.id == \"B\") {\n event_value = MESEvents.MES_DPAD_BUTTON_B_DOWN;\n } else if (e.id == \"C\") {\n event_value = MESEvents.MES_DPAD_BUTTON_C_DOWN;\n } else if (e.id == \"D\") {\n event_value = MESEvents.MES_DPAD_BUTTON_D_DOWN;\n } else if (e.id == \"1\") {\n event_value = MESEvents.MES_DPAD_BUTTON_1_DOWN;\n } else if (e.id == \"2\") {\n event_value = MESEvents.MES_DPAD_BUTTON_2_DOWN;\n } else if (e.id == \"3\") {\n event_value = MESEvents.MES_DPAD_BUTTON_3_DOWN;\n } else if (e.id == \"4\") {\n event_value = MESEvents.MES_DPAD_BUTTON_4_DOWN;\n }\n } else if (e.action == \"touchend\") {\n if (e.id == \"A\") {\n event_value = MESEvents.MES_DPAD_BUTTON_A_UP;\n } else if (e.id == \"B\") {\n event_value = MESEvents.MES_DPAD_BUTTON_B_UP;\n } else if (e.id == \"C\") {\n event_value = MESEvents.MES_DPAD_BUTTON_C_UP;\n } else if (e.id == \"D\") {\n event_value = MESEvents.MES_DPAD_BUTTON_D_UP;\n } else if (e.id == \"1\") {\n event_value = MESEvents.MES_DPAD_BUTTON_1_UP;\n } else if (e.id == \"2\") {\n event_value = MESEvents.MES_DPAD_BUTTON_2_UP;\n } else if (e.id == \"3\") {\n event_value = MESEvents.MES_DPAD_BUTTON_3_UP;\n } else if (e.id == \"4\") {\n event_value = MESEvents.MES_DPAD_BUTTON_4_UP;\n }\n }\n if ((ubit.isConnected()) && (event_value != null)) {\n ubit.sendEvent(event_type, event_value);\n }\n\n if ((e.id == \"right\") && e.hasOwnProperty(\"x\")) {\n ubit.sendUart(`x:${e.x}\\n`);\n }\n if ((e.id == \"left\") && e.hasOwnProperty(\"y\")) {\n ubit.sendUart(`y:${e.y}\\n`);\n }\n});\n\n/* Setup handlers for ubit (bluetooth) events */\nubit.onConnect(() => {\n document.body.classList.add(\"connected\");\n});\n\nubit.onDisconnect(() => {\n document.body.classList.remove(\"connected\");\n});\n\n"]} \ No newline at end of file diff --git a/service-worker.js b/service-worker.js index 54d70b9..1c84182 100644 --- a/service-worker.js +++ b/service-worker.js @@ -1,7 +1,7 @@ var APP_PREFIX = 'microbitgamepad' // Identifier for this app (this needs to be consistent across every cache update) -var VERSION = '1.0.2' // Version of the off-line cache (change this value everytime you want to update cache) +var VERSION = '1.0.3' // Version of the off-line cache (change this value everytime you want to update cache) var CACHE_NAME = APP_PREFIX + VERSION -var URLS = ['/microbit-gamepad/','/microbit-gamepad/fa-brands-400.04246ac6.woff','/microbit-gamepad/fa-brands-400.a1db9459.woff2','/microbit-gamepad/fa-brands-400.ac88be85.svg','/microbit-gamepad/fa-brands-400.ae1da9aa.eot','/microbit-gamepad/fa-brands-400.e8eab21c.ttf','/microbit-gamepad/fa-regular-400.4f946da8.woff','/microbit-gamepad/fa-regular-400.6adc9fcd.eot','/microbit-gamepad/fa-regular-400.6e35f891.woff2','/microbit-gamepad/fa-regular-400.a215af91.ttf','/microbit-gamepad/fa-regular-400.ed807156.svg','/microbit-gamepad/fa-solid-900.88a6089c.woff','/microbit-gamepad/fa-solid-900.ab906712.woff2','/microbit-gamepad/fa-solid-900.e5b19c09.svg','/microbit-gamepad/fa-solid-900.ec16851e.ttf','/microbit-gamepad/fa-solid-900.ef18b3bb.eot','/microbit-gamepad/index.html','/microbit-gamepad/main.cf2d245a.js','/microbit-gamepad/main.cf2d245a.js.map','/microbit-gamepad/maskable_icon_x128.ae1e74f9.png','/microbit-gamepad/maskable_icon_x144.fc14e37c.png','/microbit-gamepad/maskable_icon_x152.ce6c1441.png','/microbit-gamepad/maskable_icon_x384.1af8edb2.png','/microbit-gamepad/maskable_icon_x512.a9ac38fa.png','/microbit-gamepad/maskable_icon_x72.b43d35ec.png','/microbit-gamepad/maskable_icon_x96.e03bc2e8.png','/microbit-gamepad/pwa-192x192.b3dbd8bb.png','/microbit-gamepad/pwa-512x512.b27071b7.png','/microbit-gamepad/service-worker.js','/microbit-gamepad/styles.ac699ce3.css','/microbit-gamepad/styles.ac699ce3.css.map'] // This will be replaced by the deploy-script +var URLS = ['/microbit-gamepad/','/microbit-gamepad/fa-brands-400.04246ac6.woff','/microbit-gamepad/fa-brands-400.a1db9459.woff2','/microbit-gamepad/fa-brands-400.ac88be85.svg','/microbit-gamepad/fa-brands-400.ae1da9aa.eot','/microbit-gamepad/fa-brands-400.e8eab21c.ttf','/microbit-gamepad/fa-regular-400.4f946da8.woff','/microbit-gamepad/fa-regular-400.6adc9fcd.eot','/microbit-gamepad/fa-regular-400.6e35f891.woff2','/microbit-gamepad/fa-regular-400.a215af91.ttf','/microbit-gamepad/fa-regular-400.ed807156.svg','/microbit-gamepad/fa-solid-900.88a6089c.woff','/microbit-gamepad/fa-solid-900.ab906712.woff2','/microbit-gamepad/fa-solid-900.e5b19c09.svg','/microbit-gamepad/fa-solid-900.ec16851e.ttf','/microbit-gamepad/fa-solid-900.ef18b3bb.eot','/microbit-gamepad/index.html','/microbit-gamepad/main.fa7f93a2.js','/microbit-gamepad/main.fa7f93a2.js.map','/microbit-gamepad/maskable_icon_x128.ae1e74f9.png','/microbit-gamepad/maskable_icon_x144.fc14e37c.png','/microbit-gamepad/maskable_icon_x152.ce6c1441.png','/microbit-gamepad/maskable_icon_x384.1af8edb2.png','/microbit-gamepad/maskable_icon_x512.a9ac38fa.png','/microbit-gamepad/maskable_icon_x72.b43d35ec.png','/microbit-gamepad/maskable_icon_x96.e03bc2e8.png','/microbit-gamepad/pwa-192x192.b3dbd8bb.png','/microbit-gamepad/pwa-512x512.b27071b7.png','/microbit-gamepad/service-worker.js','/microbit-gamepad/styles.ac699ce3.css','/microbit-gamepad/styles.ac699ce3.css.map'] // This will be replaced by the deploy-script // Respond with cached resources self.addEventListener('fetch', function (e) { -- cgit v1.2.3